C++学习日记 - 多态、纯虚函数和抽象类、虚析构和纯虚析构、文件操作

2021/7/11 14:06:31

本文主要是介绍C++学习日记 - 多态、纯虚函数和抽象类、虚析构和纯虚析构、文件操作,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、多态的基本概念

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

多态分为两类:

静态多态:函数重载和运算符重载

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

函数前面加virtual,这个函数就被称为虚函数

静态多态和动态多态有什么区别?

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

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

#include <iostream>
using namespace std;

//多态
//设计动物类
class Animal{
public:
	//在函数头前加virtual	
	//虚函数
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

//设计猫类
class Cat:public Animal{
public:
	//子类必须重写父类的虚函数  函数名,参数列表完全相同。
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

//设计狗类
class Dog:public Animal{
public:
	//子类必须重写父类的虚函数  函数名,参数列表完全相同。
	virtual void speak()
	{
		cout << "小狗在说话" << endl;
	}
};

//执行说话的函数
//因为地址早绑定,在编译阶段已经确定了地址。

void dospeak(Animal &animal)  //这个animal有时候是代表小猫,有时候代表是小狗。
{
	//1. 已经早绑定了,所以在编译时,就已经确定好,这个speak就是animal里面的speak,所以无论传递什么东西过来,都是调用animal的speak()。
	//animal.speak();

	//2. 如果想让小猫或者小狗说话,则不要在编译阶段绑定死animal和speak,而是要等到运行时,传递什么,就调用什么的speak();
	//如何告诉编译器要晚绑定呢?  
	//		在函数头前面添加一个关键词,叫virtual
	//加了virtual之后,有什么变化吗?
	//      animal就不会在编译阶段与speak()绑定在一起,而是在调用时候,才会根据传入的东西来绑定。
	//      这时候,这个speak函数就是多种形态。 
	animal.speak();
}

int main()
{
	Cat c;  //实例化一个具体的猫
	dospeak(c);

	Dog d;   //实例化一个具体的狗
	dospeak(d);

	return 0;
}

总结:
动态多态满足条件:
1、继承关系。
2、子类必须重写父类的虚函数。

动态多态使用:
父类指针或者引用指向子类的对象

   Animal &animal = c;
    父类的引用   子类的对象

二、多态案例一  ---  计算机类。
分别使用普通写法和多态技术,设计实现两个操作数进行运算的计算器类。

#include <iostream>
using namespace std;

//普通写法。
/*
class Calculator{
public:
	int getResult(string oper)
	{
		if(oper == "+")
		{
			return m_Num1 + m_Num2; 
		}
		else if(oper == "-")
		{
			return m_Num1 - m_Num2;
		}
		else if(oper == "*")
		{
			return m_Num1 * m_Num2;
		}
		//如果想扩展新的功能,需要修改源码。
		//在真实开发中,提倡开闭原则。
		//开闭原则: 对扩展进行开放,对修改进行关闭。
	}

	int m_Num1;  //操作数1
	int m_Num2;  //操作数2
};

void test01()
{
	Calculator c;  //通过计算机类,实例化一个具体的计算器。
	c.m_Num1 = 10;
	c.m_Num2 = 10;

	cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl; 
	cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
	cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
*/

//多态写法。
//多态的好处:
//1. 组织结构清晰
//2. 可读性强
//3. 对于前期后期维护非常方便

//总结:C++中提倡大家使用多态技术,因为多态优点非常多。 

//设计计算器基类。
class Calculator{
public:
	//虚函数
	virtual int getResult()
	{

	}

	int m_Num1;
	int m_Num2;
};

//设计加法类。
class AddCalculator:public Calculator{
public:
	//子类重写父类虚函数
	virtual int getResult()
	{
		return m_Num1 + m_Num2;
	}
};

//设计减法类。
class SubCalculator:public Calculator{
public:
	//子类重写父类虚函数
	virtual int getResult()
	{
		return m_Num1 - m_Num2;
	}
};

//设计乘法类。
class MulCalculator:public Calculator{
public:
	//子类重写父类虚函数
	virtual int getResult()
	{
		return m_Num1 * m_Num2;
	}
};

void test02()
{	
	//多态使用条件:父类指针/引用指向子类的对象。
	Calculator *c = new AddCalculator;
	c->m_Num1 = 10;
	c->m_Num2 = 10;

	cout << c->m_Num1 << " + " << c->m_Num2 << " = " << c->getResult() << endl;
	delete c;

	c = new SubCalculator;
	c->m_Num1 = 10;
	c->m_Num2 = 10;

	cout << c->m_Num1 << " - " << c->m_Num2 << " = " << c->getResult() << endl;
	delete c;

	c = new MulCalculator;
	c->m_Num1 = 10;
	c->m_Num2 = 10;

	cout << c->m_Num1 << " * " << c->m_Num2 << " = " << c->getResult() << endl;
	delete c;
}

int main()
{
	//普通写法。
	//test01();
	
	//多态写法。
	test02();

	return 0;
}

三、纯虚函数和抽象类。
在多态中,通常父类的虚函数的实现是毫无意义的,主要是调用子类重写的内容。
因此,我们可以将父类的虚函数写成纯虚函数。
纯虚函数语法: virtual 返回值类型 函数名(参数列表) = 0;
当类中有了纯虚函数,这个类称之为抽象类。

