✨✨欢迎大家来到Celia的博客✨✨
🎉🎉创作不易,请点赞关注,多多支持哦🎉🎉
所属专栏:C++
目录
[2.1 定义格式](#2.1 定义格式)
[2.2 继承基类成员的访问方式限制](#2.2 继承基类成员的访问方式限制)
[2.3 继承类模板格式](#2.3 继承类模板格式)
[4.1 隐藏](#4.1 隐藏)
[5.1 派生类常见默认成员函数](#5.1 派生类常见默认成员函数)
[5.2 不能被继承的类](#5.2 不能被继承的类)
[8.1 菱形继承](#8.1 菱形继承)
[8.2 虚继承](#8.2 虚继承)
一、继承的概念
继承,是面向对象编程代码复用的手段,它可以在保留原先类的基础上,对原先的类进行扩展,形成新的派生类。在这之前,我们常能接触到的是函数层次上的复用,而继承是类层次上的复用。为了更好的理解继承,我们先来看如下代码:
cpp
class Person
{
public:
string name;
int age;
};
class Student
{
public:
string name;
int age;
string schoolflg;
};
这是Student类和Person类,仔细观察可以发现,这两个类的成员变量是有公共部分的,它们都有姓名、年龄这两个成员变量 。这就有一些冗余,可不可以把姓名、年龄单独提取出来呢?继承就可以很好的解决这个问题。
二、继承的定义
2.1 定义格式
cpp
// 派生类 继承方式 基类
// ↓ ↓ ↓
class Student : public Person
{
public:
string schoolflg;
};
可以通过如上方式来让Student类继承Person类,这时,Person的成员可以看作被Student继承下来。 Student中就具有了姓名、年龄等从Person中继承下来的成员,并可以使用它们。
2.2 继承基类成员的访问方式限制
| 类成员\继承方式 | public继承 | protected继承 | private继承 |
| 基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 派生类中不可见 | 派生类中不可见 | 派生类中不可见 |
---|
- 基类的private成员无论在什么继承方式下,在派生类中都是不可见的。但这种不可见并不代表没有继承下来,而仅仅是由于权限的原因,在派生类中无法使用这些成员。
- 基类的private成员在外部是不能被访问的,如果想要让这些成员在派生类中可以被访问,但不想在类外访问,就可以把访问权限改成protected。protected成员只有基类和派生类内部可以访问,在类外是不能够被访问的。
- 如果不写继承方式,class默认的继承方式为private,struct默认的继承方式为public。
2.3 继承类模板格式
cpp
template <class T>
class mystack : public std::vector<T>
{
public:
void push(const T& data)
{
vector<T>::push_back(data);
}
void pop()
{
vector<T>::pop_back();
}
const T& top()
{
return vector<T>::back();
}
bool empty()
{
return vector<T>::empty();
}
};
在继承类模板时,需要指定类域来调用继承下来的成员函数。 原因是在实例化mystack<T>时,vector<T>也被实例化了,但是由于按需实例化的原因,实例化vector<T>时只会实例化它的构造函数,其他函数并没有进行实例化,所以必须指定类域进行访问。
三、基类和派生类之间的转换
- public继承的派生类对象可以赋值给基类的指针/基类的引用。这里有一种说法叫做"切片",把派生类中基类的部分切割出来,并且让基类的指针/基类的引用指向这部分空间。
- 基类对象不可以赋值给派生类对象。
- 基类的引用或者指针可以通过强制类型转换 赋值给派生类的指针或引用。但前提是,这个基类的指针或引用本身的指向就是派生类的对象。
切片
cpp
int main()
{
Student student1;
Person& person = student1;//派生类的对象赋值给基类的引用
Student& student2 = (Student&)person;//基类的引用赋值给派生类的引用(基类的引用本身指向派生类)
return 0;
}
四、继承中的作用域
4.1 隐藏
- 在基类中和派生类中都有独立的作用域。
- 如果基类和派生类有同名成员,派生类成员函数将屏蔽基类的同名成员,直接优先访问派生类的同名成员,这种现象就叫做隐藏。
- 如果想要在构成隐藏的情况下,对基类的同名成员进行访问,需要指定类域。
- 如果函数构成隐藏,只需要函数名相同就可以。
cpp
class Person
{
public:
string name = "Celia";
int age = 10;
};
class Student : public Person
{
public:
void whoami()
{
cout << name << ' ' << age << ' ' << schoolflg << endl; //age构成隐藏
cout << name << ' ' << Person::age << ' ' << schoolflg << endl; //指定类域访问age
}
int age = 999;
string schoolflg = "1234";
};
int main()
{
Student s;
s.whoami();
return 0;
}
五、派生类的默认成员函数
5.1 派生类常见默认成员函数
默认成员函数是指,不进行编写,编译器能够自动生成的成员函数,那么继承体系下派生类的默认成员函数会发生什么变化呢?
- 派生类的构造函数必须调用基类的构造函数完成基类部分成员的初始化,若是基类没有默认构造函数,则必须在派生类的初始化列表中显式调用。
- 派生类的拷贝构造函数必须显式调用基类的拷贝构造函数。调用基类的拷贝构造函数时,可以传入派生类的对象。
- 派生类的赋值重载函数(operator=())必须显式调用基类的赋值重载函数,但是需要注意指定类域调用。不然派生类自身的赋值重载函数会和基类的赋值重载构成隐藏,造成栈溢出。
- 派生类的析构函数调用完成后会自动调用基类的析构函数。因为这样才能保证派生类先清理派生类的成员然后再清理基类的成员。
- 派生类对象初始化先调用基类构造再调用派生类构造。
- 派生类析构先调用派生类析构再调用基类析构。
- 由于多态的需要,所有的析构函数的函数名编译器都会处理成destructor()。所以基类析构函数不加virtual的情况下,派生类析构函数和基类析构函数构成隐藏关系。
cpp
class Person
{
public:
string name;
int age;
Person(string name, int age)
:name(name)
,age(age)
{}
Person(const Person& person)
:name(person.name)
,age(person.age)
{}
void operator=(const Person& person)
{
name = person.name;
age = person.age;
}
};
class Student : public Person
{
public:
string schoolflg;
void whoami()
{
cout << name << ' ' << age << ' ' << schoolflg << endl;
cout << name << ' ' << Person::age << ' ' << schoolflg << endl;
}
Student(string name, int age, string schoolflg)
:Person(name,age) //调用基类构造方法
,schoolflg(schoolflg)
{}
Student(const Student& student)
:Person(student) //调用基类拷贝构造
,schoolflg(student.schoolflg)
{}
void operator=(const Student& student)
{
Person::operator=(student); //调用基类赋值重载
schoolflg = student.schoolflg;
}
};
5.2 不能被继承的类
如果想让一个类不能被继承,C++98的方法是把构造函数放用private修饰,C++11的方法是在这个类的类名后加上 final 关键字即可。
cpp
//C++11
class Person final //Person类不可被继承
{
public:
string name;
int age;
Person(string name, int age)
:name(name)
,age(age)
{}
Person(const Person& person)
:name(person.name)
,age(person.age)
{}
void operator=(const Person& person)
{
name = person.name;
age = person.age;
}
};
cpp
//C++98
class Person
{
public:
string name;
int age;
void operator=(const Person& person)
{
name = person.name;
age = person.age;
}
private:
Person(string name, int age)
:name(name)
, age(age)
{}
Person(const Person& person)
:name(person.name)
, age(person.age)
{}
};
六、继承关系与友元函数
友元关系不可以被继承,基类的友元并不能继承到派生类当中。
七、继承关系和静态成员
基类定义了static静态成员,那么不论派生类实例化多少个对象,都只有一个该static静态成员实例。
八、多继承
- 单继承:派生类只有一个基类
- 多继承:派生类有一个或多个直接基类
cpp
//多继承示例
class A{};
class B{};
class C : public A, public B
{
//...
};
8.1 菱形继承
菱形继承是多继承的一种特殊情况,结构如下所示:
这种情况下,C中就会有两份Base类的成员,一份来自A,另一份来自B。若是想要访问这些冗余的成员变量,必须要指定类域(如果不指定类域会有访问歧义),例如A,B 都有age整型变量,那么在C中想要访问这两个不同类域的age形式只能是:
cpp
A::age 或者 B::age
这种访问方法非常不方便, 而且存在成员冗余的情况。想要解决这个问题,就需要用到虚继承。
8.2 虚继承
cpp
class A : virtual public Base
{
//...
};
class B : virtual public Base
{
//...
};
虚继承在继承方式前加了virtual关键字,自此,如果有类同时继承了A和B,那么A和B中的同名成员可以当作是同一份。这样就解决了成员冗余的问题。