【C++】从零认识C++的“继承”
继承的定义方式是:代码语言:javascriptAI代码解释class 派生类名 : 继承方式 基类名{派生类内容};假如定义一个Person类表示人,派生出一个Student类表示学生。Student包括Person的方法与属性,同时也有Student的独特方法与属性。
一、继承的概念
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,可以增加方法(成员函数)和属性(成员变量),这样产生的类,叫做派生类(或子类)。作为基础的原有类,称为基类(或父类)。
二、定义与使用
1. 定义方式
继承的定义方式是:
代码语言:javascript
AI代码解释
class 派生类名 : 继承方式 基类名
{派生类内容};
假如定义一个Person类表示人,派生出一个Student类表示学生。Student包括Person的方法与属性,同时也有Student的独特方法与属性。那么就可以先定义出Person类,再继承出Student类:
代码语言:javascript
AI代码解释
class Person
{
public:
Person()
{
cout << "Person()" << endl;
}
private:
//名字
string name;
};
class Student : public Person
{
public:
Student()
{
cout << "Student()" << endl;
}
private:
//学号
string id;
};
2. 继承方式
继承方式有三种:public、private、protected。就是三种访问限定符,它们之间有什么关系呢?
|
基类成员的属性\派生类的继承方式 |
public继承后↓ |
protected继承后↓ |
private继承后↓ |
|---|---|---|---|
|
基类的public成员变成→ |
派生类的public成员 |
派生类的protected成员 |
派生类的private成员 |
|
基类的protected成员变成→ |
派生类的protected成员 |
派生类的protected成员 |
派生类的private成员 |
|
基类的prvate成员变成→ |
在派生类中不可见 |
在派生类中不可见 |
在派生类中不可见 |
哎,这里我们就看出来protected和private的区别了。
- 总结一下上面的表格能发现,基类的private成员在派生类中都是不可见的。基类的其他成员在派生类中的访问方式为Min(在基类的访问限定符,继承方式),也就是两者里的较小者。其中public > protected > private。
- 基类的private成员无论以什么方式继承,在派生类中都是不可见的。不可见指的是基类的private成员其实被继承到了派生类中,但是语法上限制派生类对象在类内或类外都不能去访问不可见成员。
- 基类private成员在派生类中不能直接被访问。如果基类成员不想在类外被访问,但是想在派生类中能访问,就定义为protected,可以看出protected限定符是因继承才出现的。
- 继承方式也可以不写,使用class关键字时默认的继承方式是private,使用struct时默认的继承方式是public。最好显式写出继承方式。
- 在实际应用中我们一般都使用public继承,很少使用也不建议用protected和private继承,不利于代码维护。public继承能使基类和派生类中的接口访问方式保持一致。
3. 类模板的继承
类模板也是可以继承的。特殊的是,在定义派生类时,基类如果是类模板,必须指定类域,否则编译报错。
代码语言:javascript
AI代码解释
template<class T>
class stack : public std::vector<T> //或者前面using namespace std;
{
public:
void push(const T& x)
{
push_back(x);
}
};
三、基类和派生类的转换
public继承的派生类对象可以赋值给基类的指针或基类的引用,叫做赋值兼容转换,形象的说法是切片,就像把派生类中的基类那部分切割出来。基类指针或引用指向的是派生类中切出来的基类那部分。

在这里插入图片描述
比如:
代码语言:javascript
AI代码解释
class Person
{
public:
Person()
{
cout << "Person()" << endl;
}
protected:
string name = "zhangsan";
};
class Student : public Person
{
public:
Student()
{
cout << Person::name << endl;
}
private:
string id = "000001";
};
int main()
{
Student stu;
Person* ptr = &stu;
Person& ref = stu;
//此时ptr和ref只指向和引用stu中属于基类的内容
return 0;
}
注意:基类对象不能赋值给派生类对象。
四、基类和派生类的作用域
在继承体系中,基类和派生类都有各自独立的作用域,所以派生类和基类中可以有同名成员,但派生类中将屏蔽对基类的同名成员的直接访问,这种情况叫做隐藏。若想访问基类的这个成员,需要写成基类::基类成员显式访问。 需要知道的是,如果是基类和派生类的成员函数,只要函数名相同,就构成隐藏。 不过,在实际应用继承体系中,我们最好就不要定义同名的成员了。
代码语言:javascript
AI代码解释
class A
{
protected:
int x;
};
class B : public A
{
public:
test()
{
//直接访问不到基类的同名成员
cout << x << endl;
//必须指定类域才能访问
cout << A::x << endl;
}
protected:
int x;
};
再来看一个例子:
代码语言:javascript
AI代码解释
class A
{
public:
void func(int x)
{
cout << "func(int x)" << endl;
}
};
class B : public A
{
public:
void func()
{
cout << "func()" << endl;
}
};
基类A和派生类B中有同名函数,虽然参数列表不同,但是还是构成隐藏关系。直接访问func只会调到派生类的func:

在这里插入图片描述
若想访问到基类的func,也要指定类域:

在这里插入图片描述
五、派生类的默认成员函数
类有默认成员函数的概念,派生类也有。最重要的是要理解:基类成员的初始化、清理等行为都是作为一个整体来完成的。
- 派生类的构造函数必须要调用基类的构造函数,来初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类的构造函数的初始化列表阶段显式调用。而派生类对象初始化时,先调用基类构造,再调派生类构造。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数,因为这样才能保证派生类对象先清理派生类成员,再清理基类成员的顺序。派生类析构函数和基类析构函数构成隐藏关系,这涉及到多态的知识。
- 派生类的拷贝构造函数,必须调用基类的拷贝构造函数,完成基类成员的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=,完成基类的复制。需要注意的是,派生类的operator=和基类的operator=构成隐藏关系,所以显式调用基类的operator=时需要指定基类作用域。
更多推荐



所有评论(0)