C++学习日记3——友元、运算重载符、继承、多态

目录

一、友元

1.1 通俗解释

1.2 编程解释

1.3 友元的关键字

1.4 友元的三种实现

1.4.1 全局函数做友元

1.4.2 类做友元

1.4.3 成员函数做友元

二、运算重载符

2.1 概念

2.2 加号运算符重载

2.2.1 作用

2.2.2 代码1:成员函数重载+号

2.2.3 代码2:全局函数重载+号、运算重载符函数重载

2.3 左移运算符重载

2.3.1 代码

2.4 递增运算符重载

2.4.1 作用

2.5 赋值运算符重载

2.5.1 默认构造函数(无参,函数体为空)

2.5.2 默认析构函数(无参,函数体为空)

2.5.3 默认拷贝构造函数,对属性进行值拷贝

2.5.4 赋值运算符operator=,对属性进行值拷贝

2.5.5 代码

2.6 关系运算符重载

2.6.1 作用

2.6.2 代码

2.7 函数调用运算符重载

2.7.1 定义

2.7.2 代码

三、继承

3.1 概念

3.2 继承的基本语法

3.2.1 继承的好处

3.2.2 语法

3.2.3 代码

3.3 继承方式

3.4 继承中的对象模型

3.4.1 问题:

3.4.2 代码:

3.5 继承中构造和析构顺序

3.5.1 问题提出

3.5.2 代码

3.6 继承同名成员处理方法

3.6.1 问题:

3.6.2 方法:

3.6.3 代码

3.7 继承同名静态变量处理方法

3.7.1 问题:

3.7.2 方法:

3.7.3 代码:

3.8 多继承语法

3.8.1 注意

3.8.2 语法:

3.8.3 代码:

3.9 菱形继承

3.9.1 概念

3.9.2 典型案例

3.9.3 菱形继承问题

3.9.4 代码1

3.9.5 代码2

四、多态

4.1 多态的基本概念

4.1.1 多态分为两类

4.1.2 静态多态和动态多态区别

4.1.3 多态满足条件

4.1.4 多态使用条件

4.1.5 重写

4.1.6 代码1

4.1.7 代码2

4.2 多态的原理剖析

4.2.1 讲解

 4.2.2 代码

4.3 案例:实现计算器

4.3.1 多态好处

4.3.1 代码1

4.3.2 代码2

4.4 纯虚函数和抽象类

4.4.1 概述

4.4.2 纯虚函数语法:

4.4.3 抽象类特点

4.4.4 代码

4.5 案例:制作饮料

4.5.1 简介

4.5.2 代码

4.6 虚析构和纯虚析构

4.6.1 解决方式:

4.6.2 虚析构和纯虚析构共性:

4.6.3 虚析构和纯虚析构区别:

4.6.4 代码1

4.6.5 纯虚函数

4.6.6 总结

4.7 案例:电脑组装


一、友元

1.1 通俗解释

生活中你的家有客厅(Public),有你的卧室

客厅所有来的客人都可以进去,但是你的卧室是私有的,只有你能进去

但是,你也可以允许你的好朋友进去。

1.2 编程解释

在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术

友元的目的就是让一个函数或者类访问另一个类中私有成员

1.3 友元的关键字

frend

1.4 友元的三种实现

1.4.1 全局函数做友元

1、代码

#include
#include
using namespace std;

class Building
{
	// goodFrend这个全局函数是Building类的好朋友,可以访问Building中的私有成员
	friend void goodFrend(Building* building);

public:
	Building()
	{
		sittingRoom = "客厅";
		bedRoom = "卧室";
	}

public:
	string sittingRoom;

private:
	string bedRoom;
};

void goodFrend(Building* building)
{
	cout << "好朋友这一个全局函数正在访问:" << building->sittingRoom << endl;
	cout << "好朋友这一个全局函数正在访问:" << building->bedRoom << endl;
}

int main(void) 
{
	Building building;
	goodFrend(&building);

	system("pause");
	return 0;
}

