day08 多态

目录

多态

多态的基本概念

多态案例一-计算器类

纯虚函数和抽象类

多态案例二-制作饮品

虚析构和纯虚析构

多态案例三-电脑组装

文件操作

文本文件

写文件

读文件

写二进制文件

读文件二进制


多态

多态的基本概念

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

 简单理解和cin>>有关,因为多个子类继承父类函数后,每个子类进行什么操作是不同的,那么他应该怎么调用函数呢?

多态的概念及作用(理解)_danieldingyi的博客-CSDN博客_多态的概念        多态是面向对象的重要特性,简单说:“一个接口,多种实现”,就是同一种事物表现出的多种形态。编程就是一个将具体事务抽象化的过程,多态就是抽象化的一种体现,把一系列具体事务的共同点抽象出来,再通过这个抽象的事物,与不同的具体事物进行对话。        例:对不同对象发出相同的消息将会有不同的行为。比如说领导说九点钟开始工作,不同部门岗位的员工做不同的工作。多态的作用:1. 应用程序不...https://blog.csdn.net/danieldingyi/article/details/79922775?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165551425816782248575865%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165551425816782248575865&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-79922775-null-null.142%5Ev17%5Epc_search_result_control_group,157%5Ev15%5Enew_3&utm_term=%E5%A4%9A%E6%80%81%E7%9A%84%E6%84%9F%E5%BF%B5&spm=1018.2226.3001.4187

 多态的概念和意义_勇士后卫头盔哥的博客-CSDN博客在之前同名覆盖引发问题的那一篇文章,我们说过了编译器在同名覆盖中所作的行为并不是我们期望的行为,面向对象中期望的行为是根据实际的对象类型判断如何调用重写函数,期望做到父类指针(引用)指向父类对象则调用父类中定义的函数,指向子类对象则调用子类中定义的重写函数,不要根据指针类型去调用,要根据指针指向的对象类型去调用,现在我们引入多态的概念要实现多态,就需要用到virtual关键字对多态进行支持,被...https://blog.csdn.net/qq_41936794/article/details/104866138?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165551425816782248575865%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165551425816782248575865&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-104866138-null-null.142%5Ev17%5Epc_search_result_control_group,157%5Ev15%5Enew_3&utm_term=%E5%A4%9A%E6%80%81%E7%9A%84%E6%84%9F%E5%BF%B5&spm=1018.2226.3001.4187

多态分为两类

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

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

静态多态和动态多态区别:

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

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

  •  这里重点记一下,在函数中如何调用父类中函数的,

  • 注意一下,两个子类继承一个父类

 

 给父类创建一个调用函数,再来个两个子类创建调用,依次按照顺序。

 这样想要调用父类,直接调用即可,想调用继承父类的子类,只要通过父类去调用子类就可以了

class Animal
{
public:
	//Speak函数就是虚函数
	//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

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

class Dog :public Animal
{
public:

	void speak()
	{
		cout << "小狗在说话" << endl;
	}

};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编

void DoSpeak(Animal & animal)
{
	animal.speak();
}
//
//多态满足条件: 
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象

void test01()
{
	Cat cat;
	DoSpeak(cat);


	Dog dog;
	DoSpeak(dog);
}


int main() {

	test01();

	system("pause");

	return 0;
}

总结:

多态满足条件

  • 有继承关系

  • 子类重写父类中的虚函数

多态使用条件

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

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

多态案例一-计算器类

案例描述:

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:

  • 代码组织结构清晰

  • 可读性强

  • 利于前期和后期的扩展以及维护

传统计算器

class calculator
{
public:
	int get_result(string oper)
	{
		if (oper == "+")
		{
			return n_num1 + n_num2;
		}
		else if (oper == "-")
		{
			return n_num1 - n_num2;
		}
		else if (oper == "*")
		{
			return n_num1 * n_num2;
		}
		//如果要提供新的运算,需要修改源码
		//比如加一个相除什么的
		//但是利用多太,就可以在原来基础上,不断地写屎一样的代码来解决问题
	}

