题外话1:浪费了两天,可耻!
题外话2:你这个年纪,做得好是理所当然,做不好是罪孽深重!!! --- 深以为然。
题外话3:从开始看C++ Primer 到现在,整整24天了,没想到基础方面耗费这么久---主要是没想到C++居然如此繁琐。精勤求学,当持之以恒。
面向对象的三大特征:数据抽象、继承、动态绑定。其实就是Java中说的封装、继承、多态,翻译的不同而已。
多态这个词用的不好,太抽象,动态绑定就清晰的多。
继承语法
class B : public A { /*类B*/ };
上面最重要的就是“:”和public。前者记住就行,继承的语法规定。后者是权限访问符(我更喜欢叫它继承权限),public、protected、private。
继承权限和父类成员的访问权限,共同决定了继承的成员在子类中的访问权限---继承权限可以进一步限制,但不能放松---最小访问控制。
例如父类的public成员a,继承权限为private,则a在子类中就是private部分。
这里只有一个需要注意的点,如果省略继承权限,那么 class 默认为private,struct默认为public。
补充:
//using声明导入 class Derived : private Base { public: // maintain access levels for members related to the size of the object using Base::size; protected: using Base::n; // ... };
继承的结构
子类对象的父类部分其实就是一个父类对象!这个认识很重要!
父类的引用或者指针,指向的就是子类对象的父类对象部分!!!
动态绑定
首先,这是一个运行期的概念,它出现的条件是:通过父类的引用或指针调用函数。
这点同Java的概念一样。但有一点不同:动态绑定仅限于 virtual 函数!
就是说,普通的函数不会动态绑定!!!这个认识更重要!
普通函数的绑定是编译器进行的,静态绑定。
这里的关键点是,父类的引用或者指针是静态类型,而子类对象(或者父类本身的对象)是在运行期才知道的。所以叫动态绑定。
就是说,对象的实际类型可能不同于父类的引用或指针类型,这是C++动态绑定的关键。
关于虚函数
不是重载,是重写。
如果想调用某个父类的虚函数,需要使用作用域操作符,如 A::x(); --仅限于子类的成员函数中调用。
注意:虚函数的默认实参如果不一致,会导致问题。
继承之初始化
隐式的,会调用父类的默认构造进行初始化。再初始化子类自身的成员。
显式的,只能在子类的初始化列表中调用父类的构造进行初始化。
--- 注意,都是先初始化父类对象,且只能直接初始化直接父类。
继承之复制控制
隐式的,会调用父类的复制控制操作父类成员。再调用子类的复制控制。
如果自定义了复制构造函数,应该显式的调用父类的复制构造函数操作父类成员。
如果自定义了赋值操作符,应该显式的调用父类的赋值操作符操作父类成员。
--- 注意,必须防止赋值操作符操作自身!
B &B::operator=(const B &rhs){ if(this != &rhs){ A::operator=(rhs); // 显式调用父类的赋值操作符 /*其他*/ } }
继承之析构函数
注意,子类析构不负责父类析构,而是由编译器显式的调用父类的析构函数!
需要将根类的析构函数设为虚函数!!
析构顺序与构造顺序相反。
纯虚函数
在虚函数的参数列表后面加上 =0 ,即将该虚函数声明为纯虚函数。
具有该函数的类将无法创建对象。
--这个就和Java的抽象方法、抽象类一致了。
重要
在构造或者析构期间,总是先构造父类或者析构子类,这时类型是不完整的。
为了解决这个问题,编译器将构造或者析构期间的对象看作父类对象!!!
所以,构造或者析构期间调用的虚函数只会是构造或者析构函数所在类的虚函数!不是子类的虚函数!!!
函数重载
子类的同名函数会覆盖掉父类中所有的同名函数--哪怕是不同的函数原型!!!(务必注意,非 virtual)
认真的说,我不认为这是重载,不过书上这么叫,那就这么叫吧。
这种覆盖用起来很不爽,因为完全覆盖了所有的父类版本。所幸我们有比较好的解决办法:使用using声明。
1 #include <iostream> 2 3 using namespace std; 4 5 // 根类的析构函数,必须virtual。 6 // 子类的函数会覆盖父类的同名函数。 7 // 如果想重载,使用using声明即可。 8 class A 9 { 10 public: 11 A(){}; 12 virtual ~A(){}; 13 void hi(){ 14 cout<<"hi from A"<<endl; 15 } 16 void hi(string name){ 17 cout<<"hi "<<name<<" from A"<<endl; 18 } 19 }; 20 21 class B:public A 22 { 23 public: 24 B(){}; 25 ~B(){}; 26 using A::hi; // using 声明 27 void hi(int num){ 28 cout<<"hi num["<<num<<"] from B, overloaded"<<endl; 29 } 30 31 }; 32 33 34 int main(int argc, char const *argv[]) 35 { 36 B b; 37 b.hi(); 38 b.hi("joe"); 39 b.hi(9527); 40 41 return 0; 42 }
继承特性:
友元关系不能继承。
静态成员只会存在一个实例。
构造函数和复制控制不能继承。
根类的析构函数应该设为虚函数。
构造函数和析构函数都只能调用所在类的函数,哪怕是虚函数---就是说构造或者析构期间没有动态绑定。