1.4.2 类做友元

1、代码

#include
#include
using namespace std;

class Building
{
	// goodFrend这个类是Building类的好朋友,可以访问Building中的私有成员
	friend class goodFrend;

public:
	Building();

public:
	string sittingRoom;

private:
	string bedRoom;
};

// 类外写成员函数
Building::Building()
{
	sittingRoom = "客厅";
	bedRoom = "卧室";
}

class goodFrend
{
public:
	Building* building;
	goodFrend()
	{
		building = new Building;
	}
	void visit();
};

// 类外写成员函数
void goodFrend::visit()
{
	cout << "好朋友类正在访问:" << building->bedRoom << endl;
}

int main(void) 
{
	goodFrend gg;
	gg.visit();

	system("pause");
	return 0;
}

1.4.3 成员函数做友元

1、代码

friend void goodFriend::visit();

二、运算重载符

2.1 概念

对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

2.2 加号运算符重载

2.2.1 作用

实现两个自定义数据类型相加的运算

2.2.2 代码1:成员函数重载+号

#include
using namespace std;

// 加号运算符重载

class Person
{
public:
	// 1、成员函数重载+号
	Person operator+(Person& p)
	{
		Person temp;
		temp.A = this->A + p.A;
		temp.B = this->B + p.B;
		return temp;
	}
	int A;
	int B;
};

void test()
{
	Person p1;
	Person p2;
	p1.A = 10;
	p1.B = 10;
	p2.A = 15;
	p2.B = 20;
	Person p3 = p1 + p2;
    Person p4 = p1.operator+(p2);
    cout << p3.A << endl;
	cout << p4.A << endl;
}


int main(void)
{
    test();
	system("pause");
	return 0;
}

2.2.3 代码2:全局函数重载+号、运算重载符函数重载

#include
using namespace std;

// 加号运算符重载

/* Person operator+(Person& p1, Person& p2)
{
	Person temp;
	temp.A = p1.A + p2.A;
	temp.B = p1.B + p2.B;
	return temp;
}*/

class Person
{
public:
	int A;
	int B;
};

// 2、全局函数重载+号
Person operator+(Person& p1, Person& p2)
{
	Person temp;
	temp.A = p1.A + p2.A;
	temp.B = p1.B + p2.B;
	return temp;
}

// 函数重载的版本
Person operator+(Person& p1, int num)
{
	Person temp;
	temp.A = p1.A + num;
	temp.B = p1.B + num;
	return temp;
}

void test()
{
	Person p1;
	Person p2;
	p1.A = 10;
	p1.B = 10;
	p2.A = 15;
	p2.B = 20;
	Person p3 = p1 + p2;
	Person p4 = operator+(p1, p2);
	Person p5 = p1 + 60;
	cout << p3.A << endl;
	cout << p4.A << endl;
	cout << p5.A << endl;
}


int main(void)
{
	test();
	system("pause");
	return 0;
}

2.3 左移运算符重载

2.3.1 代码

#include
using namespace std;

class Person
{
	friend ostream& operator<<(ostream& cout, Person& p);
public:
	int A;

private:
	int B = 10;
};

// 只能利用全局函数重载 左移运算符
ostream& operator<<(ostream& cout, Person& p)
{
	cout << "A = " << p.A << "\n" << "B = " << p.B;
	return cout;
}

