C++基础知识:继承与派生详解
扫描二维码
随时随地手机看文章
通过特殊化已有的类来建立新类的过程,叫做“类的派生”, 原有的类叫做”基类”,新建立的类叫做“派生类”。
类的继承是指派生类继承基类的数据成员和成员函数。继承用来表示类属关系,不能将继承理解为构成关系。
增加新的成员(数据成员和成员函数)
重新定义已有的成员函数
改变基类成员的访问权限
代码格式:
class 派生类名: 访问控制 基类名 {private: 成员声明列表protected: 成员声明列表public: 成员声明列表}
“冒号”表示新类是哪个基类的派生类;“访问控制”指继承方式。
三个方式:public、protected、private
// 基类class Point {int x;int y;public:Point(int a, int b) {x = a;y = b;cout << "init Point" << endl;}void showPoint() {cout << "x = " << x << ", y = " << y << endl;}~Point() {cout << "delete Point" << endl;}};// 派生类class Rect: public Point {int w;int h;public:// 调用基类的构造函数对基类成员进行初始化Rect(int a, int b, int c, int d):Point(a, b) {w = c;h = d;cout << "init Rect" << endl;}void showRect() {cout << "w = " << w << ", h = " << h << endl;}~Rect() {cout << "delete Rect" << endl;}};int main() {Rect r(3, 4, 5, 6);r.showPoint();r.showRect();/** 输出结果init Point // 当定义一个派生类的对象时, 首先调用基类的构造函数, 完成对基类成员的初始化init Rect // 然后执行派生类的构造函数, 完成对派生类成员的初始化x = 3, y = 4 // 调用基类成员函数showPoint();w = 5, h = 6 // 调用派生类成员函数showRect();delete Rect // 构造函数的执行顺序和构造函数的执行顺序相反, 首先调用派生类的析构函数delete Point // 其次调用基类的析构函数*/}
如果希望Rect中的showRect()函数可以一次显示x、y、w、h。我们直接修改showRect()函数是不行的。
void showRect() {cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;}
报错 error: 'x' is a private member of‘Point' 'y' is a private member of‘Point'
x, y为Point类的私有成员,公有派生时,在Rect类中是不可访问的。
我们还需要将基类Point中的两个成员声明为protected的属性。
像这样:
// 基类class Point {// 公有数据成员protected:int x;int y;public:Point(int a, int b) {x = a;y = b;cout << "init Point" << endl;}void showPoint() {cout << "x = " << x << ", y = " << y << endl;}};// 派生类class Rect: public Point {int w;int h;public:// 调用基类的构造函数对基类成员进行初始化Rect(int a, int b, int c, int d):Point(a, b) {w = c;h = d;cout << "init Rect" << endl;}/** 公有派生, Point类中的受保护数据成员, 在Rect类中也是受保护的, 所以可以访问 // 而通过公有继承的基类私有的成员, 在派生类中是不可被访问的 void showRect() {cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;}*/};int main() {Rect r(3, 4, 5, 6);r.showPoint();r.showRect();}
在根类中,对于成员的访问级别有三种:public、protected、private
在派生类中,对于成员的访问级别有四种:public(公有)、protected(受保护)、private(私有)、inaccessible(不可访问)
(1)公有派生和赋值兼容规则
公有派生:
基类成员的访问权限在派生类中基本保持不变。
基类的公有成员在派生类中仍然是公有的
基类的保护成员在派生类中仍然是受保护的
基类的不可访问的成员在派生类中仍然是不可访问的
基类的私有成员在派生类中变成了不可访问的
总结:在公有派生的情况下,通过派生类自己的成员函数可以访问继承过来的公有和保护成员, 但是不能访问继承来的私有成员, 因为继承过程的私有成员,变成了第四个级别,不可访问的。
赋值兼容规则:
在公有派生的情况下, 一个派生类的对象可以作为基类的对象来使用的情况。
像这样:
// 基类class Point {// 这里声明成员属性为受保护的protected:int x;int y;public:Point(int a, int b) {x = a;y = b;}void show() {cout << "x = " << x << ", y = " << y << endl;}};// 派生类class Rect: public Point {int w;int h;public:// 调用基类的构造函数对基类成员进行初始化Rect(int a, int b, int c, int d):Point(a, b) {w = c;h = d;}void show() {cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;}};int main() {Point a(1, 2);Rect b(3, 4, 5, 6);a.show();b.show();Point & pa = b; // 派生类对象初始化基类的引用pa.show(); // 实际调用基类的show()函数Point * p = &b; // 派生类对象的地址赋值给指向基类的指针p -> show(); // 实际也是调用基类的show()函数Rect * pb = &b; // 派生类指针pb -> show(); // 调用派生类的show()函数a = b; // 派生类对象的属性值, 更新基类对象的属性值a.show(); // 调用基类的show()函数/**x = 1, y = 2x = 3, y = 4, w = 5, h = 6x = 3, y = 4x = 3, y = 4x = 3, y = 4, w = 5, h = 6x = 3, y = 4*/}
(2)“isa”和”has-a“的区别
继承和派生 isa
比如一个Person类,派生出一个Student类,我们可以说Student就是Person,也就是 Student isa Person,而反过来则不行。
一个类用另一个类的对象作为自己的数据成员或者成员函数的参数 has-a。
像这样:
// 地址类class Address {};class PhoneNumber {};// 职工类class Worker {String name;Address address;PhoneNumber voiceNumber;};
表示一个Worker对象有一个名字,一个地址,一个电话号码,has-a的关系,包含的关系。
(3)私有派生
通过私有派生,基类的私有和不可访问成员在派生类中是不可访问的,而公有和保护成员这里就成了派生类的私有成员。
// 基类class Point {int x;int y;public:Point(int a, int b) {x = a;y = b;}void show() {cout << "x = " << x << ", y = " << y << endl;}};// 派生类class Rect: private Point {int w;int h;public:Rect(int a, int b, int c, int d):Point(a, b) {w = c;h = d;}void show() {Point::show(); // 通过私有继承, Point类中的公有成员show(), 在Rect中为私有cout << "w = " << w << ", h = " << h << endl;}};class Test: public Rect {public:Test(int a, int b, int c, int d):Rect(a, b, c, d) {}void show() {Rect::show();//Point::show();/** error: 'Point' is a private member of ‘Point’标明: 不可访问基类Point中的成员Rect类私有继承自Point类, 所以Point中的私有成员x, 私有成员y, 在Rect类中为不可访问: Point类中公有成员show(), 在Rect中变私有Test类公有继承自Rect类, 所以在Rect中成员x, 成员y, 仍然是不可访问, Rect::show()还是public, 但是Point::show()不可访问 */}};
因为私有派生不利于进一步派生, 因而实际中私有派生用得并不多。
(4)保护派生保护派生使原来的权限都降一级使用
即private变为不可访问,protected变为private,public变为protected。
限制了数据成员和成员函数的访问权限,因此在实际中保护派生用得也不多。
比如:我们在上个例子中,Rect类保护派生于Point,则在Test类中Point::show();就可以使用啦!
代码格式:
class 派生类名: 访问控制 基类名1, 访问控制 基类名2, … {//定义派生类自己的成员}
像这样:
// 基类A, 也叫根类class A {int a;public:void setA(int x) {a = x;}void showA() {cout << "a = " << a << endl;}};// 基类B, 也叫根类class B {int b;public:void setB(int x) {b = x;}void showB() {cout << "b = " << b << endl;}};// 多重继承, 公有继承自类A, 私有继承自类Bclass C: public A, private B {int c;public:void setC(int x, int y) {c = x;setB(y);}void showC() {showB();cout << "c = " << c << endl;}};int main() {C c;c.setA(53); // 调用基类setA()函数c.showA(); // 调用基类showA()函数c.setC(55, 58); // 调用派生类C的setC()函数c.showC(); // 调用派生类C的showC()函数// 派生类C私有继承自基类B, 所以基类B中私有成员b, 在派生类C中不可访问, 基类B中公有成员setB(), showB()在派生类C中变私有. 在main()函数中不可访问// c.setB(60); // error: 'setB' is a private member of 'B'// c.showB(); // 'showB' is a private member of 'B'/**a = 53b = 58c = 55*/}
对基类成员的访问必须是无二义性的,如果一个表达式的含义可以解释为可以访问多个基类中的成员,则这种对基类成员的访问就是不确定的,称这种访问具有二义性。
代码格式:
类名::标识符
:: 为作用域分辨符,"类名"可以是任一基类或派生类名,“标识符”是该类中声明的任一成员名,
像这样:
// 基类A, 也叫根类class A {public:void func() {cout << "A func" << endl;}};// 基类B, 也叫根类class B {public:void func() {cout << "B func" << endl;}void gunc() {cout << "B gunc" << endl;}};// 多重继承class C: public A, public B {public:void gunc() {cout << "C gunc" << endl;}void hunc() {/**这里就具有二义性, 它即可以访问A类中的func(), 也可以访问类B中的func()*///func(); // error: Member 'func' found in multiple base classes of different types}void hunc1() {A::func();}void hunc2() {B::func();}};int main() {C c;//c.func(); //具有二义性c.A::func();c.B::func();c.B::gunc();c.C::gunc();c.gunc();c.hunc1();c.hunc2();/** 输出结果A funcB funcB guncC guncC gunc // 如果基类中的名字在派生类中再次声明, 则基类中的名字就被隐藏. 如果我们想要访问被隐藏的基类中的成员则使用作用域分辨符B::gunc();A funcB func*/}
如果派生类定义了一个同基类成员函数同名的新成员函数(具有相同参数表的成员函数),派生类的新成员函数就覆盖了基类的同名成员函数。
在这里,直接使用成员名只能访问派生类中的成员函数,使用作用域运算符,才能访问基类的同名成员函数。
派生类中的成员函数名支配基类中的同名的成员函数名,这称为名字支配规则。
如果一个名字支配另一个名字,则二者之间不存在二义性,当选择该名字时,使用支配者的名字。
例如上个例子中
c.gunc() // 输出”C gunc”, 基类B中的gunc成员函数被支配了c.B::gunc(); // 加上作用域分辨符, 来使用被支配的成员
-END-
来源 :老九学堂
推荐阅读
免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!






