【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;
}

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

相关推荐
jrrz08289 分钟前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
咖啡里的茶i25 分钟前
Vehicle友元Date多态Sedan和Truck
c++
海绵波波10731 分钟前
Webserver(4.9)本地套接字的通信
c++
黑叶白树34 分钟前
简单的签到程序 python笔记
笔记·python
@小博的博客37 分钟前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
幸运超级加倍~1 小时前
软件设计师-上午题-15 计算机网络(5分)
笔记·计算机网络
南宫生1 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
爱吃喵的鲤鱼2 小时前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++
懒惰才能让科技进步2 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
7年老菜鸡2 小时前
策略模式(C++)三分钟读懂
c++·qt·策略模式