生活中有人给与帮助那是幸运, 没人给与帮助, 那是命运,.
我们要学会在幸运青睐自己的时候学会感恩, 在命运磨练自己的时候学会坚韧!
16. 继承
windows64位操作系统, vs2013
16.1继承的概念以及如何定义
16.1.1继承的概念(龙生龙, 凤生凤, 老鼠的儿子会打洞)
例子: 诺基亚手机—>半智能手机—>智能手机
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
#include <iostream>
using namespace std;
#include <string>
//基类 || 父类
class Person{
public:
void SetPerson(const string& name, const string& gender, int age, double money){
_name = name;
_gender = gender;
_age = age;
_money = money;
}
public:
string _name;
string _gender;
protected:
int _age;
private:
double _money;
};
//派生类 || 子类
class Student :public Person{
public:
void SetStudent(int stuID){
_stuID = stuID;
}
void Study(){
cout << "努力学习!" << endl;
}
private:
int _stuID;
};
int main(){
cout << sizeof(Student) << endl;
Student s;
s.SetPerson("小白", "男", 25, 8000.00);
s.SetStudent(201493144175);
s.Study();
return 0;
}
16.1.2 继承定义
<1> 定义格式
<2> 继承关系和访问限定符
<3> 继承基类成员访问方式的变化
public > protected > private
// 派生类/子类---一定要对基类进行扩展
// public继承方式:基类中public和protected访问权限修饰的成员在子类中的权限不变
// 基类中private修饰的成员在子类中不能直接访问 || 不可见---已经被继承到子类中
class Derived : public Base
{
public:
void SetDerived(int pubD, int proD, int priD)
{
_pub = pubD;
// 基类中protected修饰的成员,在子类中的权限:protected
_pro = proD;
// public继承方式,基类中private成员在派生类中不能直接访问---不可见
//_pri = priD;
}
public:
int _pubD;
protected:
int _proD;
private:
int _priD;
};
class D : public Derived
{
public:
void TestFunc()
{
_pro = 10;
}
};
int main()
{
cout << sizeof(Derived) << endl;
Derived d;
d._pub = 10;
//d._pro = 20;
return 0;
}
#endif
#if 0
// public
// 基类/父类
class Base
{
public:
void SetBase(int pub, int pro, int pri)
{
_pub = pub;
_pro = pro;
_pri = pri;
}
void PrintBase()
{
cout << _pub << " " << _pro << " " << _pri << endl;
}
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
// 派生类/子类---一定要对基类进行扩展
// protected继承方式:基类中被public修饰的成员在子类中的权限降为protected
// 基类中被protected修饰的成员在子类中的权限不变
// 基类中private修饰的成员在子类中不能直接访问 || 不可见---已经被继承到子类中
class Derived : protected Base
{
public:
void SetDerived(int pubD, int proD, int priD)
{
_pub = pubD;
_pro = proD;
//_pri = priD;
}
};
class D : public Derived
{
public:
void TestFunc()
{
_pro = 10;
_pub = 20;
}
};
int main()
{
Derived d;
//d._pub = 10;
return 0;
}
#endif
#if 0
class Base
{
public:
void SetBase(int pub, int pro, int pri)
{
_pub = pub;
_pro = pro;
_pri = pri;
}
void PrintBase()
{
cout << _pub << " " << _pro << " " << _pri << endl;
}
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
// 派生类/子类---一定要对基类进行扩展
// private继承方式:基类中被public和protected修饰的成员在子类中的权限降为private
// 基类中private修饰的成员在子类中不能直接访问 || 不可见---已经被继承到子类中
class Derived : private Base
{
public:
void SetDerived(int pubD, int proD, int priD)
{
_pub = pubD;
_pro = proD;
//_pri = priD;
}
};
class D : public Derived
{
public:
void TestFunc()
{
//_pub = 10;
//_pro = 10;
}
};
#endif
<4> 总结
- 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
- 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
- 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
- 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中
扩展维护性不强。
16.2 基类和派生类对象赋值转换 (赋值兼容规则)
前提条件: 公有的继承方式
(1) 派生类对象可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去;(孩子像父亲)
(2) 基类对象不能赋值给派生类对象;
(3) 基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。
class Base
{
public:
void SetBase(int b)
{
_b = b;
}
void PrintBase()
{
cout << _b << endl;
}
protected:
int _b;
};
/*
public继承方式: 基类中成员在派生类中访问权限不会发生改变
*/
struct Derived : public Base
{
public:
void SetDerived(int b, int d)
{
_b = b;
_d = d;
}
int _d;
};
// 如果是public的继承方式,子类与基类的关系: is-a
// is-a: 可以将子类对象看成是一个基类对象
// 函数调用(在类外):在类外所有用到基类对象的位置,都可以使用派生类对象代替
// 对象模型:对象中各个成员变量在内存中的布局方式
// 1. 可以将子类对象直接赋值给基类对象
// 2. 可以让基类的指针或者引用直接指向子类的对象
int main()
{
Base b;
b.SetBase(10);
Derived d;
d.SetDerived(20, 30);
/*b.SetBase(40);
b.PrintBase();*/
d.SetBase(40);
d.PrintBase();
b = d;
//d = b;
Base* pb = &b;
pb = &d;
Base& rb = d;
Derived* pd = &d;
pd = (Derived*)&b;
pd->_d = 30;
return 0;
}
16.3 继承中的作用域
同名隐藏(重定义)
基类与派生类中具有相同名称的成员(成员变量,成员函数)时 :
(1) 如果通过派生类对象调用相同名称的成员时,优先调用派生类自己;
(2) 如果是成员变量同名,与成员变量的类型是否相同无关;
(3) 如果是成员函数同名,与成员函数的原型是否相同无关, 都优先调用派生类自己的同名成员;
(4) 如果想要调用基类的同名成员,只需在同名成员前增加基类的名称以及作用域限定符, (在子类成员函数中,可以使用 基类::基类成员 显示访问);
class Base
{
public:
void SetBase(int b)
{
_b = b;
}
void TestFunc(int)
{}
void PrintBase()
{
cout << _b << endl;
}
int _b;
};
struct Derived : public Base
{
public:
void SetDerived(int b, int d)
{
_b = b;
}
void TestFunc()
{}
char _b;
};
int main()
{
cout << sizeof(Derived) << endl;
Derived d;
d._b = 1;
d.Base::_b = 2;
d.Base::TestFunc(10);
return 0;
}
16.4 派生类的默认成员函数
注意:
如果一个类没有显示定义任何构造函数, 编译器将会生成一个无参的默认的构造函数; 如果基类具有带有参数的构造函数时, 派生类必须显示定义自己的构造函数, 并且在其初始化列表的位置显示调用基类的构造函数.
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的复制。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
- 派生类对象初始化先调用基类构造再调派生类构造。
- 派生类对象析构清理先调用派生类析构再调基类的析构。
#if 0
// 如果基类没有定义构造函数 或者 基类具有默认的构造函数(无参和全缺省的构造函数)
// 1. 派生类可以不用定义
// 2. 如果派生类需要做其他事情,将自己的构造函数显式给出
class Base
{
public:
Base(int b = 10)
: _b(b)
{}
void SetBase(int b)
{
_b = b;
}
void TestFunc(int)
{}
void PrintBase()
{
cout << _b << endl;
}
int _b;
};
struct Derived : public Base
{
public:
// 编译器给派生类生成的默认构造函数
//Derived()
// : Base()
//{}
void SetDerived(int b, int d)
{
_b = b;
_d = d;
}
void TestFunc()
{}
char _d;
};
#endif
#if 0
class Base
{
public:
Base(int b)
: _b(b)
{}
Base(const Base& b)
: _b(b._b)
{}
Base& operator=(const Base& b)
{
if (this != &b)
{
_b = b._b;
}
return *this;
}
void SetBase(int b)
{
_b = b;
}
void TestFunc(int)
{}
void PrintBase()
{
cout << _b << endl;
}
~Base()
{
cout << "Base::~Base()" << endl;
}
int _b;
};
// 如果基类具有无参的构造函数时,派生类必须显式定义自己的构造函数
// 并且必须在其初始化列表的位置显式调用基类的构造函数完成派生类对象
// 中基类部分成员的初始化
struct Derived : public Base
{
public:
Derived(int b, int d)
: Base(b)
, _d(d)
{}
// 编译器给派生类生成的默认构造函数
//Derived()
// : Base()
//{}
// 如果基类没有显式定义自己的拷贝构造函数,派生类是否定义都可以
// 如果基类显式定义自己的拷贝构造函数,派生类必须显式定义拷贝构造函数
// 并且需要在其拷贝构造初始化列表的位置显式调用基类的拷贝构造函数
Derived(const Derived& d)
: Base(d)
, _d(d._d)
{}
/*
没有涉及到资源管理时:如果派生类没有显式定义自己的赋值运算符重载,编译器会生成一份默认的,
该默认的赋值运算符重载可以完整的赋值
如果类中涉及到资源管理:派生类需要将自己的拷贝构造函数显式给出,必须在其内部显式调用基类的
赋值运算符重载完成基类部分的赋值,在完成自己特有成员的赋值
*/
Derived& operator=(const Derived& d)
{
if (this != &d)
{
Base::operator=(d);
_d = d._d;
}
return *this;
}
void SetDerived(int b, int d)
{
_b = b;
_d = d;
}
void TestFunc()
{}
// 派生类对象:基类部分 + 派生类特有
~Derived()
{
// 派生类清理自己的资源
cout << "Derived::~Derived()" << endl;
// 编译器在拍摄类最后一条语句之后插入一条调用基类析构函数的汇编语句
// 清理派生类对象中属于基类部分的资源
// call Base::~Base();
}
char _d;
};
void TestDerived()
{
Derived d1(10, 20);
Derived d2(30, 40);
d1 = d2;
}
int main()
{
TestDerived();
return 0;
}
#endif
#if 0
class Base
{
public:
Base(int b)
: _b(b)
{
cout << "Base::Base()" << endl;
}
~Base()
{
cout << "Base::~Base()" << endl;
}
int _b;
};
struct Derived : public Base
{
public:
Derived(int b, int d)
: Base(b)
, _d(d)
{
cout << "Derived::Derived()" << endl;
}
// 派生类对象:基类部分 + 派生类特有
~Derived()
{
// 派生类清理自己的资源
cout << "Derived::~Derived()" << endl;
// 编译器在拍摄类最后一条语句之后插入一条调用基类析构函数的汇编语句
// 清理派生类对象中属于基类部分的资源
// call Base::~Base();
}
char _d;
};
//
/*
1. 打印结果
Base::Base()
Derived::Derived()
Derived::~Derived()
Base::~Base()
*/
// 2. 函数调用次序
/*
基类构造
派生类构造
派生类析构
基类析构
创建那个类的对象,调用那个类的构造函数
析构那个类的对象,调用那个类的析构函数
Derived d(10, 20);
调用派生类的构造函数
: 调用基类的构造函数
{}
调用派生类的析构函数
{
// 析构派生类自己的资源
// call 基类的析构函数
}
*/
常见面试题: 实现一个不能继承的类
方法一:C++98(构造函数私有化+公有的静态方法并返回)
class Base{
public:
//静态的成员函数不需要要this指针
static Base GetInstrance(int b){
return Base(b); //返回无名对象,减少一次构造调用
}
private:
Base(int b = 0)
:_b(b)
{}
protected:
int _b;
};
/*
//编译器给派生类生成了一个默认的构造函数, 目的:在其初始化列表的位置
//显示调用基类的构造函数,为了完成派生类对象属于基类部分成员的初始化
class Derived:public Base{
/*
public:
Derived()
:Base()
{}
*/
};
int main(){
Base b = Base::GetInstrance(10);
return 0;
}
*/
方法二:C++11(提供的新的关键字final禁止继承)
class Base final{
public:
Base(int b = 0)
:_b(b)
{}
protected:
int _b;
};
方法三:采用类模板+虚拟方式
16.5 继承和友元
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员;
#if 0
class B
{
friend void TestFunc();
public:
protected:
int _b;
};
class D : public B
{
protected:
int _d;
};
void TestFunc()
{
// 该函数是基类的友元函数
// 可以在该函数中访问基类保护或者私有的成员
B b;
b._b = 10;
// 不能访问子类中私有或者保护的成员
D d;
d._d = 10;
// 结论:友元关系 不能被继承
// 继承:子类一定继承的是基类中的成员
// 例子:你爸爸的王哥家的财产--与你无关
}
#endif
16.6 继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
class Person{
public :
Person () {++ _count ;}
protected :
string _name ; // 姓名
public :
static int _count; // 统计人的个数。
};
int Person :: _count = 0;
class Student : public Person{
protected :
int _stuNum ; // 学号
};
class Graduate : public Student{
protected :
string _seminarCourse ; // 研究科目
};
void TestPerson(){
Student s1 ;
Student s2 ;
Student s3 ;
Graduate s4 ;
cout <<" 人数 :"<< Person ::_count << endl;
Student ::_count = 0;
cout <<" 人数 :"<< Person ::_count << endl;
}
16.7 复杂的菱形继承及菱形虚拟继承
单继承:一个子类只有一个继承父类时称这个继承关系为单继承;
多继承:一个子类有两个或以上继承父类时称这个继承关系为多继承;
#if 0
// 不同继承方式下,派生类的对象模型
// B <--- D 单继承:一个类只有一个基类
// 多继承
class B1
{
public:
int _b1;
};
class B2
{
public:
int _b2;
};
// 派生类将两个基类中的成员都继承到子类中
// 在派生类对象模型中:先继承那个基类,该基类中的成员就在对象模型的最上方
class D : public B1, public B2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
d._b1 = 1;
d._b2 = 2;
d._d = 3;
return 0;
}
#endif
菱形继承:菱形继承是多继承的一种特殊情况 (单继承+多继承)。
// 菱形继承:单继承 + 多继承
class B
{
public:
void TestFunc()
{}
public:
int _b;
};
class C1 : public B
{
public:
int _c1;
};
class C2 : public B
{
public:
int _c2;
};
class D : public C1, public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
// C1从B中继承一个_b, C2从B中继承一个_b
// D: 从C1中继承一个_b, 从C2中继承一个_b
// D中就有两份_b
// 如果直接通过派生类对象访问_b,编译器就不知道访问那个_b
// 编译时报错:访问不明确
// 以上就是菱形继承缺陷---菱形继承的二义性问题
//d._b = 1;
//d.TestFunc();
// 代码通过编译:让访问明确
// 1. 在_b前增加基类的名称
d.C1::_b = 1;
d._c1 = 2;
d.C2::_b = 3;
d._c2 = 4;
d._d = 5;
d.C1::TestFunc();
d.C2::TestFunc();
// 2. 能否让最顶层基类成员(B)在D类中只保存一份
// 可以---菱形虚拟继承
return 0;
}
#endif
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
虚拟继承
#if 0
// 虚拟继承方式
class B
{
public:
int _b;
};
class D : virtual public B
{
public:
int _d;
};
// 虚拟继承和普通的单继承有什么区别?
int main()
{
cout << sizeof(D) << endl;
D d;
d._b = 1;
d._d = 2;
return 0;
}
#endif
菱形虚拟继承
#if 0
// 菱形虚拟继承
class B
{
public:
void TestFunc()
{}
public:
int _b;
};
class C1 : virtual public B
{
public:
int _c1;
};
class C2 : virtual public B
{
public:
int _c2;
};
class D : public C1, public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
// C1从B中继承一个_b, C2从B中继承一个_b
// D: 从C1中继承一个_b, 从C2中继承一个_b
// D中就有两份_b
// 如果直接通过派生类对象访问_b,编译器就不知道访问那个_b
// 编译时报错:访问不明确
// 以上就是菱形继承缺陷---菱形继承的二义性问题
//d._b = 1;
//d.TestFunc();
// 代码通过编译:让访问明确
// 1. 在_b前增加基类的名称
d.C1::_b = 1;
d._c1 = 2;
d.C2::_b = 3;
d._c2 = 4;
d._d = 5;
d.C1::TestFunc();
d.C2::TestFunc();
// 2. 能否让最顶层基类成员(B)在D类中只保存一份
// 可以---菱形虚拟继承
d._b = 0;
d.TestFunc();
return 0;
}
#endif
#if 0
// 菱形虚拟继承
class B
{
public:
void TestFunc()
{}
public:
int _b;
};
class C1 : virtual public B
{
public:
int _c1;
};
class C2 : virtual public B
{
public:
int _c2;
};
class D : public C1, public C2
{
public:
int _d;
};
int main()
{
// 24
cout << sizeof(D) << endl;
D d;
d._b = 1;
d._c1 = 2;
d._c2 = 3;
d._d = 4;
C1& c1 = d;
c1._b = 5;
C2& c2 = d;
c2._b = 6;
return 0;
}
#endif