【C++学习笔记】继承、多态的示例

继承

点类继承几何类的简单示例

创建了一个几何类Geometry和一个点类PointPoint继承了Geometry

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Geometry {
protected:
	string _wkt;
public:
	void printWkt() {
		cout << _wkt << endl;
	}
};

class Point : public Geometry {
private:
	double _x;
	double _y;
public:
	Point(double x, double y) {
		_x = x;
		_y = y;
		_wkt = "Point(" + to_string(_x) + "," + to_string(_y) + ")";
	}
};

int main()
{
	Point pt(1, 2);
	pt.printWkt();//Point(1.000000,2.000000)

	system("pause");
	return 0;
}

继承方式

继承的语法是:class 子类 : 继承方式 父类

继承方式有三种:

  • 公共继承public·
  • 保护继承protected
  • 私有继承private

父类中的非静态成员属性都被子类继承了,只是成员属性的访问权限不同。

继承中构造和析构顺序

  1. 父类构造
  2. 子类构造
  3. 子类析构
  4. 父类析构

同名非静态成员处理方式

  • 子类同名成员变量,直接访问:son.name
  • 父类同名成员变量,需要加作用域,加父类作用域后访问:son.Father::name
cpp 复制代码
#include <iostream>
using namespace std;

class Father {
public:
	string name;
	Father() {
		name = "Father";
	}
};

class Son : public Father{
public:
	string name;
	Son(string name) {
		this->name = name;
	}
};

int main()
{
	Son son("son");
	cout << son.name << endl;//"son"
	cout << son.Father::name << endl;//"Father"
	system("pause");
	return 0;
}

如果是同名成员函数,也是同样的处理方式,需要加父类作用域。

父类中重载的同名函数,子类调用时也必须加父类作用域(因为父类中同名函数无论是否重载,都被隐藏了)。

同名静态成员处理方式

  • 如果是通过对象访问,则处理方式和同名非静态成员处理方式相同。
  • 如果是通过类名访问,也可以加父类作用域(Son::Father::name)。
cpp 复制代码
#include <iostream>
using namespace std;

class Father {
public:
	static string name;
};
string Father::name = "F";

class Son : public Father{
public:
	static string name;
};
string Son::name = "S";
int main()
{
	Son son;
	cout << son.name << endl;//"S"
	cout << son.Father::name << endl;//"F"

	cout << Son::name << endl;//"S"
	cout << Son::Father::name << endl;//"F"

	system("pause");
	return 0;
}

同名静态成员函数,也是一样的处理方式。

多继承语法

多继承语法示例:class Son : public Father, public Mother

多个父类之间用逗号隔开。

如果父类之间有同名成员,子类调用时需要加父类作用域。

菱形继承

菱形继承问题:B、C都继承了A的name,D多继承了B、C,就会有两个name。

D访问name时,可以加B作用域访问从B继承到的name,也可以加C作用域访问从C继承到的name。但是实际上,D只需要继承一个name就行了。

cpp 复制代码
#include <iostream>
using namespace std;

class A {
public:
	string name;
};
class B : public A {};
class C : public A {};
class D : public B, public C {};

int main()
{
	D d;
	d.B::name = "AB";
	d.C::name = "AC";
	cout << d.B::name << endl;//"AB"
	cout << d.C::name << endl;//"AC"

	system("pause");
	return 0;
}

菱形继承问题解决方法:虚继承,在继承前加上关键字virtual,所继承的基类也就成了虚基类

cpp 复制代码
#include <iostream>
using namespace std;

class A {
public:
	string name;
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};

int main()
{
	D d;
	d.B::name = "AB";
	d.C::name = "AC";
	cout << d.B::name << endl;//"AC"
	cout << d.C::name << endl;//"AC"
	cout << d.name << endl;//"AC"//也能直接访问了
	system("pause");
	return 0;
}

原理上是D从B、C分别继承两个vbptr(虚基类指针)(所以D中会有空间存两个虚基类指针),可以根据这两个指针分别从两个vbtable(虚基类表)中找到偏移值,根据偏移值找到同一个name变量的位置。

多态

基础语法

  1. 有继承关系
  2. 父类有虚函数(用virtual修饰的成员函数)
  3. 子类重写父类的虚函数(子类的函数的virtual修饰可以省略)

多态使用时需要:父类指针或引用指向子类对象

cpp 复制代码
#include <iostream>
using namespace std;

class Animal {
public:
	virtual void speak() {
		cout << "动物" << endl;
	}
};
class Cat : public Animal {
	void speak() {
		cout << "猫" << endl;
	}
};
class Dog : public Animal {
	void speak() {
		cout << "狗" << endl;
	}
};

int main()
{
	Dog dog;
	Cat cat;
	Animal& a1 = dog;
	Animal& a2 = cat;
	a1.speak();//狗
	a2.speak();//猫

	system("pause");
	return 0;
}

如果父类函数不加virtual,那么a1.speak();a2.speak();调用的都是Animalspeak函数。

关于静态多态和动态多态的概念
关于多态的原理

纯虚函数和抽象类

上面虚基类中虚函数通常是无意义的,其方法体可以省略,因为一般都是使用子类中所重写的函数。

纯虚函数格式:virtual 返回值 方法名 (参数列表) = 0;

通过去掉{},加上=0;,将虚函数变成纯虚函数:

cpp 复制代码
class Animal {
public:
	virtual void speak() = 0;
};

只要类中有一个纯虚函数,那么这个类就称为抽象类

抽象类的特点:

  1. 不能被实例化
  2. 抽象类的子类必须重写父类的纯虚函数,否则也是抽象类,也不能被实例化
  3. 子类重写父类(抽象类)的纯虚函数后,就可以被实例化

另外,子类也没法通过增加父类作用域,调用父类(抽象类)的纯虚函数。

虚析构和纯虚析构

问题:

父类指针析构时,无法自动调用子类对象的析构,导致资源没法释放。

cpp 复制代码
#include <iostream>
using namespace std;

class Animal {
public:
	Animal() {
		cout << "Animal构造函数" << endl;
	}
	~Animal() {
		cout << "Animal析构函数" << endl;
	}
};
class Cat : public Animal {
public:
	string* name;
	Cat(string _name) {
		name = new string(_name);
		cout << *name << "的构造函数" << endl;
	}	
	~Cat() {
		cout << *name << "的析构函数" << endl;
		if(name != NULL){
			delete name;
			name = NULL:
		}
	}
};

int main()
{
	Animal* animal = new Cat("汤姆");
	delete animal;
	system("pause");
	return 0;
}

控制台结果:

复制代码
Animal构造函数
汤姆的构造函数
Animal析构函数

解决方法:将父类的析构改为虚析构或纯虚析构。

有了纯虚析构,类也就成了抽象类。

虚析构语法:virtual ~类名(){}

纯虚析构语法:virtual ~类名() = 0;

所以给Animal增加虚析构:

cpp 复制代码
class Animal {
public:
	Animal() {
		cout << "Animal构造函数" << endl;
	}
	virtual ~Animal() {
		cout << "Animal析构函数" << endl;
	}
};

控制台结果:

复制代码
Animal构造函数
汤姆的构造函数
汤姆的析构函数
Animal析构函数

与虚析构不同的是,纯虚析构不仅需要声明,还需要实现(因为父类也可能有堆区要释放):

cpp 复制代码
class Animal {
public:
	Animal() {
		cout << "Animal构造函数" << endl;
	}
	virtual ~Animal() = 0;
};
Animal::~Animal() {
	cout << "Animal析构函数" << endl;
}

如果子类中没有堆区资源要释放,可以不用在父类中提供虚析构或纯虚析构。

相关推荐
摇滚侠6 分钟前
Spring Boot 3零基础教程,WEB 开发 自定义静态资源目录 笔记31
spring boot·笔记·后端·spring
摇滚侠7 分钟前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 遍历 笔记40
spring boot·笔记·thymeleaf
沐怡旸33 分钟前
【穿越Effective C++】条款02:尽量以const, enum, inline替换#define
c++·面试
给大佬递杯卡布奇诺1 小时前
FFmpeg 基本API avcodec_alloc_context3函数内部调用流程分析
c++·ffmpeg·音视频
Jose_lz1 小时前
C#开发学习杂笔(更新中)
开发语言·学习·c#
Chloeis Syntax1 小时前
接10月12日---队列笔记
java·数据结构·笔记·队列
QT 小鲜肉1 小时前
【个人成长笔记】Qt 中 SkipEmptyParts 编译错误解决方案及版本兼容性指南
数据库·c++·笔记·qt·学习·学习方法
A9better1 小时前
嵌入式开发学习日志41——stm32之SPI总线基本结构
stm32·单片机·嵌入式硬件·学习
看到我,请让我去学习1 小时前
Qt 控件 QSS 样式大全(通用属性篇)
开发语言·c++·qt
筱砚.2 小时前
【STL——vector容器】
开发语言·c++