	int n_num1;
	int n_num2;
};
void test01()
{
//创建一个计算器的对象
	calculator c;
	c.n_num1 = 10;
	c.n_num2 = 20;
	cout << c.n_num1 << "+" << c.n_num2 << "=" << c.get_result("+") << endl;
	cout << c.n_num1 << "-" << c.n_num2 << "=" << c.get_result("-") << endl;
	cout << c.n_num1 << "*" << c.n_num2 << "=" << c.get_result("*") << endl;

}

 多态写法下的计算器,添加新功能,直接引用,不用加改动源码

//实现计算器的抽象类
class abstract_calculator
{
public:
	virtual int get_result()
	{
		return 0;
	}
	int m_num1;
	int m_num2;
};
class add_calculator :public abstract_calculator 
{
public:
	virtual int get_result()
	{
		return m_num1+m_num2;
	}
};
class sub_calculator :public abstract_calculator
{
public:
	virtual int get_result()
	{
		return m_num1 - m_num2;
	}
};
void test02()
{
	//多态使用条件
	//父类指针或引用指向子类对象

	//加法计算
	abstract_calculator* abc = new add_calculator;
	abc->m_num1 = 10;
	abc->m_num2 = 20;

	cout << abc->m_num1 << "+" << abc->m_num2 << "=" << abc->get_result() << endl;
//用完释放堆区数据
	delete abc;

	abc = new sub_calculator;
	abc->m_num1 = 30;
	abc->m_num2 = 20;
	cout << abc->m_num1 << "-" << abc->m_num2 << "=" << abc->get_result() << endl;
	delete abc;

}

 结构清晰,,,读写简单。

纯虚函数和抽象类

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

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

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;

 纯虚函数

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

抽象类特点

  • 无法实例化对象

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

class Base
{
public:
	//纯虚函数
	//类中只要有一个纯虚函数就称为抽象类
	//抽象类无法实例化对象
	//子类必须重写父类中的纯虚函数,否则也属于抽象类
	virtual void func() = 0;
};

class Son :public Base
{
public:
	virtual void func() 
	{
		cout << "func调用" << endl;
	};
};

void test01()
{
	Base * base = NULL;
	//base = new Base; // 错误,抽象类无法实例化对象
	base = new Son;
	base->func();
	delete base;//记得销毁
}

int main() {

	test01();

	system("pause");

	return 0;
}

多态案例二-制作饮品

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

假设两个饮品,一个咖啡,一个奶茶,;两者区别是什么?一个是原料,一个是加的辅料

 父类,一致的都是纯虚函数调用

 制作茶的直接赋值即可

 

 这里要注意一下,因为用了指针,所以一定要delete释放掉。

#include <iostream>
#include<string>
using namespace std;

class abstract_drinking
{
public:

	//煮水  纯虚函数
	virtual void boil() = 0;
	// 冲泡 
	virtual void brew() = 0;
	//倒入杯中
	virtual void pour_in_cup() = 0;

	//加入辅料
	virtual void put_some() = 0;
	//制作饮品
    void  make_drink()
	{
		boil();
		brew();
		pour_in_cup();
		put_some();
	}
};
//制作咖啡(直接复制)
class tea :public abstract_drinking 
{
	//煮水  纯虚函数在子类声明
	virtual void boil()
	{
		cout << "将来自昆仑山脉的神圣山泉加热至100度" << endl;
	}
	// 冲泡 
	virtual void brew()
	{
		cout << "将100度神圣山泉浸泡茶叶" << endl;
	}
	//倒入杯中
	virtual void pour_in_cup()
	{
		cout << "将热茶倒入德玛西亚大师制作的神社白骨瓷杯中" << endl;
	}