void test()
{
	Person p;
	p.A = 10;
	cout << p << endl;
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

2.4 递增运算符重载

2.4.1 作用

通过重载递增运算符(++),实现自己的整形数据

#include
using namespace std;

class MyInteger
{
	friend ostream& operator<<(ostream& cout, MyInteger& myint);
public:
	MyInteger()
	{
		Num = 0;
	}

	// 前置++运算符
	MyInteger& operator++()
	{
		Num++;
		return *this;
	}

	// 后置++运算符
	MyInteger& operator++(int)  // int代表占位参数,可以用于区分前置和后置递增
	{
		MyInteger temp = *this;
		Num++;
		return temp;
	}

private:
	int Num;
};

// 重载左运算符
ostream& operator<<(ostream& cout, MyInteger& myint)
{
	cout << myint.Num;
	return cout;
}

void test()
{
	MyInteger myint;
	cout << ++myint << endl;
	cout << ++++myint << endl;
	cout << myint++ << endl;
	cout << myint << endl;
}

int main(void)
{
	test();
	system("pause");
	return 0;

}

2.5 赋值运算符重载

C++编译器至少给一个类添加4个函数

2.5.1 默认构造函数(无参,函数体为空)

2.5.2 默认析构函数(无参,函数体为空)

2.5.3 默认拷贝构造函数,对属性进行值拷贝

2.5.4 赋值运算符operator=,对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

2.5.5 代码

#include
using namespace std;

class Person
{
public:
	Person(int age)
	{
		Age = new int(age);
	}

	~Person()
	{
		if (Age != NULL)
		{
			delete Age;
			Age = NULL;
		}
	}

	// 重载赋值运算符
	Person& operator=(Person& p)
	{
		// 先释放掉堆区内存
		if (Age != NULL)
		{
			delete Age;
			Age = NULL;
		}
		Age = new int(*p.Age);
		return *this;
	}

	int* Age;
};

void test()
{
	Person p1(18);
	Person p2(10);
	Person p3(20);
	p3 = p2 = p1;
	cout << *p3.Age << endl;
}

int main(void)
{
	test();
	system("pause");
	return 0;

}

2.6 关系运算符重载

2.6.1 作用

重载关系运算符,可以让两个自定义类型对象进行比对操作

2.6.2 代码

#include
using namespace std;

class Person
{
public:
	Person(string Name,int Age)
	{
		name = Name;
		age = Age;
	}
	bool operator==(Person& p)
	{
		if (this->name == p.name && this->age == p.age)
		{
			return true;
		}
		return false;
	}
	string name;
	int age;
};

void test()
{
	Person p1("Tom", 18);
	Person p2("Jam", 18);
	if (p1 == p2)
	{
		cout << "p1和p2是相等的" << endl;
	}
	else
	{
		cout << "p1和p2是不相等的" << endl;
	}
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

2.7 函数调用运算符重载

2.7.1 定义

函数调用运算符()也可以重载

由于重载后使用的方式非常像函数的调用,因此称为仿函数

仿函数没有固定写法,非常灵活

2.7.2 代码

#include
#include
using namespace std;

// 打印输出类
class MyPrint
{
public:
	void operator()(string test)
	{
		cout << test << endl;
	}

};

void MyPrint02(string test)
{
	cout << test << endl;
}

// 仿函数比较灵活,没有固定的写法
class MyAdd
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

void test()
{
	MyPrint myPrint;
	myPrint("hello world");  // 由于使用起来非常类似于函数调用,因此称为仿函数
	MyPrint02("hello world");

	MyAdd myAdd;
	int ret = myAdd(1, 2);
	cout << ret << endl;

	// 匿名函数对象
	cout << MyAdd()(100, 100) << endl;
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

三、继承

3.1 概念

继承是面向对象三大特性之一

有些类与类之间存在特殊的关系,例如下图中:

C++学习日记3——友元、运算重载符、继承、多态_第1张图片

我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。

这个时候我们就可以考虑利用继承的技术,减少重复代码。

3.2 继承的基本语法

3.2.1 继承的好处

减少重复代码

3.2.2 语法

class 子类 : 继承方式 父类
 

3.2.3 代码

#include
#include
using namespace std;

// 公共页面
class BasePage
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共头部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++...(公共分类列表)" << endl;
	}
};

// Java页面
class Java : public BasePage
{
	void content()
	{
		cout << "Java学科视频" << endl;
	}
};

// Python页面
class Python : public BasePage
{
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};

// CPP页面
class Python : public BasePage
{
	void content()
	{
		cout << "CPP学科视频" << endl;
	}
};

int main(void)
{
	system("pause");
	return 0;
}

3.3 继承方式

3.3.1 继承方式一共有三种:

1、公共继承

2、保护继承

3、私有继承

C++学习日记3——友元、运算重载符、继承、多态_第2张图片

3.4 继承中的对象模型

3.4.1 问题:

从父类继承过来的成员,哪些属于子类对象中?

3.4.2 代码:

#include
#include
using namespace std;

class Base
{
public:
	int A;
protected:
	int B;
private:
	int C;
};

class Son : public Base
{
public:
	int D;
};

void test()
{
	// 父类中所有非静态成员属性都会被子类继承下去
	// 父类中所有私有成员属性 是被编译器给隐藏了,因此是访问不到,
	// 但是确实被继承下来了
	cout << "size of Son = " << sizeof(Son) << endl;
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

C++学习日记3——友元、运算重载符、继承、多态_第3张图片

3.5 继承中构造和析构顺序

3.5.1 问题提出

子类继承父类后,创建子类对象,也会调用父类构造函数

问题:父类和子类的构造和析构顺序是谁先谁后?

3.5.2 代码

#include
#include
using namespace std;

class Base
{
public:
	Base()
	{
		cout << "Base构造函数!" << endl;
	}
	~Base()
	{
		cout << "Base析构函数!" << endl;
	}
};

class Son : public Base
{
public:
	Son()
	{
		cout << "Son构造函数!" << endl;
	}
	~Son()
	{
		cout << "Son析构函数!" << endl;
	}
};

void test()
{
	Son son;
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

C++学习日记3——友元、运算重载符、继承、多态_第4张图片

3.6 继承同名成员处理方法

3.6.1 问题:

当子类与父类出现同名的成员,如何通过子类对象,访问到子类或者父类中同名的数据呢?

3.6.2 方法:

访问子类同名成员,直接访问即可

访问父类同名成员,需要加作用域

3.6.3 代码

#include
#include
using namespace std;

class Base
{
public:
	Base()
	{
		A = 100;
	}
	void func()
	{
		cout << "Base 的 func() 被调用了" << endl;
	}
	void func(int a)
	{
		cout << "Base 的 func(int a) 被调用了" << endl;
	}
	int A;
};

class Son : public Base
{
public:
	Son()
	{
		A = 200;
	}
	void func()
	{
		cout << "Son 的 func() 被调用了" << endl;
	}
	int A;
};

void test()
{
	Son s;
	cout << " Son中的 A = " << s.A << endl;
	cout << "Base中的 A = " << s.Base::A << endl;
	// 子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
	s.Base::func(2);
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

3.7 继承同名静态变量处理方法

3.7.1 问题:

继承中同名的静态成员在子类对象上如何进行访问?

3.7.2 方法:

静态成员和非静态成员出现同名,处理方式一致

访问子类同名成员,直接访问即可

访问父类同名成员,需要加作用域

3.7.3 代码:

#include
#include
using namespace std;

class Base
{
public:
	static int A;
};
int Base::A = 100;

class Son : public Base
{
public:
	static int A;
};
int Son::A = 200;

void test()
{
	Son s;
	// 1、通过对象进行访问
	cout << " Son中的 A = " << s.A << endl;
	cout << "Base中的 A = " << s.Base::A << endl;
	// s::func();
	// s::Base::func();
	
	// 2、通过类名进行访问
	cout << " Son中的 A = " << Son::A << endl;
	// 第一个::代表通过类名方式访问;第二个::代表访问父类作用域下
	cout << "Base中的 A = " << Son::Base::A << endl;
	// 访问函数的方式为
	// Son::func();
	// Son::Base::func();
    // Son::Base::func(100);
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

3.8 多继承语法

3.8.1 注意

C++允许一个类继承多个类

多继承可能会引发父类中有同名成员出现,需要加作用域区分

C++实际开发中不建议用多继承

3.8.2 语法:

class 子类 : 继承方式 父类1, 继承方式 父类2...

3.8.3 代码:

#include
#include
using namespace std;

class Base1
{
public:
	Base1()
	{
		A = 100;
	}
	int A;
};

class Base2
{
public:
	Base2()
	{
		A = 200;
	}
	int A;
};

class Son : public Base1, public Base2
{
public:
	Son()
	{
		C = 300;
		D = 400;
	}
	int C;
	int D;
};

void test()
{
	Son s;
	cout << "Base中的 A = " << s.Base1::A << endl;
	cout << "Base中的 A = " << s.Base2::A << endl;
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

C++学习日记3——友元、运算重载符、继承、多态_第5张图片

C++学习日记3——友元、运算重载符、继承、多态_第6张图片

3.9 菱形继承

3.9.1 概念

两个派生类继承同一个基类

又有某个类同时继承者两个派生类

这种继承被称为菱形继承,或者钻石继承

C++学习日记3——友元、运算重载符、继承、多态_第7张图片

3.9.2 典型案例

C++学习日记3——友元、运算重载符、继承、多态_第8张图片

3.9.3 菱形继承问题

1、羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。

2、草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

3.9.4 代码1

#include
#include
using namespace std;

class Animal
{
public:
	int Age;
};

class Sheep: public Animal { };

class Tuo : public Animal { };

class SheepTuo : public Sheep, public Tuo {	};

void test()
{
	SheepTuo st;
	st.Sheep::Age = 18;
	st.Tuo::Age = 28;
	cout << st.Sheep::Age << endl;
	cout << st.Tuo::Age << endl;
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

C++学习日记3——友元、运算重载符、继承、多态_第9张图片

3.9.5 代码2

#include
#include
using namespace std;

class Animal
{
public:
	int Age;
};

// 继承之前加上关键字 virtual 变为虚继承
// Animal类称为 虚基类
class Sheep: virtual public Animal { };

class Tuo : virtual public Animal { };

class SheepTuo : public Sheep, public Tuo {	};

void test()
{
	SheepTuo st;
	st.Sheep::Age = 18;
	st.Tuo::Age = 28;
	cout << st.Sheep::Age << endl;
	cout << st.Tuo::Age << endl;
	cout << st.Age << endl;
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

C++学习日记3——友元、运算重载符、继承、多态_第10张图片

C++学习日记3——友元、运算重载符、继承、多态_第11张图片

四、多态

4.1 多态的基本概念

多态是C++面向对象三大特性之一

4.1.1 多态分为两类

静态多态:函数重载和运算符重载属于静态多态,复用函数名

动态多态:派生类和虚函数实现运行时多态

4.1.2 静态多态和动态多态区别

静态多态的函数地址早绑定——编译阶段确定函数地址

动态多态的函数地址晚绑定——运行阶段确定函数地址

4.1.3 多态满足条件

有继承关系

子类重写父类中的虚函数

4.1.4 多态使用条件

父类指针或引用指向子类对象

4.1.5 重写

函数返回值类型函数名参数列表完全一致称为重写
 

4.1.6 代码1

#include
#include
using namespace std;

class Animal
{
public:
	void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat : public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

// 执行说话的函数
// 地址早绑定 在编译阶段确定函数地址
void doSpeak(Animal& animal)
{
	animal.speak();
}

void test()
{
	Cat cat;
	doSpeak(cat);
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

4.1.7 代码2

class Animal
{
public:
	// 虚函数
	// 如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat : public Animal
{
public:
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

4.2 多态的原理剖析

4.2.1 讲解

class Animal
{
public:
	void speak()
	{
		cout << "动物在说话" << endl;
	}
};

void test() 
{
    cout << sizeof(Animal) << endl;
}

上面这个类的大小为1,说明这个类没有占用内存,

我们给这个类加上一个 virtual 后变成了4个字节,

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

void test() 
{
    cout << sizeof(Animal) << endl;
}

说明这个函数内存放了一个地址指针了,这个指针指向了一个表,这个表用于存放要执行函数的地址。

C++学习日记3——友元、运算重载符、继承、多态_第12张图片

当其他的类继承了这个动物类的时候,其他的类同时就继承了这个指针。当子类重写父类中的虚函数的方法的时候,就改变了子类中指针指向的位置。

重写前:

C++学习日记3——友元、运算重载符、继承、多态_第13张图片

 重写后:

C++学习日记3——友元、运算重载符、继承、多态_第14张图片

 4.2.2 代码

#include
#include
using namespace std;

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat : public Animal
{
public:
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

void doSpeak(Animal& animal)
{
	animal.speak();
}

void test()
{
	Cat cat;
	cout << sizeof(Animal) << endl;
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

C++学习日记3——友元、运算重载符、继承、多态_第15张图片

 C++学习日记3——友元、运算重载符、继承、多态_第16张图片

 vfptr是一个指针,指向了vftable这个表,表内有Animal::speak的函数地址

猫类没有重写结构图如下 

C++学习日记3——友元、运算重载符、继承、多态_第17张图片

重写了以后结构图如下

C++学习日记3——友元、运算重载符、继承、多态_第18张图片

4.3 案例:实现计算器

4.3.1 多态好处

1、组织结构清晰

2、可读性强

3、对于前期和后期扩展以及维护性高

4.3.1 代码1

#include
#include
using namespace std;

class Calculator
{
public:
	int getResult(string oper)
	{
		if (oper == "+")
		{
			return num1 + num2;
		}
		else if (oper == "-")
		{
			return num1 - num2;
		}
		else if (oper == "*")
		{
			return num1 * num2;
		}
	}
	int num1;
	int num2;
};

void test()
{
	Calculator c;
	c.num1 = 10;
	c.num2 = 10;
	cout << c.num1 << " + " << c.num2 << " = " << c.getResult("+") << endl;
	cout << c.num1 << " - " << c.num2 << " = " << c.getResult("-") << endl;
	cout << c.num1 << " * " << c.num2 << " = " << c.getResult("*") << endl;
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

4.3.2 代码2

#include
#include
using namespace std;

class Calculator
{
public:
	virtual int getResult(string oper)
	{
		return 0;
	}
	int num1;
	int num2;
};

class AddCalculator : public Calculator
{
public:
	int getResult(string oper)
	{
		return num1 + num2;
	}
};

class SubCalculator : public Calculator
{
public:
	int getResult(string oper)
	{
		return num1 - num2;
	}
};

class MulCalculator : public Calculator
{
public:
	int getResult(string oper)
	{
		return num1 * num2;
	}
};

void test()
{
	// 父类指针或者引用指向子类对象
    Calculator* c = new AddCalculator;
	c->num1 = 10;
	c->num2 = 10;
	cout << c->num1 << " + " << c->num2 << " = " << c->getResult("+") << endl;
	// 用完后销毁堆区的数据
    delete c;

    // 让指针重新指向一个新的子类对象
	c = new SubCalculator;
	c->num1 = 10;
	c->num2 = 10;
	cout << c->num1 << " - " << c->num2 << " = " << c->getResult("-") << endl;
	delete c;

	c = new MulCalculator;
	c->num1 = 10;
	c->num2 = 10;
	cout << c->num1 << " * " << c->num2 << " = " << c->getResult("*") << endl;
	delete c;
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

4.4 纯虚函数和抽象类

4.4.1 概述

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。

因此可以将虚函数改为纯虚函数

当类中有了纯虚函数,这个类也被称为抽象类

4.4.2 纯虚函数语法:

virtual 返回值类型 函数名 (参数列表) = 0;

4.4.3 抽象类特点

无法实例化对象

子类必须重写抽象类中的纯虚函数,否则也属于抽象类

4.4.4 代码

#include
#include
using namespace std;

class Base
{
public:
	virtual void func() = 0;
	
};

class Son : public Base
{
public:
	virtual void func() {};
};

void test()
{
	Son son;

	Base* base = new Son;
	base->func();
}

int main(void)
{
	test();
	system("pause");
	return 0;
}

4.5 案例:制作饮料

4.5.1 简介

制作饮品的大致流程为:煮水—冲泡—倒入杯中—加入辅料

C++学习日记3——友元、运算重载符、继承、多态_第19张图片

4.5.2 代码

#include
#include
using namespace std;

class AbstractDrinking
{
public:
	// 煮水
	virtual void Boil() = 0;

	// 冲泡
	virtual void Brew() = 0;

	// 倒入杯中
	virtual void PourInCup() = 0;

	// 加入辅料
	virtual void PutSomething() = 0;

	// 制作饮品
	void makeDrink()
	{
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

// 制作咖啡
class Coffee : public AbstractDrinking
{
public:
	// 煮水
	virtual void Boil()
	{
		cout << "煮农夫山泉" << endl;
	}

	// 冲泡
	virtual void Brew() 
	{
		cout << "冲泡咖啡" << endl;
	}

	// 倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}

	// 加入辅料
	virtual void PutSomething()
	{
		cout << "加入糖和牛奶" << endl;
	}
};

// 制作茶叶
class Tea : public AbstractDrinking
{
public:
	// 煮水
	virtual void Boil()
	{
		cout << "煮矿泉水" << endl;
	}

	// 冲泡
	virtual void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}

	// 倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入玻璃杯中" << endl;
	}

	// 加入辅料
	virtual void PutSomething()
	{
		cout << "加入柠檬" << endl;
	}
};


void doWork(AbstractDrinking *abs)
{
	abs->makeDrink(); 
	delete abs;
}

int main(void)
{
	doWork(new Tea);
	system("pause");
	return 0;
}

C++学习日记3——友元、运算重载符、继承、多态_第20张图片

4.6 虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

4.6.1 解决方式:

将父类中的析构函数改为虚析构或者纯虚析构

4.6.2 虚析构和纯虚析构共性:

可以解决父类指针释放子类对象

都需要有具体的函数实现

4.6.3 虚析构和纯虚析构区别:

如果是纯虚析构,该类属于抽象类,无法实例化对象

4.6.4 代码1

#include
#include
using namespace std;

class Animal
{
public:
	Animal() 
	{
		cout << "Animal构造函数调用" << endl;
	}

	~Animal()
	{
		cout << "Animal构造函数调用" << endl;
	}

	virtual void Speak() = 0;
};

class Cat : public Animal
{
public:
	Cat(string name)
	{
		cout << "Cat构造函数调用" << endl;
		Name = new string(name);
	}

	virtual void Speak() 
	{
		cout << *Name << "小猫在说话" << endl;
	}
	
	~Cat()
	{
		if (Name != NULL) 
		{
			cout << "Cat析构函数调用" << endl;
			delete Name;
			Name = NULL;
		}
	}

	string *Name;
};

void test()
{
	Animal *animal = new Cat("Tom");
	animal->Speak();
	delete animal;
}

int main()
{
	test();
	system("pause");
	return 0;
}

存在的问题:没有执行 Cat 的析构函数

C++学习日记3——友元、运算重载符、继承、多态_第21张图片

只要把父类中的析构函数变成虚函数:

class Animal
{
public:
	Animal() 
	{
		cout << "Animal构造函数调用" << endl;
	}

    // 利用虚析构可以解决父指针释放子类对象时不干净的问题
	virtual ~Animal()
	{
		cout << "Animal构造函数调用" << endl;
	}

	virtual void Speak() = 0;
};

 C++学习日记3——友元、运算重载符、继承、多态_第22张图片

4.6.5 纯虚函数

有了纯虚析构之后,这个类也属于抽象类,无法实例化对象

#include
#include
using namespace std;

class Animal
{
public:
	Animal() 
	{
		cout << "Animal构造函数调用" << endl;
	}

	virtual ~Animal() = 0;

	virtual void Speak() = 0;
};

Animal::~Animal()
{
	cout << "Animal纯虚析构函数调用" << endl;
}

class Cat : public Animal
{
public:
	Cat(string name)
	{
		cout << "Cat构造函数调用" << endl;
		Name = new string(name);
	}

	virtual void Speak() 
	{
		cout << *Name << "小猫在说话" << endl;
	}
	
	~Cat()
	{
		if (Name != NULL) 
		{
			cout << "Cat析构函数调用" << endl;
			delete Name;
			Name = NULL;
		}
	}

	string *Name;
};

void test()
{
	Animal *animal = new Cat("Tom");
	animal->Speak();
	delete animal;
}

int main()
{
	test();
	system("pause");
	return 0;
}

4.6.6 总结

1、虚析构和纯虚析构共性:

可以解决父类指针释放子类对象

都需要有具体的函数实现

2、虚析构和纯虚析构区别:

如果是纯虚析构,该类属于抽象类,无法实例化对象

3、

(1) 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

(2) 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

(3) 拥有纯虚析构函数的类也属于抽象类

4.7 案例:电脑组装

案例描述:

电脑主要组成部件为CPU (用于计算),显卡(用于显示),内存条(用于存储)

将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如 Intel 厂商 和 AMD 厂商创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

#include
#include
using namespace std;

// 抽象不同零件类
// 抽象CPU类
class CPU
{
public:
	// 抽象的计算函数
	virtual void calculate() = 0;
};


// 抽象GPU类
class GPU
{
public:
	// 抽象的显示函数
	virtual void display() = 0;
};

// 抽象CPU类
class Memory
{
public:
	// 抽象的存储函数
	virtual void storage() = 0;
};

// 组装的电脑
class Computer
{
public:
	Computer(CPU *CPU, GPU *GPU, Memory *MEM) 
	{
		cpu = CPU;
		gpu = GPU;
		memory = MEM;
	}

	// 提供工作的函数
	void work() 
	{
		cpu->calculate();
		gpu->display();
		memory->storage();
	}

	// 提供析构函数 释放3个电脑零件
	~Computer()
	{
		if (cpu != NULL)
		{
			delete cpu;
			cpu = NULL;
		}

		if (gpu != NULL)
		{
			delete gpu;
			gpu = NULL;
		}

		if (memory != NULL)
		{
			delete memory;
			memory = NULL;
		}
	}

private:
	CPU *cpu;  // CPU零件的指针
	GPU *gpu;  // GPU零件的指针
	Memory *memory;  // 内存条零件的指针
};

// 具体厂商
// Intel CPU
class IntelCPU : public CPU
{
public:
	virtual void calculate()
	{
		cout << "Intel的CPU开始运算了" << endl;
	}
};

// Intel CPU
class AMDCPU : public CPU
{
public:
	virtual void calculate()
	{
		cout << "AMD的CPU开始运算了" << endl;
	}
};

// AMD GPU
class AMDGPU : public GPU
{
public:
	virtual void display()
	{
		cout << "AMD的GPU开始显示了" << endl;
	}
};

// AMD GPU
class NVIDIAGPU : public GPU
{
public:
	virtual void display()
	{
		cout << "NVIDIA的GPU开始显示了" << endl;
	}
};

// samsung 内存条
class SamsungMemory : public Memory
{
public:
	virtual void storage()
	{
		cout << "Samsung的Memory开始存储了" << endl;
	}
};


void test()
{
	CPU *intelCPU = new IntelCPU;
	GPU *amdGPU = new AMDGPU;
	Memory *samsungMemory = new SamsungMemory;
	Computer *computer1 = new Computer(intelCPU, amdGPU, samsungMemory);
	computer1->work();
	delete computer1;

	Computer* computer2 = new Computer(new AMDCPU, new NVIDIAGPU, new SamsungMemory);
	computer2->work();
	delete computer1;
}

int main()
{
	test();
	system("pause");
	return 0;
}

你可能感兴趣的:(C/C++,c++,开发语言,后端)