C++继承相关知识点总结
1:派生类继承基类的成员并且可以定义自己的附加成员。每个派生类对象包含两个部分:从基类继承的成员和自己定义的成员。
每个派生类对象都有基类部分,包括基类的private成员。类可以访问共基类的public 和 protected 成员,就好像那些成员是派生类自己的成员一样。派生类不能访问基类的 private 成员。也就是说,虽然派生类继承了基类的私有成员,但是不可访问。比如下面的例子:
class father { public: int publ_i; father(int a, int b, int c):publ_i(a),priv_i(b), prot_i(c) {} void display_f() { cout << "[father]publ_i is " << publ_i << endl; cout << "[father]priv_i is " << priv_i << endl; cout << "[father]prot_i is " << prot_i << endl; } private: int priv_i; protected: int prot_i; }; class son: public father { public: son(int a, int b, int c):father(a, b, c) {}; void display_s() { cout << "[son]publ_i is " << publ_i << endl; cout << "[son]priv_i is " << priv_i << endl; cout << "[son]prot_i is " << prot_i << endl; } };
上面的代码编译时会出错:
test.cpp: In member function ‘void son::display_s()’: test.cpp:17:6: error: ‘int father::priv_i’ is private int priv_i; ^ test.cpp:29:32: error: within this context cout << "[son]priv_i is " << priv_i << endl; ^
2:类的成员函数中,可以访问其他同类对象中的私有成员。比如:
class father { public: int publ_i; father(int a, int b, int c):publ_i(a),priv_i(b), prot_i(c) {} void display_f(const father& ff) const { cout << "[father]my publ_i is " << this->publ_i << endl; cout << "[father]my priv_i is " << this->priv_i << endl; cout << "[father]my prot_i is " << this->prot_i << endl; cout << "[father]ff.publ_i is " << ff.publ_i << endl; cout << "[father]ff.priv_i is " << ff.priv_i << endl; cout << "[father]ff.prot_i is " << ff.prot_i << endl; } private: int priv_i; protected: int prot_i; }; int main() { father ff1(1,2,3); father ff2(4,5,6); ff1.display_f(ff2); }
在father::display_f成员函数中,可以访问对象ff中的所有成员,包括私有成员。这段代码的运行结果是:
[father]my publ_i is 1 [father]my priv_i is 2 [father]my prot_i is 3 [father]ff.publ_i is 4 [father]ff.priv_i is 5 [father]ff.prot_i is 6
3:派生类只能通过派生类对象访问其基类的 protected 成员,派生类对其基类类型对象的 protected 成员没有特殊访问权限。比如:
class father { public: int publ_i; father(int a, int b, int c):publ_i(a),priv_i(b), prot_i(c) {} void display_f() { cout << "[father]publ_i is " << publ_i << endl; cout << "[father]priv_i is " << priv_i << endl; cout << "[father]prot_i is " << prot_i << endl; } private: int priv_i; protected: int prot_i; }; class son: public father { public: son(int a, int b, int c):father(a, b, c) {}; void display_sf(son& ss, father& ff) { cout << "[son]ss.publ_i is " << ss.publ_i << endl; cout << "[son]ss.prot_i is " << ss.prot_i << endl; cout << "[son]ff.publ_i is " << ff.publ_i << endl; cout << "[son]ff.prot_i is " << ff.prot_i << endl; } };
上面的代码,在编译时会出错:
test2.cpp: In member function ‘void son::display_sf(son&, father&)’: test2.cpp:20:6: error: ‘int father::prot_i’ is protected int prot_i; ^ test2.cpp:34:38: error: within this context cout << "[son]ff.prot_i is " << ff.prot_i << endl; ^
4:C++ 中的函数调用默认不使用动态绑定。要触发动态绑定,满足两个条件:第一,只有指定为虚函数的成员函数才能进行动态绑定。成员函数默认为非虚函数,对非虚函数的调用在编译时确定;第二,必须通过基类类型的引用或指针进行函数调用。引用和指针的静态类型与动态类型可以不同,这是 C++ 用以支持多态性的基石。
如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本。
为了指明函数为虚函数,在其返回类型前面加上保留字 virtual。除了构造函数之外,任意非 static 成员函数都可以是虚函数。保留字只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。
基类通常应将派生类需要重定义的任意函数定义为虚函数。如果派生类没有重定义某个虚函数,则使用基类中定义的版本。
派生类中虚函数的声明必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。
一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用 virtual 保留字,但不是必须这样做。
5:即使在派生类中重新定义了虚函数,实际上基类中的虚函数也被派生类继承了,只不过它被派生类的虚函数覆盖了而已。如果希望覆盖虚函数机制并强制使用虚函数的特定版本,这里可以使用作用域操作符:
class father { public: int publ_i; father(int a, int b, int c):publ_i(a),priv_i(b), prot_i(c) {} virtual void display() { cout << "[father]publ_i is " << publ_i << endl; cout << "[father]priv_i is " << priv_i << endl; cout << "[father]prot_i is " << prot_i << endl; } private: int priv_i; protected: int prot_i; }; class son: public father { public: son(int a, int b, int c):father(a, b, c) {}; void display() { cout << "[son]publ_i is " << publ_i << endl; cout << "[son]prot_i is " << prot_i << endl; } }; int main() { son ss(4, 5, 6); father *fp = &ss; fp->display(); fp->father::display(); }
上面的” fp->father::display();”语句,将强制调用father中定义的版本,该调用将在编译时确定。上述代码的结果如下:
[son]publ_i is 4 [son]prot_i is 6 [father]publ_i is 4 [father]priv_i is 5 [father]prot_i is 6
或者,直接在派生类中调用基类中的函数:
class son: public father { public: son(int a, int b, int c):father(a, b, c) {}; void display() { father::display(); } }; int main() { son ss(4, 5, 6); father *fp = &ss; fp->display(); }
这段代码结果为:
[father]publ_i is 4 [father]priv_i is 5 [father]prot_i is 6
6:,虚函数也可以有默认实参。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。因此,在同一虚函数的基类版本和派生类版本中使用不同的默认实参几乎一定会引起麻烦。
class father { public: int publ_i; virtual void display(int a = 1, int b = 2, int c = 3) { cout << "[father]a is " << a << endl; cout << "[father]b is " << b << endl; cout << "[father]c is " << c << endl; } private: int priv_i; protected: int prot_i; }; class son: public father { public: virtual void display(int a = 4, int b = 5, int c = 6) { cout << "[son]a is " << a << endl; cout << "[son]b is " << b << endl; cout << "[son]c is " << c << endl; } }; int main() { son ss; father *fp = &ss; son *sp = &ss; fp->display(); sp->display(); }
fp->display(),使用指向派生类对象的基类指针调用display,此时尽管发生动态绑定,调用的是派生类的display函数,但是默认实参却是基类中的版本;sp->display(),使用指向派生类对象的派生类指针调用display,默认实参是派生类的版本。结果如下:
[son]a is 1 [son]b is 2 [son]c is 3 [son]a is 4 [son]b is 5 [son]c is 6
7:如果成员在基类中为 private,则只有基类和基类的友元可以访问该成员。派生类不能访问基类的 private 成员,也不能使自己的用户能够访问那些成员。
如果基类成员为 public 或 protected,则派生列表中使用的访问标号决定该成员在派生类中的访问级别:
• 如果是公用继承,基类成员保持自己的访问级别:基类的 public 成员为派生类的 public 成员,基类的 protected 成员为派生类的 protected成员。
• 如果是受保护继承,基类的 public 和 protected 成员在派生类中为protected成员。
• 如果是私有继承,基类的的所有成员在派生类中为 private 成员。
比如下面的代码:
class father { public: int publ_i; father(int a, int b, int c):publ_i(a),priv_i(b), prot_i(c) {} virtual void display() { cout << "[father]publ_i is " << publ_i << endl; cout << "[father]priv_i is " << priv_i << endl; cout << "[father]prot_i is " << prot_i << endl; } void fun() { cout << "[father]fun" << endl; } private: int priv_i; protected: int prot_i; }; class public_son: public father { public: public_son(int a, int b, int c):father(a, b, c) {}; void display() { cout << "[public_son]publ_i is " << publ_i << endl; cout << "[public_son]prot_i is " << prot_i << endl; } }; class grandson1:public public_son { public: grandson1(int a, int b, int c):public_son(a, b, c) {}; void display() { cout << "[grandson1]publ_i is " << publ_i << endl; cout << "[grandson1]prot_i is " << prot_i << endl; } }; int main() { public_son ss(1, 2, 3); ss.display(); ss.fun(); }
在public_son中,father中的public和protected成员在派生类中保持他们的属性,因此,在grandson1::display中可以访问publ_i和prot_i,在用户代码中,可以调用public_son::display和public_son::fun函数;上面的代码结果如下:
[public_son]publ_i is 1 [public_son]prot_i is 3 [father]fun
再看保护继承:
class protected_son: protected father { public: protected_son(int a, int b, int c):father(a, b, c) {}; void display() { cout << "[protected_son]publ_i is " << publ_i << endl; cout << "[protected_son]prot_i is " << prot_i << endl; } }; class grandson2:public protected_son { public: grandson2(int a, int b, int c):protected_son(a, b, c) {}; void display() { cout << "[grandson2]publ_i is " << publ_i << endl; cout << "[grandson2]prot_i is " << prot_i << endl; } };
在protected_son中,father中的public和protected成员在派生类中成为了protected成员,因此,在grandson2::display中可以访问publ_i和prot_i。但是在用户代码中,不可以调用protected_son::fun函数:
protected_son s1(1,2,3); s1.fun();
编译出错:
inherit.cpp: In function ‘int main()’: inherit.cpp:20:7: error: ‘void father::fun()’ is inaccessible void fun() ^ inherit.cpp:113:9: error: within this context s1.fun(); ^ inherit.cpp:113:9: error: ‘father’ is not an accessible base of ‘protected_son’
在用户代码中,也不可以调用grandson2::fun函数:
grandson2 s2(4, 5, 6); s2.fun();
编译同样出错:
inherit.cpp: In function ‘int main()’: inherit.cpp:20:7: error: ‘void father::fun()’ is inaccessible void fun() ^ inherit.cpp:116:9: error: within this context s2.fun(); ^ inherit.cpp:116:9: error: ‘father’ is not an accessible base of ‘grandson2’
最后看一下私有继承:
class private_son: private father { public: private_son(int a, int b, int c):father(a, b, c) {}; void display() { cout << "[private_son]publ_i is " << publ_i << endl; cout << "[private_son]prot_i is " << prot_i << endl; } }; class grandson3:public private_son { public: grandson3(int a, int b, int c):private_son(a, b, c) {}; void display() { cout << "[grandson1]publ_i is " << publ_i << endl; cout << "[grandson1]prot_i is " << prot_i << endl; } }; int main() { private_son s1(1,2,3); s1.display(); s1.fun(); }
在private_son中,father中的public和protected成员在派生类中成为了private成员,因此,在grandson3::display中不可以访问publ_i和prot_i;在用户代码中,也不可以调用private_son::fun函数:
inherit.cpp: In member function ‘virtual void grandson3::display()’: inherit.cpp:11:6: error: ‘int father::publ_i’ is inaccessible int publ_i; ^ inherit.cpp:103:38: error: within this context cout << "[grandson1]publ_i is " << publ_i << endl; ^ inherit.cpp:28:6: error: ‘int father::prot_i’ is protected int prot_i; ^ inherit.cpp:104:38: error: within this context cout << "[grandson1]prot_i is " << prot_i << endl; ^ inherit.cpp: In function ‘int main()’: inherit.cpp:20:7: error: ‘void father::fun()’ is inaccessible void fun() ^ inherit.cpp:115:9: error: within this context s1.fun(); ^ inherit.cpp:115:9: error: ‘father’ is not an accessible base of ‘private_son’
总而言之,派生类派生列表中的访问标号并不影响直接派生类的成员函数,它影响的是直接派生类的用户代码,以及间接派生类的成员函数的访问权限。
8:派生类可以使用using声明,修改继承成员的访问级别。(C++ primer第四版 15.2.5中说,派生类不能使访问级别比基类中原来指定的更严格或更宽松,这是错的,实验证明,对于基类中的public成员或protected成员,派生类可以使用using声明修改它们的访问权限):
class private_son: private father { public: private_son(int a, int b, int c):father(a, b, c) {}; using father::prot_i; void display() { cout << "[private_son]publ_i is " << publ_i << endl; cout << "[private_son]prot_i is " << prot_i << endl; } }; int main() { private_son ss1(4, 5, 6); ss1.prot_i = 10; ss1.display(); }
在private_son类中,按照私有继承的规则,father::publ_i和father::prot_i在private_son中成为了私有成员。这里使用using声明,修改了father::prot_i的访问属性为public,因此,可以直接在用户代码中设置ss1.prot_i的值。上面代码中结果如下:
[private_son]publ_i is 4 [private_son]prot_i is 10
注意,基类中的private成员,不能在派生类中任何地方用using声明。
9:使用 class 保留字定义的派生默认具有private 继承,而用 struct 保留字定义的类默认具有 public 继承:
class Base { /* ... */ }; struct D1 : Base { /* ... */ }; // public inheritance by default class D2 : Base { /* ... */ }; // private inheritance by default
10:友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。
11:如果基类定义 static 成员,则整个继承层次中只有一个这样的成员。无论从基类派生出多少个派生类,每个 static 成员只有一个实例。
12:如果有一个派生类型的对象,则可以使用它的地址对基类类型的指针进行赋值或初始化。同样,可以使用派生类型的引用或对象初始化基类类型的引用。
一般可以使用派生类型对象对基类对象进行赋值或初始化。对基类对象进行初始化或赋值,实际上是在调用函数:初始化时调用构造函数,赋值时调用赋值操作符。基类一般(显式或隐式地)定义自己的复制构造函数和赋值操作符,这些成员接受一个形参,该形参是基类类型的(const)引用。因为存在从派生类引用到基类引用的转换,所以这些复制控制成员可用于从派生类对象对基类对象进行初始化或赋值。这种情况下,使用派生类对象的基类部分,对基类对象进行初始化或赋值,而派生类本身的部分则被切掉了。
从基类到派生类的自动转换是不存在的,需要派生类对象时不能使用基类对象,派生类对象的指针和引用也不能使用基类对象的指针或引用进行赋值。
13:派生类的作用域嵌套在基类作用域中。如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义。
名字查找是在编译时发生的,因此,即使使用基类类型的引用或指针指向派生类对象时,也不能通过该引用或指针,访问派生类中存在而基类中不存在的成员:
class father { public: int publ_i; father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) { } private: int priv_i; protected: int prot_i; }; class son: public father { public: void son_func() { cout << "this is son func" << endl; } }; int main() { father *fp = new son; fp->son_func(); }
上面的代码中,即使指针fp指向了一个派生类对象。但是该指针还是一个基类指针,不能调用派生类中特有的函数。上述代码发送编译错误:
test.cpp: In function ‘int main()’: test.cpp:30:6: error: ‘class father’ has no member named ‘son_func’ fp->son_func(); ^
14:如果派生类中的某个成员名字与基类中成员名字相同,则尽管派生类继承了基类的该成员,但是它自身的成员屏蔽掉了基类中的成员。
特别是对于成员函数来说,即使函数原型不同,基类成员也会被屏蔽:
class father { public: int publ_i; father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) { } void fun() { cout << "this is father fun" << endl; } private: int priv_i; protected: int prot_i; }; class son: public father { public: void fun(int a) { cout << "this is son fun" << endl; } }; int main() { son ss; ss.fun(3); ss.fun(); }
调用”ss.fun(3)”时,调用的是son类中的版本;而调用ss.fun()时,将会发生编译错误,因为编译器查找名字fun时,在son中找到该名字之后就不会再继续在基类中查找了。这个调用与son中的fun定义不匹配,因此编译出错:
test.cpp: In function ‘int main()’: test.cpp:35:9: error: no matching function for call to ‘son::fun()’ ss.fun(); ^ test.cpp:25:7: note: candidate: void son::fun(int) void fun(int a) ^ test.cpp:25:7: note: candidate expects 1 argument, 0 provided
要想明确调用基类中的fun函数,可以采用域操作符:
ss.father::fun();
从而得到正确的结果:
this is son fun this is father fun
因此,如果基类中的成员函数有多个重载版本,则只要派生类中重定义了一个同名函数,则派生类对象只能访问该重定义版本,因为它屏蔽了所有基类中的同名函数。
如果派生类需要仅仅重定义一个重载集中某些版本的行为,并且想要继承其他版本的含义,派生类可以为重载成员使用之前提过的 using 声明。一个 using 声明只指定一个名字,不指定形参表,因此,为基类成员函数名称而作的 using 声明将该函数的所有重载实例加到派生类的作用域,之后,派生类只需要重定义必须定义的那些函数,对其他版本可以使用继承的定义:
class father { public: int publ_i; father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) { } void fun() { cout << "this is father fun" << endl; } void fun(int a) { cout << "this is father fun(int a)" << endl; } private: int priv_i; protected: int prot_i; }; class son: public father { public: using father:fun; void fun(int a) { cout << "this is son fun(int a)" << endl; } }; int main() { son ss; ss.fun(3); ss.fun(); }
结果如下:
this is son fun(int a) this is father fun
15:虚函数在基类和派生类中必须拥有同一原型。如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数。考虑如下(人为的)类集合:
class Base { public: virtual int fcn(); }; class D1 : public Base { public: // hides fcn in the base; this fcn is not virtual int fcn(int); // parameter list differs from fcn in Base // D1 inherits definition of Base::fcn() }; class D2 : public D1 { public: int fcn(int); // nonvirtual function hides D1::fcn(int) int fcn(); // redefines virtual fcn from Base };
D1 中的 fcn 版本没有重定义 Base 的虚函数 fcn,相反,它屏蔽了基类的fcn。结果 D1 有两个名为 fcn 的函数:从 Base 继承了一个名为 fcn 的虚函数,又定义了自己的名为 fcn 的非虚成员函数,该函数接受一个 int 形参。但是,从 Base 继承的虚函数不能通过 D1 对象(或 D1 的引用或指针)调用,因为该函数被 fcn(int) 的定义屏蔽了。
类 D2 重定义了它继承的两个函数,它重定义了 Base 中定义的 fcn 的原始版本并重定义了 D1 中定义的非虚版本。
考虑下面的代码:
Base bobj; D1 d1obj; D2 d2obj; Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj; bp1->fcn(); // ok: virtual call, will call Base::fcnat run time bp2->fcn(); // ok: virtual call, will call Base::fcnat run time bp3->fcn(); // ok: virtual call, will call D2::fcnat run time
16:在成员函数形参表后面写上 = 0 ,可以指定该成员函数为纯虚函数。含有(或继承)一个或多个纯虚函数的类是抽象基类。将函数定义为纯虚能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类中的版本决不会调用。重要的是,用户将不能创建抽象基类的对象。试图创建抽象基类的对象将发生编译时错误:
class father { public: int publ_i; father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) { } virtual void fun() = 0; private: int priv_i; protected: int prot_i; }; class son: public father { void fun(int a) {} };
father类包含纯虚函数fun(),派生类son继承了该纯虚函数,而且重定义了一个原型不同的同名函数fun(int a),因此,son也是个抽象基类。所以,语句”son ss”将会是编译错误:
test.cpp: In function ‘int main()’: test.cpp:27:6: error: cannot declare variable ‘ss’ to be of abstract type ‘son’ son ss; ^ test.cpp:19:7: note: because the following virtual functions are pure within ‘son’: class son: public father ^ test.cpp:12:15: note: virtual void father::fun() virtual void fun() = 0; ^