当前位置:  开发笔记 > 编程语言 > 正文

C++编程风格

如何解决《C++编程风格》经验,为你挑选了7个好方法。

我是一个老的(但不是太老)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> Adam..:

除了其他人在这里所说的,还有更重要的问题:

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++有这个成语的原因,因为如果你不这样做,你会为自己制造很多麻烦.我知道 :/


这是我发现的唯一答案,实际上并不像"java做错了;分离是好的",但实际上提供了为什么它是它的原因.你可以提一下c ++是在一遍中编译的,并且有一些特殊的结构支持这种风格(前向声明)
并且不存在模块概念,因此需要头文件来提供通常在关于模块的元信息中找到的信息.
这两点大致对应于你的两点,但你的观点解释了这些点(大标题(因为缺少模块)/循环依赖(记住:编译两遍,没有循环依赖问题了)).

2> Scott Langha..:

代码构建时,C++预处理器构建转换单元.它以.cpp文件开头,解析#includes从头文件中抓取文本,并生成一个包含所有头文件和.cpp代码的大文本文件.然后,此翻译单元将编译为将在您定位的平台上运行的代码.每个翻译单元最终作为一个目标文件.

因此,.h文件包含在多个翻译单元中,而.cpp文件只包含在一个文件中.

如果.h文件包含许多内容(包括实现),那么翻译单元将相应地更大.编译时间会增加,当您更改标题中的任何内容时...每个使用它的翻译单元都需要重新编译.

因此,最小化.h文件中的内容可以大大缩短编译时间.您可以编辑.cpp文件来更改功能,只需要重建一个翻译单元.

完成关于编译的故事...
一旦所有翻译单元都构建到目标文件(本平台的本机二进制代码).链接器完成它的工作,并将它们拼接到您的.exe,.dll或.lib文件中..lib文件可以链接到另一个构建中,因此可以在多个.exe或.dll中重用它.

我认为Java编译模型更先进,并且您放置代码的位置含义较少.



3> Scott Langha..:

.h和.cpp文件经常将声明与定义分开,这里有一些顺序和概念封装.标题有'what',实现文件有'how'.

我发现java中的一个文件中的所有内容都是杂乱无章的.所以,每个人都给自己.

C++惯例和习语都有深思熟虑的推理,就像其他语言对其社区采用的习语一样.

我认为在编写Java时最好使用java(作为动词),在使用C++时最好使用C++!你会理解为什么有这种语言的经验.与切换到任何新语言相同.



4> David Rodríg..:

大多数答案都是关于标题和编译单元的分离.我同意大多数情况,你必须使用它,因为它更高效,更清洁,对同事更友好,或者只是因为......(你会发现编译时间优势不会太长).

无论如何,我只想发布问题的另一部分:C++是OOP大师吗?

C++是一种多范式语言.它允许程序,面向对象和通用编程.这是一种美德,而不是缺陷.如果您愿意,您仍然可以使用纯OOP,但您的代码肯定会受益于学习和知道何时使用其他范例.

作为一个例子,我不喜欢实用程序类,其中所有成员函数都是静态的,并且没有数据.没有理由创建实用程序类,而不仅仅是在一个通用名称下将一组自由函数组合在一起.你必须用Java来做,因为它坚持纯粹的OO语法,正如Tom所赞,它与真正的OO不同.在C++中,定义命名空间和一组自由函数提供了类似的解决方案,您无需通过声明私有构造函数来锁定对象的创建,也不允许用户从空类中实例化非感知对象.

对我而言,经验(我曾经是一名只有Java的程序员)告诉我,有不同的工具可以更好地适应不同的问题.我还没有找到一把金锤 ,C++的优势在于你可以为每个不同的任务选择不同的锤子,即使在同一个编译单元中也是如此.

更正:litb在评论中告诉我WNDCLASS是win32 API中的结构.因此,对不使用初始化列表的类的批评显然是无稽之谈,因此我将其删除.



5> 小智..:

找到适合自己的风格,就像其他人一样.没有人强迫你使用其中一种"丑陋"的风格,除非你的雇主执行指导文件.;-)

虽然请记住,将成员函数定义放在类中而不是外部具有不同的语义.当您在类中定义成员函数时,它隐式内联,无论好坏.



6> T.E.D...:

这就是所谓的分离界面和实现.这样,客户就可以在不必浏览所有代码的情况下弄清楚如何调用您的类.坦率地说,我认为人们认为它"错误" 几乎是了.

但是,您会很高兴地发现很多C++开发人员都同意您的意见.所以继续把你的整个实现放在*.h文件中.有一所风格的学校与你同意.



7> Uhall..:

无处不在地调用函数,而不是在类中使用方法; 所有这一切似乎......错了!

最后,我有什么理由继续对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++的问题,你将拥有一个非常强大的工具(因为这就是所有的语言).

祝你的冒险好运!

推荐阅读
帆侮听我悄悄说星星
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有