	//加入辅料
	virtual void put_some()
	{
		cout << "在杯中加上上海小赤果" << endl;
	}
	//制作饮品
};
//制作咖啡
class coffee :public abstract_drinking
{
	//煮水  纯虚函数在子类声明
	virtual void boil()
	{
		cout << "将来自昆仑山脉的神圣山泉加热至100度" << endl;
	}
	// 冲泡 
	virtual void brew()
	{
		cout << "将100度神圣山泉浸泡咖啡" << endl;
	}
	//倒入杯中
	virtual void pour_in_cup()
	{
		cout << "将咖啡倒入德玛西亚大师制作的神社白骨瓷杯中" << endl;
	}

	//加入辅料
	virtual void put_some()
	{
		cout << "在杯中加上万物之母的乳汁" << endl;
	}
	//制作饮品
};
void work(abstract_drinking* abs)
{
	abs->make_drink();
	//在指针处delete,防止内存泄露
	delete abs;
}
void test01()
{
	//制作咖啡
	work(new coffee);
	
}
void test02()
{
	work(new tea);
}


int main() {
	test01();
	cout << endl;
	cout << endl;
	cout << endl;
	test02();
	system("pause");

	return 0;
}

虚析构和纯虚析构

父类中无法调用子类析构代码,因此当子类有些数据存放在堆区,父类中的析构代码不能是释放子类中的数据。

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

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

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

  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

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

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

类名::~类名(){}

class animal
{
public:
	//纯虚函数    1
	virtual void speak() = 0;

};
class cat :public animal {
public:
	cat(string name)
	{	
		m_name1=new string(name);
	}
	virtual  void speak()
	{
		cout << *m_name1<<"小猫在说话" << endl;
	}

	
	string *m_name1;
};

void test01()
{
	animal* animal = new cat("tom");
	animal->speak();
	delete animal;

}

int main() {

	test01();
	system("pause");

	return 0;
}

 

 正常写好之后,我们让小猫去说话,但是使用的是指针,要是释放堆区的数据。

因此需要析构函数。

但是父类中也有析构函数的话,子类中的析构函数就会走不到哪一步

 因此要使用纯虚析构,也可以用虚析构,但是纯虚析构更好使

#include <iostream>
#include<string>
using namespace std;


class animal
{
public:
	//纯虚函数    1
	virtual void speak() = 0;
	animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}
	/*~animal()
	{
		cout << "Animal虚析构函数调用!" << endl;
	}*/
	// 通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
		//怎么解决?给基类增加一个虚析构函数
		//虚析构函数就是用来解决通过父类指针释放子类对象
	virtual ~animal() = 0;
};

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

class cat :public animal {
public:
	cat(string name)
	{
		cout << "Cat构造函数调用!" << endl;
		m_name1 = new string(name);
	}
	virtual  void speak()
	{
		cout << *m_name1 << "小猫在说话" << endl;
	}
	~cat()
	{
		cout << "Cat析构函数调用!" << endl;
		if (this->m_name1 != NULL)
		{
			
			delete m_name1;
			m_name1 = NULL;
		}
	}
	string* m_name1;
};

void test01()
{
	animal* animal = new cat("tom");
	animal->speak();
	delete animal;

}

int main() {

	test01();
	system("pause");

	return 0;
}

 虚析构函数


	//析构函数加上virtual关键字,变成虚析构函数
	virtual ~Animal()
	{
		cout << "Animal虚析构函数调用!" << endl;
	}

总结:

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

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

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

多态案例三-电脑组装

本来是联系,却变成了折磨自己,形参列表一定要记清楚,实在不行,就写纸上

然后内存地址是固定的,如果想创建下一个工作变量,就必须重新new地址。

#include<iostream>
#include<string>
using namespace std;

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

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

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

//电脑类
class Computer
{
public:
	Computer(CPU* cpu, VideoCard* vc, Memory* mem)
	{
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}

