本文转载自微信公众号《程序喵大师》,作者程序喵大师。转载本文请联系程序大师喵公众号。提前声明,本文仅代表程序喵的个人观点,文中肯定有部分或大部分观点与大家的想法不一致。可以在评论区交流哦!什么是班级?我理解类是对现实世界的描述,也是对业务的描述。抽象,类设计的好不好,很大程度上取决于你抽象的巧合。类设计中最重要的一点是代表某个领域的概念。以我最近在做的音视频剪辑为例。在剪辑业务中,有轨道的概念,也有片段的概念。每个轨道可以包含多个片段,这个时候,有一些问题需要考虑。在现实世界中,轨迹可以被复制吗?碎片可以复制吗?曲目可以移动吗?碎片可以移动吗?那么我们可以进一步将现实世界中的tracks和fragments抽象成类是的,可以分为两个类,track类和fragment类。这两个类是否需要提供复制构造函数和移动构造函数完全取决于它们在现实世界中的样子。tips:类名要清楚的告诉用户这个类的用途。一个类需要自己写构造函数和析构函数吗?反正我每次定义一个类,都会显式的写出构造函数和析构函数,哪怕是空实现,即使我不写编译器,也会默认情况下生成一个,自动生成的一个称为默认构造函数。但是我不想依赖编译器,建议大家不要太依赖编译器。显式写出构造函数和析构函数也是一个好习惯。在大多数情况下,类并不是那么简单。大多数情况下,编译器默认生成的构造函数和解析的Constructors不一定是我们想要的。默认的构造函数不会初始化我们的数据成员,所以我们需要自己写一个构造函数。事实上,构造函数中的语句不能称为初始化。这是一个赋值操作。真正的初始化可以通过初始化列表或者声明直接给成员赋初值,类似下面的代码。如果我们的类有指针数据成员,我们已经在某处为它分配了一块内存,编译器自动生成的析构函数默认不会释放这块内存。为了避免这种潜在的风险,最好写一个!Tips:编译器在某些情况下会生成移动构造函数或移动赋值运算符,但要记住这些情况太麻烦了。建议手动控制。要的时候自己写一个,不要的时候就删掉。classA{public:A():a_(2){}//一种初始化,标准的初始化形式~A(){}private:inta_;intb_=3;//另一种初始化};该类需要手动声明默认构造函数?什么是默认构造函数?看百度百科的定义:默认构造函数是在没有显式提供初始化时调用的构造函数。它由不带参数或为所有形式参数提供默认参数的构造函数定义。如果在定义类的变量时没有提供初始化,将使用默认构造函数。这与上一个问题类似。首先,您需要了解何时需要默认构造函数。请参见以下代码。当为类提供了带参数的构造函数时,编译器不会为该类生成默认构造函数。如果此时在其他地方构造了类的一个对象,没有参数,编译器就会报错,找不到对应的构造函数。如何解决?一种方式是为类设置一个默认的无参构造函数(如下面的代码),另一种方式是自己提供对应的构造函数。我倾向于后一种方式,前一种方式只能解决编译问题,但可能存在潜在的bug。classA{A(inta){}A()=默认;};数据成员集是私有的、公共的还是受保护的?三种访问权限我就不过多介绍了。说说我平时是怎么设置数据成员权限的吧!对于Ordinary成员变量,我都是private的,除非类作为基类,子类也需要访问父类的private成员,这时候我就把父类的private改成protected。什么时候使用公共?一般情况下,我只会考虑对一些静态常量使用public修饰,前提是有外部需要访问这个常量。classA{public:constexprstaticintkConstValue=2;private:inta_;};该类是否需要虚拟析构函数?这很清楚。如果该类将被派生为基类,则基类的析构函数必须声明为虚函数,如果一个类确定不被派生,则不要将其析构函数声明为虚函数。类是否需要提供复制构造函数?这里需要慎重考虑,需要明确是否提供。这需要结合这个类在现实生活中的实际意义。类是对某个领域的某个业务的抽象。假设有一个试卷Class,因为试卷可以复制,那么显式提供一个复制构造函数,假设有一个Person类,因为不允许克隆,那么显式禁用复制构造函数。这里也可以参考智能指针中的unique_ptr,它明确禁止了复制操作。类是否需要提供移动构造函数?移动构造是C++11引入的新特性。它涉及左值和右值等概念。具体可以参考我的文章:《c++11新特性,所有知识点都在这了!》一个类只有拥有moveconstructor语义才能移动,如果追求资源管理的效率,移动资源的效率一般要高于复制资源的效率。这里重点关注是否需要提供移动构造函数。答案还是,想清楚,结合实际。假设我们为美国总统定义了一个类,我们可以提供一个移动的构造函数,因为美国总统过几年就要换了。假设我们已经定义了一个挂在美国最愚蠢的总统身上的类,那么移动构造函数应该被禁用,因为只有国王知道,它永远不能被移动。陷阱:赋值运算符需要考虑是否能正确阻止自己给自己赋值?classA{public:A();A(constA&rhs);A&operator=(constA&rhs){if(this==&rhs)return*this;//必需的deletem_ptr;m_ptr=newint[5];memcpy(m_ptr,rhs.m_ptr,5);返回*这个;}私有:int*m_ptr;修改是什么意思?意思是类的数据成员不能在这个函数中被修改。如果在const修饰的成员函数中修改了成员变量,编译器会编译失败。其实不标const是没有问题的,但是如果我们期望在一个函数中不会修改任何成员变量,我们就应该把成员函数标为const,防止自己或者其他程序员误操作,而当它们被错误地更改时指定了一些成员变量,编译器就会报错。如果你期望不改变一个成员函数中的成员函数,但是它没有被标记为const,那么你或者其他人改变了这个函数中的一些成员变量,而编译器并没有对此给出任何提示,这可能是Create潜在的错误。Tips:const对象上只能调用const成员函数,非const对象上既可以调用non-const成员函数,也可以调用const成员函数。什么时候需要加noexcept?如果你确认一个函数不会抛出异常,就把它标记为noexcept,这样编译器就可以进一步优化这个函数(不知道做了什么优化),提供程序运行效率。简而言之,尽量将可以标记为noexcept的标记为noexcept。函数参数传递问题?函数传参无非是传值还是传引用的选择:函数内部需要修改参数,函数外部使用修改值时:引用传参需要函数内部修改,但在函数外使用修改前的值时:传值参数在函数中不会被修改,如果参数类型是基本类型(int等):传值参数在函数中不会改变,如果参数类型是class类型:通过const引用class声明和实现是不是应该分开写在不同的文件里?一般来说,类的声明会写到头文件中,类的定义会写到源文件中,但是很多人会把定义写到头文件中。我也看到有人#include"xxx.cpp",这里建议如果不想内联函数,那么就把定义写到源文件里。如果在一个头文件中定义了一个非内联函数,当多个源文件引用这个头文件时,编译器会报错。至于类声明是写在头文件还是源文件,要看情况。看下面的代码,有的类声明写在头文件里,有的类声明写在源文件里!//a.hclassAImpl;classA{public:A();~A();voidfunc();private:AImpl*impl_;};源文件如下://a.ccclassAImpl{public:voidfunc(){std::cout<<"realfunc\n";}};A::A(){impl_=newAImpl;}A::~A(){deleteimpl_;}voidA::func(){_impl->func();}是否需要异常处理?关于异常处理的详细介绍可以参考我的文章:《你的c++团队还在禁用异常处理吗?》这里有介绍。如果是服务器端编程,建议使用异常处理,而不是错误码错误处理。关于异常处理有两个常见的问题:构造函数可以吗?析构函数可以使用异常吗?结论是构造函数在处理错误时可以使用异常,推荐使用异常。异常也可以用在析构函数中,但不要让异常从析构函数中逃逸。有异常应该在析构函数中捕获并处理掉。Tips:异常处理方式尽量方便易用,但会增加程序体积10%-20%左右。如果环境对程序大小比较敏感,我可以想到嵌入式或者移动端的编程环境,这个需要慎重考虑下。您需要将其标记为内联吗?inline的好处是可以减少函数调用的开销。inline的缺点是容易增加代码段的大小。如果一个函数体很短,比如两三行代码,会被频繁调用,可以考虑标记为inline,如果太长,不追求极致性能,则没有必要标记为inline.Tips:inline关键字只是开发者对编译器的一个请求。建议编译器做内联处理。编译器是否内联取决于它的心情。finaloverridevirtual关键字的使用如果确定一个类永远不会被其他类继承,那么显式地将该类标记为final,这样就可以防止其他类继承!如果子类要重写一个虚基类的函数,可以把这个函数标记为override,那么这个函数就必须重写父类的虚函数,否则编译器会报错。表示一个函数是一个虚函数,当有子类继承时可以重写这个函数的行为。Tips:注意不要在构造函数和析构函数中调用虚函数。考虑使用智能指针直接在类中查看代码:classA{public:A(){a_=newint;}~A(){deleta_;}private:int*a_;};可以考虑改成:classA{public:A(){a_=std::make_unique
