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;
}
标签:文件,函数,ios,多态,C++,抽象类,include,虚析构 来源: https://blog.csdn.net/weixin_41558261/article/details/117250659