	//提供工作的函数
	void work()
	{
		//让零件工作起来,调用接口
		m_cpu->calculate();

		m_vc->display();

		m_mem->storage();
	}

//提供析构函数,释放三个电脑零件
	~Computer()
	{
		if (m_cpu != NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}
		if (m_vc != NULL)
		{
			m_vc != NULL;
			m_vc = NULL;
		}
		if (m_mem != NULL)
		{
			m_mem != NULL;
			m_mem = NULL;
		}
	}
private:

	CPU* m_cpu; //CPU的零件指针
	VideoCard* m_vc; //显卡零件指针
	Memory* m_mem; //内存条零件指针
};

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

class IntelVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Intel的显卡开始显示了!" << endl;
	}
};

class IntelMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Intel的内存条开始存储了!" << endl;
	}
};

//Lenovo厂商
class LenovoCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Lenovo的CPU开始计算了!" << endl;
	}
};

class LenovoVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Lenovo的显卡开始显示了!" << endl;
	}
};

class LenovoMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Lenovo的内存条开始存储了!" << endl;
	}
};


void test01()
{
	//第一台电脑零件
	CPU* intelCpu = new IntelCPU;
	VideoCard* intelCard = new IntelVideoCard;
	Memory* intelMem = new IntelMemory;

	cout << "第一台电脑开始工作:" << endl;
	//创建第一台电脑
	Computer* computer1 = new Computer(intelCpu, intelCard, intelMem);
	computer1->work();
	delete computer1;

	cout << "第2台电脑开始工作:" << endl;
	//创建第一台电脑
	/*Computer* computer2 = new Computer(intelCpu, VideoCard, intelMem);
	computer2->work();
	delete computer2;          为什么是new啊?因此要重新创建地址?                    */
	Computer* computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);;
	computer2->work();
	delete computer2;

	cout << "-----------------------" << endl;
	cout << "第三台电脑开始工作:" << endl;
	//第三台电脑组装
	Computer* computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);;
	computer3->work();
	delete computer3;


}

int main()
{
	test01();


	system("pause");
	return 0;
}

文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放

通过文件可以将数据持久化

C++中对文件操作需要包含头文件 ==< fstream >==

文件类型分为两种:

  1. 文本文件 - 文件以文本的ASCII码形式存储在计算机中

  2. 二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

操作文件的三大类:

  1. ofstream:写操作

  2. ifstream: 读操作

  3. fstream : 读写操作

文本文件

写文件

写文件步骤如下:

  1. 包含头文件

    #include <fstream>

  2. 创建流对象

    ofstream ofs;

  3. 打开文件

    ofs.open("文件路径",打开方式);

  4. 写数据

    ofs << "写入的数据";

  5. 关闭文件

    ofs.close();

 

注意: 文件打开方式可以配合使用,利用|操作符

例如:用二进制方式写文件 ios::binary | ios:: out

#include <iostream>
using namespace std;
#include<fstream>  //1.头文件包含

//文本文件, 写文件


int main()
{
	//2/创建流对象

	
	ofstream ofs;

	//3.指定打开方式
	ofs.open("test.txt", ios::out);

	//4.写内容
	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:67" << endl;
	ofs << "地址:放假哦爱固化剂哦啊见过" << endl;

	//5.关闭文件
	ofs.close();

	system("pause");
	return 0;
}
  • 文件操作必须包含头文件 fstream

  • 读文件可以利用 ofstream ,或者fstream类

  • 打开文件时候需要指定操作文件的路径,以及打开方式

  • 利用<<可以向文件中写数据

  • 操作完毕,要关闭文件

读文件

读文件与写文件步骤相似,但是读取方式相对于比较多

读文件步骤如下:

  1. 包含头文件

    #include <fstream>

  2. 创建流对象

    ifstream ifs;

  3. 打开文件并判断文件是否打开成功

    ifs.open("文件路径",打开方式);

  4. 读数据

    四种方式读取

  5. 关闭文件

    ifs.close();

#include <iostream>
using namespace std;
#include<string>
#include<fstream>  //1.头文件包含

