先谈谈继承的概念
一.继承的关系及继承访问限定符
下面是三种继承关系下基类成员在派生类中的访问变化
那么private和protected都是限定直接访问,那么他们有什么区别?
先看下面这个单继承示例代码在作解释:
#include<iostream>
using namespace std;
class person //父类/基类
{
public:
person()
{
cout << "person" << endl;
}
protected:
int id;
private:
int _age;
};
//class student:protected person
//class student:private person
class student :public person //class 子类/派生类: 继承关系 父类/基类
{
public:
student()
{
cout<< person::id << endl;
cout << _num << endl;
}
public:
int _num;
};
int main()
{
person p;
student s;
return 0;
}
下图发现类可以直接调用父类的id和person(),而不能调用父类中私有的_age成员
要想访问类中的私有成员,只能通过类中的其它访问权限的方法调用成员,然后在类外使用。
二.赋值兼容规则
看下面示例代码:
#include<iostream>
using namespace std;
class person
{
public:
void Display()
{
cout << "person()" << endl;
}
protected:
string _name;
private:
int _age;
};
class student :public person
{
public:
int _num;
};
int main()
{
person b;
student a;
//子类对象可以赋值给父类对象(切片/切割)
b = a; //yes
//父类对象不可以赋值给子类对象
//a = b; //no
//父类指针/引用可以指向子类对象
person* p1 = &a;
person& p1 = a;
//子类指针/引用不能指向父类对象(可以通过强制类型转换来指向)
//student* s1 = &b; //no
student* s1 = (student*)&b; //yes
//student& s2 = b; //no
student& s2 = (student&)b; //yes
return 0;
}
可以总结:
子类对象可以赋值给父类对象(切片/切割)
父类对象不可以赋值给子类对象
父类指针 / 引用可以指向子类对象
子类指针 / 引用不能指向父类对象(可以通过强制类型转换来指向)
下面我们一条一条解释
那什么是切片/切割呢?看下图
子类可以赋值给父类,是因为子类里边包含父类里的全部成员变量,所以可以通过切片把父类的成员都赋予相应的值
父类赋给子类为什么不可以呢?是因为父类里面没有子类独有的那部分变量,所以无法给子类独有的成员变量赋值,因此不能用父类给子类赋值
父类指针 / 引用可以指向子类对象
现在我们知道子类指针 / 引用不能指向父类对象,因为子类比父类空间大,所以会越界,那强制类型转换会出现什么情况呢?
如果上面代码的main函数内加上以下这两句代码程序会是什么反应呢?
s1->_num = 10;
s2._num = 20;
解释如下图所示:
继承体系中的作用域:
//派生类构造函数和析构函数的构造规则。
#include <iostream>
using namespace std;
class First // 声明基类
{
public:
First()
:a(0)
, b(0)
{}
First(int x, int y)
:a(x)
, b(y)
{}
~First()
{}
void print()
{
cout << "\n a=" << a << "b = " << b;
}
private:
int a, b;
};
class Second : public First //声明基类Frist的公有派生类Second
{
public:
Second()
:First(1,1)
,c(0)
,d(0)
{}
Second(int x , int y)
:First(x+1, y+1)
, c(x)
, d(y)
{}
Second(int x, int y, int m, int n)
:First(m, n)
, c(x)
, d(y)
{}
~Second()
{}
void print()
{
First::print();
cout << " c=" << c << " d= " << d;
}
private:
int c, d;
};
class Third:public Second //声明Second的公有派生类Third
{
public:
Third()
:e(0)
{}
Third(int x, int y)
:Second(x,y)
{}
Third(int x, int y, int z,int m, int n)
:Second(x, y,m,n)
, e(z)
{}
~Third()
{}
void print()
{
Second::print();
cout << " e=" << e;
}
private:
int e;
};
int main()
{
Third l(3, 2,1);
l.print();
}
通常情况下,当创建派生类对象时,首先要调用基类的构造函数,随后在调用派生类的构造函数,当撤销派生类对象时,则先调用派生类的析构函数,随后调用基类的析构函数,遵循先调用的后释放,后调用的先释放。
看如上代码总结出以下三点:
(1)“Third()”从这里可以看出,当基类构造函数不带参数时, 派生类不一定需要定义构造面数, 系统会自动的调用基类的无参构造函数; 然而当基类的构造函数那怕只带有一个参数, 它所有的派生类都必须定义构造函数,
甚至所定义的派生类构造函数的函数体可能为空, 它仅仅起参数的传递作用, 例如, 在上面的程序段中" Third(int x, int y)", 派生类 Third就不使用参数x和y, x和y只是被传递给了要调用的基类构造函数Second
(2)若基类使用默认构造函数或不带参数的构造函数, 则在派生类中定义构造函数时可略“:基类构造函数名(参数表)”, 此时若派生类也不需要构造函数, 则可不定义构造函数
3)如果派生类的基类也是一个派生类, 每个派生类只需负责其直接基类数据成员的初始,依次上溯。
多继承与菱形继承会出现二义性和数据冗余现象:
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B : virtual public A
{
public:
int _b;
};
class C : virtual public A
{
public:
int _c;
};
class D : public C, public B
{
public:
int _d;
};
int main()
{
D dd;
cout << sizeof(dd) << endl;
dd.B::_a = 1;
dd._b = 3;
dd.C::_a = 2;
dd._c = 4;
dd._d = 5;
B bb;
C cc;
cout << sizeof(bb) << endl;
//bb = dd;
cc = dd;
//A* pa = ⅆⅆ
//B* pb = ⅆ
//C* pc = ⅆⅆ
//D* pd = ⅆ
return 0;
}
为什么运行结果是这样?看完下面的对象的内存分布也就明白了
从上图可以发现,当使用虚继承后,通过虚基表就解决了数据二义性的问题。多了虚基表的空间所以sizeof(dd)就变成24了。
int main()
{
D dd;
dd.B::_a = 1;
dd._b = 3;
dd.C::_a = 2;
dd._c = 4;
dd._d = 5;
B bb;
C cc;
A* pa = ⅆ
B* pb = ⅆ
C* pc = ⅆ
D* pd = ⅆ
return 0;
}
补充几点:
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例