C++知识点总结(四)——面向对象的编程细节总结

1.空类的默认函数

       一般情况下,对于任意一个类A,如果程序员不显示的声明和定义上述函数,C++编译器将会自动的为A产生4个public inline(公有、内联)的默认函数,这4个函数最常见的形式为:

      (1)默认构造函数          A() {}
      (2)默认拷贝构造函数   A(const A&){}
      (3)默认析构函数         ~A() {}
      (4)默认赋值函数         A& operator = (const A &) {}


2.静态成员

            在C++中,类的静态成员(static member)必须在类内声明,在类外初始化(否则意味着全员所有)。如下:

class A
{  
private:
    static int count ; // 类内声明
};

int A::count = 0 ; // 类外初始化,不必再加static关键字

           如果限制对静态成员的访问,可以定义公有静态成员去访问。同样常量成员也不能在类中初始化。类中初始化的成员只有一种,那就是静态常量成员。静态成员可以使用“.”和“::”访问。注意:

         (1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。

      (2) 不能将静态成员函数定义为虚函数。

      (3) 由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针,函数地址类型是一个“nonmember 函数指针”。

      (4) static 并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。静态数据成员是静态存储的,所以必须对它进行初始化。初始化时不加该成员的访问权限控制符 private、public;

    (5)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。初始化以及使用时,使用作用域运算符来标明它所属类;


3.析构函数与构造函数

   析构函数 

        基类的析构函数为什么设置为虚函数:当基类指针指向派生类对象时,不会出现派生类的析构函数未被调用,从而导致内存泄露,因为由于静态联编,此时只会调用基类的对应析构函数。注意,一旦某个类的析构函数设置为虚,则其所有派生类的析构函数都是虚函数。

    构造函数
        
        (1)构造函数不能为虚函数。因为创建函数时,必须知道准确类型。
        (2)倘若派生类构造函数没有显式调用基类的构造函数,则调用默认构造函数。
        (3)一旦定义其它构造函数还需要默认构造函数的,需要自己定义
        (4)最好提供显式默认构造函数,使得数据成员初始化为合理的值(特别是包括指针成员时)
        (5)构造函数在完成其工作前,对象并不存在
        (6)构造函数声明为private能阻止编译器生成拷贝构造函数。


4.静态局部变量

           (1) 静态局部变量在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,存储在动态存储区空间(而不是静态存储区空间),函数调用结束后即释放。 
         (2) 为静态局部变量赋初值是在编译时进行值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的 值。而为自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。 
         (3) 如果在定义局部变量时不赋初值的话,对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符型变量)。而对自动变量来说,如果不赋初 值,则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不确定的。 
         (4) 虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的,也就是说,在其他函数中它是“不可见”的。


5.虚函数

         (1)虚函数要求同名同参,但是可以不同的返回类型。一旦派生类的对应同名虚函数有不同参数,则基类虚函数将被隐藏。

      (2)一旦基类函数声明为虚函数,则派生类同名同参的函数不用显式说明为虚函数。同样,在类外定义时,也不需要关键字声明。

      (3)若基类中,虚函数本身被重载多次。则在派生类中,必须对所有的重载版本进行重定义,否则未重定义的版本将会被隐藏。新定义可以直接调用基类版本从而满足某些重载版本定义不需要修改的情况(使用作用域符::)。

      (4)友元函数非类成员,故不能声明为虚。但可以通过让友元函数调用虚成员函数来解决友元的虚拟问题。

      (5)派生类将始终使用最新的虚函数版本(从派生链向下索引)。

      (6)虚函数不能滥用。存在虚表——系统开销,不想派生的类没必要使用虚构成员函数。


6.纯虚函数和抽象类

             抽象类不能生成对象,只能作为基类。但是基类中的纯虚函数可以有定义。当函数没有实现方法或者需要子类来定义实现方法的时候,可以在父类中定义纯虚函数。如代码:

class test{
public:
      virtual void print();
      virtual void order()=0;
      int array[20];
};
void test::print(){
      order();
      printf("打印结果: ");
      for(int i=0; i<20; i++)
           printf("%d ", array[i]);
}

             打印函数中,调用了order函数对array进行了排序,然后输出结果。问题是不知道order函数是什么算法,或者说order函数因人而异,所以无法确定!于是当不同的子类继承这个父类的时候,定义不同的实现方法,那么实例化这个子类的时候,这个纯虚函数就有了不同的方法。这也解释了为什么包含纯虚函数的抽象类为什么不能实例化,因为它中间有函数根本不知道是怎么实现!当然我们可以用其他方法避免使用纯虚函数,比方说在子类中重写print方法,但是这样一来等于除了order函数代码以外所有的代码都要重新复制一遍,当继承类越来越多的时候,要修改print等于这一堆继承类都要修改。简化了编程使得面向对象的方法更加灵活。


7.引用类型形参和返回值

          (1)在编写使用对象为形参的函数时,应使用引用来传递对象。能够节约生成和析构临时对象的时间继而提高效率,对于不修改参数的函数,还应声明为const类型。另外基类已用也接受派生类对象。

      (2)返回为引用同样也相同的节约时间提高效率。但是应避免返回对临时对象的引用。另外倘若形参为引用,注意要返回形参时,也应该返回引用。

      (3)基类指针和基类引用都可以(隐式,即不经过转换)地指向派生类对象,反过来则不可以(除非显式的转换,但是可能没意义)。当然,派生类对象也可以作为实参赋给以基类指针和基类引用为形参的函数。

      (4)当把基类对象赋值给派生类对象时。当且仅当派生类存在接受基类对象和其它参数的转换构造函数或者有重载了的接受基类对象的对应的赋值运算符函数。才能隐式地将基类对象赋值给派生类对象、引用、指针.


8.const关键字

           (1)const形参保证不修改实参值。对于const指针和引用表征不会修改指针指向和引用对象

     (2)const类型函数,表征函数不修改调用他的对象的值

     (3)返回const类型,表示返回不能修改值的变量。


9.基类函数和其继承特性

         (1)构造函数,析构函数,赋值运算符都不能继承。析构函数先调用派生类的析构函数再调用基类的析构函数,与构造的顺序相反。友元函数也不能继承,因为其非成员函数。

    (2)派生类的构造函数自动调用基类的默认构造函数,如果成员初始化列表中没显式指定其他构造函数(一旦指定其他的,也应该提供默认构造函数)

    (3)派生类可以使用通过作用域解析运算符显式地调用公有和保护的基类方法。

    (4)派生类的友元函数可以通过强制类型转换,使用转换后的指针或者引用调用基类的友元函数。

    (5)虚基类(ABC)包含至少一个纯虚函数,且不一定(也可以定义)需要定义虚的方法。


10.其它

    (1)对于外部世界来说,protected和private相似。对于派生类来说,protected和public相似。但是慎用protected,因为在派生类中也可以修改基类成员变量,破坏了类的封装性。

    (2)宏、内联函数、模板都在编译时解析,虚函数在运行时确定。

    (3)对于含单参构造函数的某个类A,A a=5 合法。此时隐含类型转换,可以使用explicit关键字声明消除这种隐含转换。

    (4)初始化列表(A:b(i),c(j){})的初始化顺序依据成员变量声明的顺序执行,因此赋值的时候要分先后,避免随机值出现(例如c可能使用c(b+i)初始化,而b因为定义的顺序在c之后而在c之后初始化,此时c产生随机值)。

    (5)强制转换类型重载定义。operator char*()。char*是目标类型。

   (6)虚函数机制,破环类的封装。假设基类为public,派生类为private的虚函数多态。通过虚表,使用基类指针仍然能够访问派生的对应重载虚函数,尽管其为private。
      虚函数必须同名同参同返回(注意函数重载可以不同返回)
    (7)若当前类含有对象时,则构造顺序:基类>本层对象>本层构造函数


原文地址:https://www.cnblogs.com/engineerLF/p/5393040.html