//文本文件, 写文件

void test01()
{
	//2/创建流对象
	ifstream ifs;
	//3.指定打开方式,b并判断是否打开成功
	ifs.open("test.txt", ios::in);

	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;//停止运行
	}
	//4.读内容

	//第一种方式
	//char buf[1024] = { 0 };
	//这是啥?字符数组?听着解释,把三行数据读到字符数组中,就是那个dos明了窗口
	/*while (ifs >> buf)
	{
		cout << buf << endl;
	}*/
	//第二种方式
	/*char buf[1024] = { 0 };
	while (ifs.getline(buf,sizeof(buf)))
	{
		cout << buf << endl;
	}*/
	
	//第三种
	string buf;//用这种加上string的头文件
	while (getline(ifs,buf))
	{
		cout << buf << endl;
	}

	第四种
	//char c;
	//while ((c = ifs.get())!=EOF)  //EOF是end of file文件尾部的意思
	//{
	//	cout << c;
	//}

	//5.关闭文件
	ifs.close();
}


int main()
{
	test01();

	system("pause");
	return 0;
}

写二进制文件

以二进制的方式对文件进行读写操作

打开方式要指定为 ==ios::binary==

二进制方式写文件主要利用流对象调用成员函数write

函数原型 :ostream& write(const char * buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

#include <fstream>
#include <string>

class Person
{
public:
	char m_Name[64];
	int m_Age;
};

//二进制文件  写文件
void test01()
{
	//1、包含头文件

	//2、创建输出流对象
	ofstream ofs("person.txt", ios::out | ios::binary);
	
	//3、打开文件
	//ofs.open("person.txt", ios::out | ios::binary);

	Person p = {"张三"  , 18};

	//4、写文件
	ofs.write((const char *)&p, sizeof(p));

	//5、关闭文件
	ofs.close();
}

int main() {

	test01();

	system("pause");

	return 0;
}

读文件二进制

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

示例:

#include <fstream>
#include <string>

class Person
{
public:
	char m_Name[64];
	int m_Age;
};

void test01()
{
	ifstream ifs("person.txt", ios::in | ios::binary);
	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
	}

	Person p;
	ifs.read((char *)&p, sizeof(p));

	cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,根据您的要求,我来为您编写代码: ```c++ #include <iostream> #include <string> using namespace std; // 定义Person类作为基类 class Person { protected: string name; // 姓名 int year; // 出生年份 int month; // 出生月份 int day; // 出生日期 public: Person(string n, int y, int m, int d) : name(n), year(y), month(m), day(d) {} virtual void Print() { cout << "姓名:" << name << endl; cout << "出生日期:" << year << "年" << month << "月" << day << "日" << endl; } }; // 定义Teacher类,继承自Person类 class Teacher : public Person { private: string Tno; // 工号 public: Teacher(string n, int y, int m, int d, string t) : Person(n, y, m, d), Tno(t) {} virtual void Print() { Person::Print(); // 调用基类的Print函数,打印姓名和出生日期 cout << "工号:" << Tno << endl; } }; // 定义Student类,继承自Person类 class Student : public Person { private: string Sno; // 学号 public: Student(string n, int y, int m, int d, string s) : Person(n, y, m, d), Sno(s) {} virtual void Print() { Person::Print(); // 调用基类的Print函数,打印姓名和出生日期 cout << "学号:" << Sno << endl; } }; int main() { // 定义基类指针和派生类对象 Person* ptr; Teacher t("张三", 1980, 3, 14, "1001"); Student s("李四", 2000, 9, 1, "20180101"); // 通过基类指针指向派生类对象,实现多态 ptr = &t; ptr->Print(); // 调用Teacher类的Print函数 cout << endl; ptr = &s; ptr->Print(); // 调用Student类的Print函数 cout << endl; return 0; } ``` 希望这段代码能够满足您的要求,如果有任何问题,欢迎继续提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值