    //抽象类特点:
    //1. 无法实例化对象。
    //2. 抽象类的子类必须重写父类中的纯虚函数,否则也属于抽象类。

#include <iostream>
using namespace std;

//纯虚函数和抽象类
class Base{
public:
	//父类虚函数的实现是毫无意义的,可以修改为纯虚函数。
	//纯虚函数语法:virtual 返回值类型 函数名(参数列表) = 0;
	virtual void func() = 0;   // 纯虚函数

	//只要类中有一个纯虚函数,那么这个类称之为抽象类。
	//抽象类特点:
	//1. 无法实例化对象。
	//2. 抽象类的子类必须重写父类中的纯虚函数,否则也属于抽象类。
};

class Son:public Base{
public:
	//由于子类没有重写父类的纯虚函数,那么子类也属于抽象类。
	virtual void func()
	{
		cout << "Son func的调用" << endl;
	}
};

int main()
{
	/*
	Son s;
	Base &b = s;
	b.func();
	*/

	//1. 抽象类无法示例化对象。
	//Base b;
	//new Base;
	
	//2. 子类中没有重写父类的纯虚函数,那么也是属于抽象类。
	Son s;
	//new Son;

	return 0;
}

四、多态案例二。  --  制作饮品。
案例描述:
制作饮品的大致流程为: 煮水  -- 冲泡 -- 倒入杯中 -- 加入辅料
利用多态的技术实现本案例,提供抽象制作饮品类,提供子类制作咖啡和茶。

例如:
冲咖啡:
1. 煮农夫山泉
2、冲泡咖啡
3、倒入玻璃杯中
4、加入糖和牛奶

冲茶
1、煮矿泉水
2、冲泡茶叶
3、倒入小茶杯中
4、加入柠檬

#include <iostream>
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)   //  AbstractDrinking *abs = new Coffee
{
	abs->makeDrink();
	delete abs;
}

int main()
{
	doWork(new Coffee);

	cout << "-------------------" << endl;

	doWork(new Tea);

	return 0;
}

五、虚析构和纯虚析构。
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。
解决方式: 将父类中析构函数修改为虚析构或者纯虚析构。

虚析构和纯虚析构共性:
1)可以解决父类指针释放子类对象。
2)都需要有具体的函数实现。

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

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

}

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

}
 

#include <iostream>
using namespace std;

//虚析构和纯虚析构
class Animal{
public:
	//构造函数
	Animal()
	{
		cout << "Animal的构造函数的调用" << endl;
	}

	//纯虚函数
	//virtual void speak() = 0;

	//析构函数  
	//解决方式: 将父类的析构函数修改为虚析构或者纯虚析构。
	/*virtual ~类名()
	{

	}
	*/
	
	virtual ~Animal()   //虚析构   -> 那么Animal类就是一个普通类,可以实例化对象。
	{
		cout << "Animal的析构函数的调用" << endl;
	}
	

	/*纯虚析构
	virtual ~类名() = 0;
	类名::~类名()
	{

	}
	*/

	//virtual ~Animal() = 0;  //纯虚析构  -> 那么Animal类就是一个抽象类,无法实例化对象。
};

/*
Animal::~Animal()
{
	cout << "Animal的析构函数的调用" << endl;
}
*/

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

	//virtual void speak()
	//{
		//cout << *m_Name << "小猫在说话" << endl;
	//}

	~Cat()
	{
		if(m_Name != NULL)
		{
			cout << "Cat的析构函数的调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}

	string *m_Name;
};

int main()
{
	//Animal a;  //因为Animal类有纯虚析构,所以是抽象类,无法实例化对象,这句话就会报错。
	//new Animal;
	
	Animal a;

	//父类指针   指向子类对象
	Animal *animal = new Cat("Tom");
	//animal->speak();
	delete animal;

	return 0;
}

总结:
记住,如果子类中有属性开辟到堆区,记得把父类的析构函数修改为虚析构或者是纯虚析构。

总结:
       虚函数           纯虚函数          虚析构         纯虚析构
     不是抽象类         是抽象类        不是抽象类       是抽象类
     可以实例化对象   不可以实例化对象 可以实例化对象   不可以实例化对象

六、文件操作。
1、为什么要学习文件操作?
程序运行时产生的数据属于临时数据,程序一旦运行结束都会释放了。
通过文件可以将数据持久化。
C++对文件操作需要包含头文件#include <fstream>

2、文件类型。
一共两种。
1)文本文件。
文件以文本的ASCII码形式存储在计算机中。

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

