1、定义
继承的设计是C++中时代码可以复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类(或者子类),和基类(或者父类)是is-a关系。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程,并且是针对类设计层次的复用。
2、继承定义的格式
其中继承方式是可以省略的,如果子类是class修饰的类,则默认继承方式为private;如果子类是struct修饰的类,则默认继承方式为public。
3、继承基类成员访问方式的变化
类成员/继承方式 | public继承 | protected继承 | private继承 |
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 派生类中不可见 | 派生类中不可见 | 派生类中不可见 |
派生类中访问基类成员的权限:继承方式和基类成员访问限定方式取访问权限更小的。另外在派生类中不可见的意思是类外和类里面均不能使用基类成员;私有的含义是,类里面可以使用基类成员,类外面不能。
class Person {//下面注释中的类指的是基类和派生类两个类
//private,基类中无法被访问,类中类外都不能访问,只能自己访问
protected://在类外无法访问,类里面可以
void print() {
cout << "Person::print" << endl;
}
int _a;
};
//继承方式取基类和子类较小权限的修饰词即protected
//因此类外无法访问,只能在类中访问。
class Student : private Person {
public:
void func() {
print();
}
int _b;
};
int main()
{
Person p;
Student s;
s.func();
return 0;
}
输出结果:
4、赋值兼容规则
子类对象赋值给父类对象的引用时,中间不产生临时对象,这个叫做父子类赋值兼容规则(切割/切片,即引用子类中包含父类的那一部分)。
int main()
{
Student s("hu", 13);
Person& p2 = s;
//s和p2虽然是不同类,但是由于继承特殊关系,拥有赋值兼容规则,所以不需要临时对象
// 常见的两个不同类型之间转化是需要有临时变量的,例如double b; int& a = b;
p2.print();
return 0;
}
5、子类构造函数、拷贝构造
子类构造函数、拷贝构造一定要把父类当作一个对象去初始化,而不是在子类中初始化父类的每一个成员。并且构造函数和拷贝构造函数都是先构造父类再来完成子类剩余成员的初始化,以至于完成子类函数构造。
class Person {//下面注释中的类指的是基类和派生类两个类
//private,基类中无法被访问,类中类外都不能访问,只能自己访问
public://在类外无法访问,类里面可以
Person(const char* name = "Peter", const int age = 12)
:_name(name)
,_age(age)
{
cout << "Person()Person(const char* name = Peter, const int age = 12)" << endl;
}
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p) {
if (this != &p) {
_name = p._name;
_age = p._age;
}
return *this;
}
protected:
string _name;
int _age;
};
//继承方式取基类和子类较小权限的修饰词即protected
//因此类外无法访问,只能在类中访问。
class Student : public Person {
public:
Student(const char* name = "Peter", const int age = 12,const int no = 8)
:Person(name,age)
,_no(no)
{
cout << "Student::Student()" << endl;
}
Student(const Student& s)
: Person(s)
,_no(s._no)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator=(const Student& s) {
if (this != &s) {
Person::operator=(s);
_no = s._no;
}
return *this;
}
private:
int _no;
};
int main()
{
Student s("hu", 13);
Student s1(s);
return 0;
}
输出结果:
6、基类成员隐藏
当父类和子类中有相同的变量名(由于就近原则子类会优先使用自己的成员变量),这种情况称为子类同名成员隐藏父类同名成员。若要使用基类中的变量(函数隐藏也一样),则需要指定作用域。(通常不建议使用同名函数!)
class Person {
public:
Person(const char* name = "xiaohu", const int age = 12)
:_name(name)
, _age(age)
{}
protected:
string _name;
int _age;
};
class Student : public Person {
public:
Student(const char* name = "Peter", const int age = 12, const int no = 8)
:Person(name, age)
, _no(no)
{
cout << "Student::Student()" << endl;
}
void func() {
_name = "Bob";
}
void sprint() {
cout << _name << endl;
cout << Person::_name << endl;
}
private:
string _name;
int _no;
};
int main()
{
Student s("hu", 13);
s.func();//将同名变量赋值
s.sprint();
return 0;
}
输出结果:
7、子类的析构函数
由于多态的原因,析构函数统一会被处理成destructor,导致父子类的析构函数构成隐藏。直接在子类中指定作用域调用父类析构,会导致父类的析构调用两次。“祖师爷”为了保证释放安全(先析构子类,再析构父类),父类的析构函数不需要显示调用,因为子类析构函数结束时会自动调用父类析构函数。
注意:先析构子类,在析构父类目的时防止父类释放了,子类中还有对父类成员访问的情况发生。
class Person {
public:
~Person() {
cout << "~Person()" << endl;
}
protected:
string _name;
int _age;
};
class Student : public Person {
public:
~Student(){//会自动调用基类析构函数
cout << "~Student()" << endl;
}
private:
string _name;
int _no;
};
int main()
{
Student s;
return 0;
}
输出结果:
class Person {
public:
~Person() {
cout << "~Person()" << endl;
}
protected:
string _name;
int _age;
};
class Student : public Person {
public:
~Student(){//会自动调用基类析构函数
cout << "~Student()" << endl;
}
private:
string _name;
int _no;
};
int main()
{
Student s;
return 0;
}
8、静态成员继承
静态成员并不会真正被继承,而只是继承了使用权。
class Person {
public:
~Person() {
cout << "~Person()" << endl;
}
public:
string _name;
static int _age;
};
int Person::_age = 0;
class Student : public Person {
public:
~Student(){//会自动调用基类析构函数
cout << "~Student()" << endl;
}
private:
string _name;
int _no;
};
int main()
{
Student s;
Person p;
cout << &p._age << endl;
cout << &s._age << endl;
return 0;
}
输出结果:由结果可发现上述两个不同对象中静态成员的地址是一样的。
9、多继承
一个子类有两个或者以上直接父类,则称这个继承关系为多继承。
10、菱形继承
多继承可能会导致菱形继承(如下图),如果不做处理,会使成员有二义性(给成员赋值,但系统不知道是哪一个成员)和冗余性(指定作用域赋值后导致数据冗余)问题。
void Test ()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a ;
a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
通常解决办法是:有一个类被多个继承时,用virtual继承方式。
11、继承和组合
组合定义:一个类包含的成员中至少有一个成员是另一个类的对象。
和继承区别:public继承是一种is-a的关系,也就是说每个派生类对象都是一个基类对象,并且派生类合基类的依赖关系很强,耦合度高。
组合是一种has-a的关系,假设B组合A,每个B对象中都有一个A对象,耦合度较低,因此对代码的维护性强。
class tire {
public:
size_t _size = 80;
};
class car {
public:
void ChooseTire() {
_t._size = 100;
cout << _t._size << endl;
}
protected:
string _brand;
tire _t;
};
int main()
{
car ben;
tire t;
ben.ChooseTire();
return 0;
}
输出结果: