我是一个老的(但不是太老)Java程序员,决定学习C++.但我已经看到了很多C++编程风格,是......好吧,该死的!
把类定义放在头文件中的所有东西,以及不同源文件中的方法 - 调用函数无处不在,而不是使用类中的方法.所有这一切似乎......错了!
最后,有没有理由让我继续对OOP进行大屠杀,以及编程中任何好的和正义的东西,或者我可以忽略那些老式的C++约定,并使用我良好的Java编程风格?
顺便说一句,我正在学习C++,因为我想做游戏编程.
这是一个例子:
在C++网站上,我找到了一个Windows实现:
class WinClass { public: WinClass (WNDPROC wndProc, char const * className, HINSTANCE hInst); void Register () { ::RegisterClass (&_class); } private: WNDCLASS _class; };
该类位于头文件和构造函数中:
WinClass::WinClass (WNDPROC wndProc, char const * className, HINSTANCE hInst) { _class.style = 0; _class.lpfnWndProc = wndProc; // Window Procedure: mandatory _class.cbClsExtra = 0; _class.cbWndExtra = 0; _class.hInstance = hInst; // Owner of the class: mandatory _class.hIcon = 0; _class.hCursor = ::LoadCursor (0, IDC_ARROW); // Optional _class.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // Optional _class.lpszMenuName = 0; _class.lpszClassName = className; // Mandatory }
位于.cpp源文件中.
我能做的是:
class WinClass { public: WinClass (WNDPROC wndProc, char const * className, HINSTANCE hInst) { _class.style = 0; _class.lpfnWndProc = wndProc; // Window Procedure: mandatory _class.cbClsExtra = 0; _class.cbWndExtra = 0; _class.hInstance = hInst; // Owner of the class: mandatory _class.hIcon = 0; _class.hCursor = ::LoadCursor (0, IDC_ARROW); // Optional _class.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // Optional _class.lpszMenuName = 0; _class.lpszClassName = className; // Mandatory } void Register () { ::RegisterClass (&_class); } private: WNDCLASS _class; };
现在构造函数在它的类中.
除了其他人在这里所说的,还有更重要的问题:
1)大型翻译单元导致更长的编译时间和更大的目标文件大小.
2)循环依赖!这是最重要的一个.它几乎总是可以通过拆分标头和源来修复:
// Vehicle.h class Wheel { private: Car& m_parent; public: Wheel( Car& p ) : m_parent( p ) { std::cout << "Car has " << m_parent.numWheels() << " wheels." << std::endl; } }; class Car { private: std::vector< Wheel > m_wheels; public: Car() { for( int i=0; i<4; ++i ) m_wheels.push_back( Wheel( *this ) ); } int numWheels() { return m_wheels.size(); } }
无论你把它们放在什么顺序中,一个人总是缺乏另一个的定义,即使使用前向声明它也行不通,因为在函数体中使用了关于每个类的符号的细节.
但是如果你将它们分成适当的.h和.cpp文件并使用前向声明,它将满足编译器:
//Wheel.h //------- class Car; class Wheel { private: Car& m_parent; public: Wheel( Car& p ); }; //Wheel.cpp //--------- #include "Wheel.h" #include "Car.h" Wheel::Wheel( Car& p ) : m_parent( p ) { std::cout << "Car has " << m_parent.numWheels() << " wheels." << std::endl; } //Car.h //----- class Wheel; class Car { private: std::vector< Wheel > m_wheels; public: Car(); int numWheels(); } //Car.cpp //------- #include "Car.h" #include "Wheel.h" Car::Car() { for( int i=0; i<4; ++i ) m_wheels.push_back( Wheel( *this ) ); } int Car::numWheels() { return m_wheels.size(); }
现在,实际上必须知道关于第二类的细节的代码可以只包括头文件,该文件不需要知道关于第一类的细节.
标头只提供声明,而源文件提供定义.或者说出来的另一种方式:标题告诉你那里有什么(哪些符号有效使用)和源告诉编译器符号实际上做了什么.在C++中,除了有效符号之外,您不需要任何其他内容来开始使用它.
相信C++有这个成语的原因,因为如果你不这样做,你会为自己制造很多麻烦.我知道 :/
代码构建时,C++预处理器构建转换单元.它以.cpp文件开头,解析#includes从头文件中抓取文本,并生成一个包含所有头文件和.cpp代码的大文本文件.然后,此翻译单元将编译为将在您定位的平台上运行的代码.每个翻译单元最终作为一个目标文件.
因此,.h文件包含在多个翻译单元中,而.cpp文件只包含在一个文件中.
如果.h文件包含许多内容(包括实现),那么翻译单元将相应地更大.编译时间会增加,当您更改标题中的任何内容时...每个使用它的翻译单元都需要重新编译.
因此,最小化.h文件中的内容可以大大缩短编译时间.您可以编辑.cpp文件来更改功能,只需要重建一个翻译单元.
完成关于编译的故事...
一旦所有翻译单元都构建到目标文件(本平台的本机二进制代码).链接器完成它的工作,并将它们拼接到您的.exe,.dll或.lib文件中..lib文件可以链接到另一个构建中,因此可以在多个.exe或.dll中重用它.
我认为Java编译模型更先进,并且您放置代码的位置含义较少.
.h和.cpp文件经常将声明与定义分开,这里有一些顺序和概念封装.标题有'what',实现文件有'how'.
我发现java中的一个文件中的所有内容都是杂乱无章的.所以,每个人都给自己.
C++惯例和习语都有深思熟虑的推理,就像其他语言对其社区采用的习语一样.
我认为在编写Java时最好使用java(作为动词),在使用C++时最好使用C++!你会理解为什么有这种语言的经验.与切换到任何新语言相同.
大多数答案都是关于标题和编译单元的分离.我同意大多数情况,你必须使用它,因为它更高效,更清洁,对同事更友好,或者只是因为......(你会发现编译时间优势不会太长).
无论如何,我只想发布问题的另一部分:C++是OOP大师吗?
C++是一种多范式语言.它允许程序,面向对象和通用编程.这是一种美德,而不是缺陷.如果您愿意,您仍然可以使用纯OOP,但您的代码肯定会受益于学习和知道何时使用其他范例.
作为一个例子,我不喜欢实用程序类,其中所有成员函数都是静态的,并且没有数据.没有理由创建实用程序类,而不仅仅是在一个通用名称下将一组自由函数组合在一起.你必须用Java来做,因为它坚持纯粹的OO语法,正如Tom所赞,它与真正的OO不同.在C++中,定义命名空间和一组自由函数提供了类似的解决方案,您无需通过声明私有构造函数来锁定对象的创建,也不允许用户从空类中实例化非感知对象.
对我而言,经验(我曾经是一名只有Java的程序员)告诉我,有不同的工具可以更好地适应不同的问题.我还没有找到一把金锤 ,C++的优势在于你可以为每个不同的任务选择不同的锤子,即使在同一个编译单元中也是如此.
更正:litb在评论中告诉我WNDCLASS是win32 API中的结构.因此,对不使用初始化列表的类的批评显然是无稽之谈,因此我将其删除.
找到适合自己的风格,就像其他人一样.没有人强迫你使用其中一种"丑陋"的风格,除非你的雇主执行指导文件.;-)
虽然请记住,将成员函数定义放在类中而不是外部具有不同的语义.当您在类中定义成员函数时,它隐式内联,无论好坏.
这就是所谓的分离界面和实现.这样,客户就可以在不必浏览所有代码的情况下弄清楚如何调用您的类.坦率地说,我认为人们认为它"错误" 几乎是疯了.
但是,您会很高兴地发现很多C++开发人员都同意您的意见.所以继续把你的整个实现放在*.h文件中.有一所风格的学校与你同意.
无处不在地调用函数,而不是在类中使用方法; 所有这一切似乎......错了!
最后,我有什么理由继续对OOP进行大屠杀
好吧,调用不属于类的函数不是OOP - 它是程序编程.因此,我相信你真的很难摆脱OOP的心态.(当然C++有许多弊端,但程序编程不是其中之一.)
C++是一种多范式语言,而不仅仅是一种OO语言.模板是通用编程的一种形式,可以应用于C++的过程,OOP和元编程范例.使用下一个C++标准,您将看到一些功能范例也被添加.
把类定义放在头文件中的所有东西,以及不同源文件中的方法;
这源于C编程语言,早在70年代.C++旨在向后兼容C语言.
D编程语言试图修复C++的许多问题(正如我前面提到的那样),但保留了C++的优点 - 包括C++支持的所有各种范例:程序,OOP,元编程和功能.
如果你想突破OOP心态,试试D! 然后,当你感觉如何混合和匹配不同的范例时,你可以(如果你愿意的话)回到C++并学会处理它的语法和其他弊病.
PS我每天都使用C++而且我是它的粉丝 - 这是我最喜欢的编程语言.但正如任何有经验的C++程序员所知,C++确实有它的弊病.但是一旦你掌握了不同的范例并且知道如何解决C++的问题,你将拥有一个非常强大的工具(因为这就是所有的语言).
祝你的冒险好运!