public继承方式:基类和子类是is—a的关系
is—a:可以将一个子类对象看成是一个基类对象(所有用到基类对象的位置,都可以使用子类的对象来代替)
验证:类成员函数传参,以及对象模型
在基类中存在拷贝构造函数的情况下,就算派生类没有显示提供拷贝构造函数,但是编译器还是会自己提供默认的拷贝构造函数,在派生类的默认拷贝构造函数的初始化列表自动的去调用基类的拷贝构造函数,但是建议在基类中存在拷贝构造函数的情况下,最好派生类也显式给出拷贝构造函数
如果基类和派生类都有显示的拷贝构造函数那么此时必须在派生类中用户自己调用基类的拷贝构造函数
构造函数也是一样,当你派生类和基类都没提供的时候,编译器给你生成构造函数,并i企鹅在派生类初始化列表中去调用,当你自己显示提供构造函数之后就需要你自己显示的在初始化列表的位置进行调用。
在类中存在资源管理的情况下,必须要给出赋值运算符重载。
===========================================================================
在继承体系中构造函数和析构函数的调用次序:
class A
{
public:
A()
{
cout << "A的构造函数" << endl;
}
~A()
{
cout << "A的析构函数" << endl;
}
private:
int _a;
};
class B : public A
{
public:
B()
{
cout << "B的构造函数" << endl;
}
~B()
{
cout << "B的析构函数" << endl;
}
private:
int _b;
};
int main()
{
B b;
return 0;
}
打印结果:
这个是打印顺序,
实际上是先执行派生类的构造,在派生类的初始化列表的位置又调用了基类的构造函数,完了之后再初始化派生类自己的函数体。
析构的时候,先调用派生类的析构函数,在派生类的析构函数执行完毕之后再去调用基类的析构函数
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类 对象先清理派生类成员再清理基类成员的顺序。
- 派生类对象初始化先调用基类构造再调派生类构造。
- 派生类对象析构清理先调用派生类析构,再调用基类析构
============================================================================
实现一个不能被继承的类:
C++98版本做法:
class A
{
public:
static A& init()
{
return A();
}
static A& init(int a)
{
return A(a);
}
private:
A() //将所有能被继承用到的函数都给成私有
{}
int _a;
};
int main()
{
A b(A::init(10)); //可以通过这样来创建对象
return 0;
}
在C++11中给出了一个关键字:只需要在不想被继承的类的后面加上关键字final就可以了
class A final
{
public:
A()
{}
A(int a)
{}
private:
int _a;
};
============================================================================
友元函数不能被继承。
1.基类中的静态成员可以被继承,
2.在整个继承体系中静态的成员变量只有一个,也就是说,无论父类被多少子类继承,静态成员只有一个。
============================================================================
单继承:一个类只有一个基类,就叫做单继承。
多继承:一个类有多个基类(至少是两个)。
不同继承体系中,派生类的对象模型。
对象模型指的就是,一个对象的成员变量在内存中的布局。
class A
{
int _a;
};
class B
{
int _b;
};
class C : public A ,public B
{
int _c;
};
int main()
{
cout << sizeof(C) << endl;
return 0;
}
多继承这样写,
上面的多继承从内存窗口看是这个个样子:
得出:
先继承谁,谁的成员就在对象内存模型的最上面
1.在派生类对象的模型中,将多个基类中的成员变量全部集成到子类的对象中,
注意:继承中成员在子类对象中的排列次序与继承列表中继承的次序一致。
===========================================================================
钻石继承(菱形继承):
class A
{
public:
int _a;
};
class B : A
{
public:
int _b;
};
class C : A
{
public:
int _c;
};
class D : public B ,public C
{
public:
int _d;
};
继承方式:
int main()
{
cout << sizeof(D) << endl;
return 0;
}
//大小20
多继承的缺陷就是容易产生二义性,第一个解决的办法就是加上作用于访问限定符,例如d.B::_a,这样的话就可以运行,但是没有从根本还是那个解决二义性,假如变量大小很大的话,此时这个变量在内存中有两份,浪费了不必要的内存空间
为了解决这个问题,所以引入了虚拟继承,------>加入了菱形虚拟继承
虚拟继承唯一的作用就是解决菱形继承的二义性问题。
如下图所示:
为了研究菱形虚拟继承,首先研究单继承中的虚拟继承:
class A
{
public:
int _a;
};
class B : public virtual A
{
public:
int _b;
};
int main()
{
B b;
b._a = 1;
b._b = 2;
return 0;
}
接着继续往下走:
再分析:
虚拟单继承的内存对象模型,的前四个字节中存放的就是一个地址,这个地址指向一个虚基表
分析后就可以得出,虚拟单继承的内存对象模型。
那么为什么虚拟继承中,要把基类的变量放到下面,和普通的单继承的对象模型反过来呢?
这是因为,虚拟继承是为了结局菱形继承的二义性问题,放到单独的虚拟继承中看的话,根本是没有意义的。
所以再看看菱形虚拟继承:
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B : public virtual A
{
public:
int _b;
};
class C : public virtual A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl; //大小24字节
D d;
d._a = 1;
d._b = 2;
d._c = 3;
d._d = 4;
return 0;
}
由此再推测,菱形虚拟继承的内存对象模型: