第十五章 继承
15.1 继承和派生
15.1.1 为什么需要继承
提高代码重用,提高开发效率。
15.1.2 继承的基本概念
c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类),类B成为派生类(子类)。派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员。从基类继承过来的表现其共性,而新增的成员体现了其个性。
15.1.3 派生类的定义
class 父类{};
class 子类:继承方式 父类名
{
//新增子类数据
};
继承方式:private protected public(推荐)
所有父类私有在子类中不可访问,公共继承 保持不变,保护继承变保护,私有继承变私有。
class Base
{
private:
int a;
protected:
int b;
public:
int c;
};
class Son:public Base
{
//子类中 能访问protected b public c
public:
void func(void)
{
cout<<b<<c<<endl;
//cout<<a<<endl;//不可访问
}
};
int main()
{
Son ob;
//cout<<ob.b<<endl;//类外无法访问
cout<<ob.c<<endl;
}
15.2 继承中的构造和析构
15.2.1 子类的构造析构顺序
class Base
{
public:
Base()
{
cout<<"父类构造"<<endl;
}
~Base()
{
cout<<"父类析构"<<endl;
}
};
class Other
{
public:
Other()
{
cout<<"Other构造"<<endl;
}
~Other()
{
cout<<"Other析构"<<endl;
}
};
class Son:public Base
{
public:
Other ob;
public:
public:
Son()
{
cout<<"Son构造"<<endl;
}
~Son()
{
cout<<"Son析构"<<endl;
}
};
int main()
{
Son ob;
return 0;
}
15.2.2 子类调用成员对象、父类的有参构造
子类实例化对象时 会自动调用成员对象、父类的默认构造。
子类实例化对象时|必须死用初始化列表 调用成员对象、父类的有参构造。
初始化列表时:父类写类名称 成员对象用对像名
#include <iostream>
using namespace std;
class Base
{
public;
int a;
public:
Base()
{
cout<<"父类默认构造"<<endl;
}
Base(int a)
{
this->a = a;
cout<<"父类有参构造"<<endl;
}
~Base()
{
cout<<"父类析构"<<endl;
}
};
class other
{
public:
int b;
public:
Other()
{
cout<<"Other默认构造"<<endl;
}
Other(int b)
{
this->b = b;
cout<<"Other有参构造"<<endl;
}
~Other()
{
cout<<"Other析构"<<endl;
}
};
class Son:public Base
{
public:
Other ob;
int c;
public:
Son()
{
cout<<"Son构造"<<endl;
}
//父类写类名称 成员对象用对象名
Son(int a,int b,int c):Base(a),ob(b)
{
this->c = C;
cout<<"Son有参构造"<<endl;
}
~Son()
{
cout<<"Son析构"<<endl;
}
};
int main()
{
Son ob(10,20,30);
return 0;
}
15.3 子类和父类的同名处理
同名成员 最简单 最安全的处理方式:加作用域
15.3.1 子类和父类 同名成员数据
子类默认优先访问 子类的同名成员
必须加父类作用域 访问父类的同名成员
class Base
{
public:
int a;
public:
Base(int a)
{
this->a = a;
}
};
class Son:public Base
{
public:
int a;
Son(int x, int y):Base(x)
{
a = y;
}
};
int main()
{
Son ob(10,20);
//子类默认优先访问 子类的同名成员
cout<<ob.a<<endl;//10
//必须加父类作用域 访问父类的同名成员
cout<<ob.Base::a<<endl;//20
return 0;
}
15.3.2 子类和父类 同名成员函数
class Base
{
public:
void fun01()
{
cout<<"Base 无参的fun01"<<endl;
}
};
class Son:public Base
{
public:
void fun01()
{
cout<<"Son 无参的fun01"<<endl;
}
};
int main()
{
Son ob;
//子类默认优先访问 子类的同名成员
ob.fun01();
//必须加父类作用域 访问父类的同名成员
ob.Base::func01();
return 0;
}
15.3.3 子类重定义 父类的同名函数
重载:无继承,同一作用域,参数的个数不同、顺序不同、类型不同都可重载
重定义:有继承,子类重定义父类的同名函数(参数可以不同)(非虚函数)子类一旦重定义了父类的同名函数(不管参数是否一致),子类中都将屏蔽父类所有的同名函数。
class Base
{
public:
void fun01()
{
cout<<"Base 无参的fun01"<<endl;
}
void fun1(int a)
{
cout<<"Base 的fun01 int"<<endl;
}
void fun01(int a, int b)
{
cout<<"Base 的fun01 int int"<<endl;
}
};
class Son:public Base
{
public:
void fun01(string a)
{
cout<<"Son 的fun01 char"<<endl;
}
};
int main()
{
Son ob;
ob.fun01("hello");
ob.fun01();
ob.fun01(10);
ob.fun01(10,20);
return 0;
}
需要将父类的作用域 才能识别 屏蔽的函数
15.4 子类不能继承父类的成员
不是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。另外**operator=**也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意味着对其派生类依然有效。在继承的过程中,如果没有创建这些函数,编译器会自动生成他们。
15.5 多继承
15.5.1 多继承的概念
我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。
15.5.2 多继承的格式
class 父类1{};
class 父类2{};
class 子类:继承方式1 父类1, 继承方式2 父类2
{
//新增子类数据
};
子类就拥有了父类1,父类2的大部分数据。
class Base1
{
public:
int a;
};
class Base2
{
public:
int b;
};
class Son:public Base1,public Base2
{
public:
};
int main()
{
Son ob;
cout<<ob.a<<" "<<ob.b<<endl;
return 0;
}
15.5.3 多继承中的同名成员处理
如果多继承中 遇到同名成员 需要加父类作用域解决。
class Base1
{
public:
int a;
Base1(int a):a(a){}
};
class Base2
{
public:
int a;
Base2(int a):a(a){}
};
class Son:public Base1,public Base2
{
public:
int a;
Son(int a, int b, int c):Base1(a),Base2(b),a(c){}
};
int main()
{
Son ob(10,20,30);
cout<<ob.a<<endl;//子类a
cout<<ob.Base1::a<<endl;//Base1 a
cout<<ob.Base2::a<<endl;//Base2 a
return 0;
}
15.6 菱形继承
菱形继承:有公共祖先的继承 叫菱形继承。
最底层的子类 数据 会包含多分(公共祖先的数据)
class Animal
{
public:
int data;
};
class Sheep :public Animal {};
class Tuo :public Animal {};
class SheepTuo:public Sheep.public Tub{};
int main()
{
SheepTuo ob;
memset(&ob,0, sizeof(SheepTuo));
//cout<<ob.data<<endl;//二义性
cout<<ob.Sheep::data<<endl;
cout<<ob.Tuo::data<<endl;
}
怎么才能只要公共祖先的一份数据呢?
15.7 虚继承
虚继承 解决菱形中 多份公共祖先素据的问题。
15.7.1 虚继承的方式
在继承方式 前加virtual修饰,子类虚继承父类 子类只会保存一份公共数据。
#include<iostream>
#include<string.h>
using namespace std;
class Animal
{
public:
int data;
};
class Sheep :virtual public Animal{
};
class Tuo :virtual public Animal {
};
class SheepTuo:public Sheep,public Tuo{};
int main()
{
SheepTuo ob;
cout<<ob.data<<endl;
return 0;
}
15.7.2 虚继承的实现原理(VS有效)
1、打开命令行开发模式
2、找到类所在的源文件路径
3、在命令行中导出类的布局
cl /d1 reportSingleClassLayoutAnimal main.cpp
Animal布局:
Sheep布局:
Tuo布局:
SheepTuo布局:
虚继承 会在子类中产生 虚基类指针(vbptr)指向虚基类表(vbtable),虚基类表记录的是通过该指针访问公共祖先的数据的偏移量。
注意:
虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的。 工程开放中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承代替。