一、继承的概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,可以增加方法(成员函数)和属性(成员变量),这样产生的类,叫做派生类(或子类)。作为基础的原有类,称为基类(或父类)

二、定义与使用

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=时需要指定基类作用域。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