关于C++继承

  继承本质上就是对代码的复用,并且还可以在原来的类上进行拓展,增加功能,这充分体现了面向对象编程的思想。

  关于继承这方面的语法细节非常多,相比普通类,继承多了一个访问权限,protected权限(保护权限),如果要写一个类可被继承,类里面的访问权限可以将private替换成protected。日常使用中都使用public继承权限,如果不写,那么class默认是private继承权限,struct是public。

  注意:继承的子类在实例化时,会先按顺序调用父类的构造函数,最后再调用自己的构造函数。

class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};

class Student : public Person
{
protected:
int _stuid; // 学号
};

  继承中,子类会继承基类的所有成员,包括私有成员,但是子类是不能直接访问基类的私有成员的,需要用到公有的函数来进行访问,这也体现了封装性。有个特殊的就是静态成员,它也可以被继承,但它不被任何对象所包含,它在基类和基类中仅此一份。另外友元关系不能被继承。

  可以想象为,一个学生类继承了人类以后,当实例化一个学生对象的时候,这里面就包含了人的成员和学生类特有的成员,可以理解为,学生这个对象里面又包含了一个人这个对象,那么在实例化一个学生对象的时候会先执行人的构造函数,先把人构造出来,再来执行学生的构造函数。另外要注意的是,如果基类没有默认的构造函数(无参数或者全缺省),那么要在学生类的初始化列表里显示的调用基类的构造函数,不然会报错。

  构造的顺序是先父再子,而析构的时候就反过来了,是先子再父。我的理解是把它想象成一个栈,在构造的时候是父先,那么它就在栈底,那自然是它最后再析构咯。

  还有就是子类的对象可以赋值给基类的对象/基类的指针/基类的引用。这种用法叫切割或者切片,就是把子类中父类的成员给赋值过去给基类。要注意的是,只能是子类的给父类,反过来不行,假设有一个老师,他可以把他的所有给一个人,但是人可以不要他的工号,但是反过来,一个人把他的全部给老师,但是人又没有工号,也就是给不了,那一个老师没有工号还能是正经老师吗?(又不是日本的--doge~),所以这个地方要注意了。

  另外在基类和子类中有一个概念叫重写,它和重定义有点像,但它只针对函数,在需要重写的函数前加上virtual关键字,并且符合三同(函数名/返回值/参数,都相同),建议是在基类和和子类重写的函数前都加上,但只在基类加也可以。它其实最主要的还是为解决析构函数存在的一个问题所设计出来的。这个地方是多态的知识了。

  在C++的继承中会有一个比较坑的语法就是重定义,是指成员名相同,无论变量还是函数,这里面有一个隐藏机制,就是子类会屏蔽对基类的同名成员的直接访问。它的调用原则是就近原则,如果是子类对象在使用的话,它会先在子类中找,如果没找到,就去基类中找,还没找到就报错,当然也可以显示的指明要调用谁的。容易出错的是这里有些情况可能会和重载进行混淆,重载的前提是要在同一个作用域,而基类和子类是两个不同的作用域。下面是个特殊场景

class A

{

public:

  void f(){ cout<<"A::f()"<<endl; }

  int a;   

};



class B : public A

{

public:

  void f(int a){cout<<"B::f()"<<endl;}

  int a;

};



int main()

{

  B b;

  b.f();

  return 0;

}

这个代码中,会出现编译不通过的情况,原因就是它将父类的同名函数隐藏了起来,先找了子类的函数,然后发现参数不匹配,所以报错了。

  最后就是关于C++的继承的一个超级答辩的地方就是菱形继承,它会导致数据的冗余和二义性,它是由于多继承引起的。这个答辩甚至让喜欢抄作业的java同学都对此望尘莫及,为此直接砍掉了多继承。解决菱形继承的方法就是用虚拟继承,在继承关键字前加上virtual关键字(在菱形继承的位置中就是在爸爸位置加上),但它和虚函数没有半毛钱关系,它的作用是当一个类(后面就叫它孙子)多继承后形成了菱形继承后,它的多个爸爸们会把爷爷里的成员放在孙子体内的最低端(只有一份),然后在爸爸里面本应该存在爷爷的成员通通化身成一个指针,它指向了一个叫做虚基表的东西,那里面放着与各个爷爷的成员的地址的偏移量,每次通过爸爸调用的时候就通过这个偏移量来找到低端爷爷的成员。这样就解决的二义性和数据冗余的问题,但它也会带来资源的额外消耗,所以正常开发应该避免使用菱形继承,以免出现许多幺蛾子。

  以上就是继承了,还有一个拓展的地方就是组合,如果说继承是is-a的关系,那么组合就是has-a的关系,比如人和教师就适合用继承,汽车和轮胎就适合组合。例如以下组合:

class Tire{
protected:
string _brand = "Michelin"; // 品牌
size_t _size = 17; // 尺寸
};

class Car{
protected:
string _colour = "白色"; // 颜色
string _num = "陕ABIT00"; // 车牌号
Tire _t; // 轮胎
};

  很多情况下组合比继承要更具有优势,因为组合的耦合度比继承的低,那么当出了问题或者要改动的时候,组合的维护成本要小很多,可以理解为组合是黑箱操作,它只管用,不用管其原理。而继承就是白箱操作,在用它的同时,还要清楚它的内部原理。当然,也不是任何时候无脑的选择组合,选组合还是继承还是要具体分析场景。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值