在面向对象中,继承这个概念其实和现实生活中的继承没有太大的区别,比如说张三是一个亿万富翁,当有一天张三不幸去世了,那么张三的儿子张小三就会继承张三的亿万财产,也就是说张三的一切都会被张小三继承。
再举个例子,斑马,斑马和普通的马区别是斑马身上是黑白相间的,那么斑马的马崽子也会是黑白相间的,水牛的角又大又弯,所以水牛的牛崽子的角也会又大又弯。
所以,继承就是继承了父辈的财产,特征等。
在计算机语言中,为了让代码有更高的复用性,产生了继承这个概念,比如说现在有一个Person类,里面有一个show函数,当我创建了一个Student的类,而我又不想再写一个show函数,于是我让Student继承Person,当继承了Person后,Student就会继承show函数,即使Student中什么都不写,也可以调用show函数
继承不但可以继承函数,也可以继承变量,对象
可以看到,虽然Student类中什么都没有写,但是由于他继承了Person类中的成员,所以Student内部实际上是有和Person同样的成员函数和变量,在概念上Person被继承,他就被称为父类或者基类,Student继承了Person对象,Student被称为子类或者派生类
怎么证明子类确实继承了父类的成员呢,我们可以通过计算大小来验证:
没有继承的空类:
在设计一个类的时候,我们通常会用到三种访问限定符:public private protected
public中的成员可以在类的外部进行访问
private中的成员只能在类里面进行访问
protected中的成员不想在类外直接被访问,但可以派生类中访问
如果基类和子类拥有同名的成员函数和变量,那么在使用的时候,到底是使用父类的还是子类的呢?
来做个试验:
可以看到,在同名的情况下,访问的是基类的成员
为什么呢?简单来说,当基类和子类发生了同名函数和同名变量的情况下,会自动隐藏父类的函数和变量,所以访问的是子类的,如果就是要访问父类的,可以指定父类成员:
上面的代码中会打印什么,我们直接来看答案:
这样看的话,还是有点冗余,我们再简化一下:
可以看到,B继承了A,那么在创建B对象的时候,会先调用A的构造函数,然后再调用B的构造函数
B的生命周期结束,先析构B,再析构A,不论是A的构造还是析构都会由B自动调用,所以千万不要在B的内部手动调用A的析构函数,否则很有肯能出现对已经释放的空间进行二次释放的危险动作。
那如果A 的构造函数带有参数呢?
那就需要在B的构造函数中手动初始化A的构造函数,否则就会报错:
class A
{
public:
A(int a):age(a){ cout << "A构造" << endl; }
int age;
~A() { cout << "A析构" << endl; }
};
class B : public A
{
public:
B(int a)
:A(a)//手动初始化
{
cout << "B构造" << endl; }
~B() { cout << "B析构" << endl;
}
};
int main()
{
//A a;
B b(10);
return 0;
}
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片
或者切割。寓意把派生类中父类那部分切来赋值过去。
基类对象不能赋值给派生类对象。
基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类
的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行安全转换
class Person
{
protected :
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public :
int _No ; // 学号
};
void Test ()
{
Student sobj ;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
sobj = pobj;
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj
Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->_No = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问
题
ps2->_No = 10;
}
一个类继承两个基类,代码复用率很高,但是这个时候也会出现一个问题,如果继承的两个基类中有相同的成员怎么办?
比如说:
可以看到,在访问age的时候直接就报错了,因为编译器无法识别到底是哪个age,解决的办法也很简单粗暴,那就是在访问的时候指明是哪个类的age就行了
那如果是菱形继承呢,会发生什么?
编译器还是无非识别
这里有两种方法,第一,指明哪个类
第二,虚继承
在继承的时候加上virtual关键字,使其成为虚继承:
class A
{
public:
char name = 'A';
};
class B: virtual public A
{
public:
};
class C :virtual public A
{
public:
};
class D : public B,public C
{
};
int main()
{
D d;
d.name = 'D';
cout << d.name << endl;
return 0;
}
虚继承是一个非常坑的概念,能不用虚继承最好不用这玩意儿!!!!!!