3、操作文件。
1)ofstream  -> 写操作
2)ifstream  -> 读操作
3)fstream   -> 读写操作

七、文本文件。  --  写文件。
写文件的步骤:
1、包含头文件。
   #include <fstream>

2、创建流对象。
   ofstream ofs;   //实例化一个写操作类的对象

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

4、写入数据。
   ofs << "写入的数据";

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

八、打开方式有哪些?
ios::in    为读文件而打开文件。
ios::out   为写文件而打开文件。
ios::ate   初始化位置:文件末尾
ios::app   以追加方式写文件
ios::trunc 如果文件存在先删除,再创建
ios::binary二进制方式

注意:文件打开方式可以配合使用的,利用"|"操作符。
例如:用二进制方式写文件
      ios::binary | ios::out
 

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

//文本文件的写操作

int main()
{
	//1. 包含头文件。
	//2. 创建流对象。
	ofstream ofs;

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

	//4. 写数据
	ofs << "姓名:关国源" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;

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

	return 0;
}

结果:
在当前的路径下创建一个新文件,叫example.txt
里面的内容:
姓名:关国源
性别:男
年龄:18
 

九、文本文件。  --  读文件。
读文件与写文件步骤非常相似,但是读取的方式相对于写而言,就多很多。
读文件步骤如下:
1、包含头文件。
   #include <fstream>

2、创建流对象。
   ifstream ifs;

3、打开文件并指定打开方式。
   ifs.open("文件路径",打开方式);

4、读取数据
   四种方式读取数据。

5、关闭文件。
   ifs.close();
 

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

//文本文件读操作

int main()
{
	//1. 包含头文件。
	//2. 创建流对象。
	ifstream ifs;

	//3. 打开文件并指定打开方式。
	ifs.open("example1.txt",ios::in);
	if( !ifs.is_open() )
	{
		cout << "文件打开失败!" << endl;
		return -1;
	}

	//4. 读取数据。(四种方式)
	/* 第一种 
	char buf[1024] = {0};
	while( ifs >> buf )
	{
		cout << buf << endl;
	}
	*/

	/* 第二种 
	char buf[1024] = {0};
	while( ifs.getline(buf,sizeof(buf)) )
	{
		cout << buf << endl;
	}
	*/

	/* 第三种 
	string buf;
	while( getline(ifs,buf) )
	{
		cout << buf << endl;
	}
	*/

	/* 第四种   按照字符来读取
	char c;
	while(   (c = ifs.get()) != EOF )   //EOF: end of file   只要没有读取到文件的末尾,就一直读取。
	{ 
		cout << c;
	}
	*/

	ifs.close();

	return 0;
}

十、二进制文件。 -- 写操作。
以二进制方式来对文件进行读写操作,记住打开方式要指定: ios::binary

二进制方式写文件主要利用流对象调用成员函数write
函数原型: ostream& write(const char *buffer,int len);

参数:
buffer: 字符指针,指向一段内存中合法空间。
len: 字节数

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

//二进制文件写文件。
class Person{
public:
	char m_Name[64];  //姓名
	int m_Age;        //年龄
};

int main()
{	
	//1. 包含头文件。
	//2. 创建流对象。
	//ofstream ofs;

	//3. 打开文件,二进制方式打开。
	//ofs.open("person.txt",ios::out | ios::binary);
	ofstream ofs("person.txt",ios::out | ios::binary);

	//4. 写数据。
	Person p = {"张三",18};
	ofs.write( (const char *)&p , sizeof(Person) );

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

	return 0;
}

十一、二进制文件。 --  读文件。
二进制方式读取文件主要利用流对象调用成员函数read
函数原型: istream& read(char *buffer,int len);

参数:
buffer: 字符指针,指向内存中一段合法的空间。
len: 读取的字节数。

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

//二进制文件写文件。
class Person{
public:
	char m_Name[64];  //姓名
	int m_Age;        //年龄
};


int main()
{
	//1. 包含头文件
	//2. 创建流对象。
	ifstream ifs;

	//3. 打开文件
	ifs.open("person.txt",ios::in | ios::binary);
	if( !ifs.is_open() )
	{
		cout << "文件打开失败" << endl;
		return -1;
	}

	//4. 读文件。
	Person p;
	ifs.read((char *)&p,sizeof(Person));

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

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

	return 0;
}



这篇关于C++学习日记 - 多态、纯虚函数和抽象类、虚析构和纯虚析构、文件操作的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程