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++学习日记 - 多态、纯虚函数和抽象类、虚析构和纯虚析构、文件操作的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-02在 Objective-C 中strong 和 retain有什么区别-icode9专业技术文章分享
- 2024-11-02NSString 中的 hasPrefix 有什么作用-icode9专业技术文章分享
- 2024-11-02在 C 和 Objective-C 中inline的用法是什么-icode9专业技术文章分享
- 2024-11-02文件掩码什么意思?-icode9专业技术文章分享
- 2024-11-02在 Git 提交之前运行 composer cs-fix 命令怎么实现-icode9专业技术文章分享
- 2024-11-02为 Composer 的 cs-fix 命令指定一个目录怎么实现-icode9专业技术文章分享
- 2024-11-02微信公众号开发中怎么获取用户的 unionid-icode9专业技术文章分享
- 2024-11-01lip-sync公司指南:一文读懂主要玩家和技术
- 2024-11-01Anthropic的新RAG方法——提升大型语言模型在特定领域的表现
- 2024-11-01UniApp 中组件的生命周期是多少-icode9专业技术文章分享