c++学习心得第二期 多态+文件操作
一、多态
1、多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
#include<iostream>
#include<string>
using namespace std;
//因为早绑定,所以地址已经是animal的地址了 想执行小猫说话但结果是动物说话
class Animal{
public:
void speak()
{
cout <<"动物在说话" << endl;
}
};
class Cat :public Animal{
public:
void speak()
{
cout <<"小猫在说话" << endl;
}
};
//执行说话函数
//地址早绑定 在编译阶段确定函数的地址。
void doSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
}
int main()
{
test01();
system("pause");
return 0;
}
#include<iostream>
#include<string>
using namespace std;
class Animal{
//在函数前加 virtual 变成动态绑定。
public:
virtual void speak()
{
cout <<"动物在说话" << endl;
}
};
class Cat :public Animal{
public:
void speak()
{
cout <<"小猫在说话" << endl;
}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
}
int main()
{
test01();
system("pause");
return 0;
}
多态满足条件
有继承关系
子类重写父类中的虚函数
多态使用条件
父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
2、多态案例一-计算器类
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
代码组织结构清晰
可读性强
利于前期和后期的扩展以及维护
#include<iostream>
#include<string>
using namespace std;
class AbstractCalculator{
public :
virtual int result() = 0;
public:
int num1;
int num2;
};
class AddCalculator:public AbstractCalculator{
public:
AddCalculator(int num1,int num2)
{
this->num1 = num1;
this->num2 = num2;
}
int result()
{
return num1 + num2;
}
};
class SubstractorCalculator :public AbstractCalculator{
public:
SubstractorCalculator(int num1, int num2)
{
this->num1 = num1;
this->num2 = num2;
}
int result()
{
return num1 - num2;
}
};
class MultiplyrCalculator :public AbstractCalculator{
public:
MultiplyrCalculator(int num1, int num2)
{
this->num1 = num1;
this->num2 = num2;
}
int result()
{
return num1*num2;
}
};
void test01()
{
AbstractCalculator *abc = new AddCalculator(10, 10);
cout << abc->result() << endl;
delete abc;
abc = new SubstractorCalculator(20,10);
cout << abc->result() << endl;
delete abc;
abc = new MultiplyrCalculator(20, 10);
cout << abc->result() << endl;
delete abc;
}
int main()
{
test01();
system("pause");
return 0;
}
3、纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include<iostream>
#include<string>
using namespace std;
//纯虚函数
//类中只要有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也属于抽象类
class Base{
public:
virtual void func() = 0;
};
class Son :public Base{
public:
void func()
{
cout <<"调用了 func函数" << endl;
}
};
void test01()
{
Base *base;
//设置了纯虚函数即为抽象函数,因此抽象函数不能实例化
//base = new Base;
base = new Son();
base->func();
delete base;//分配到堆区的内存要删除
}
int main()
{
test01();
system("pause");
return 0;
}
4、多态案例二-制作饮品
制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶
#include<iostream>
#include<string>
using namespace std;
class AbstractDrinking
{
public:
virtual void Boil() = 0;
virtual void Brew() = 0;
virtual void PourInCup() = 0;
virtual void PusSomething() = 0;
void MakeDrink()
{
Boil();
Brew();
PourInCup();
PusSomething();
}
};
class Coffee :public AbstractDrinking
{
void Boil()
{
cout <<"矿泉水" << endl;
}
void Brew()
{
cout <<"泡" << endl;
}
void PourInCup()
{
cout <<"倒" << endl;
}
void PusSomething()
{
cout <<"加牛奶" << endl;
}
};
class Tea :public AbstractDrinking
{
void Boil()
{
cout << "自来水" << endl;
}
void Brew()
{
cout << "加热" << endl;
}
void PourInCup()
{
cout << "泼" << endl;
}
void PusSomething()
{
cout << "加树叶" << endl;
}
};
//使用引用调用虚函数和多态
void DoWork(AbstractDrinking& drinking)
{
drinking.MakeDrink();
}
//使用指针调用虚函数和多态
void DoWorkByPoint(AbstractDrinking *dringing)
{
dringing->MakeDrink();
}
void test01()
{
//使用引用调用虚函数和多态
Tea tea;
DoWork(tea);
Coffee coffee;
DoWork(coffee);
cout <<"------------------>" << endl;
//使用指针调用虚函数和多态
AbstractDrinking *drinking = new Tea;
DoWorkByPoint(drinking);
delete drinking;
drinking = new Coffee;
DoWorkByPoint(drinking);
delete drinking;
}
int main()
{
test01();
system("pause");
return 0;
}
5、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
总结:
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
#include<iostream>
#include<string>
using namespace std;
class Animal{
public:
Animal()
{
cout <<"Animal 构造函数的调用" << endl;
}
~Animal()
{
cout <<"Animal 析构函数的调用" << endl;
}
virtual void speak() = 0;
};
class Cat :public Animal
{
public:
Cat()
{
cout <<"小猫的构造函数在调用" << endl;
}
~Cat()
{
cout <<"小猫的析构函数在调用" << endl;
}
void speak()
{
cout <<"小猫在叫" << endl;
}
};
void test01()
{
Animal* animal = new Cat;
animal->speak();
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
小猫的析构函数没有调用,如果小猫中存在堆区的分配,有开辟的内存空间没有释放会造成内存泄漏。因此要在父类中使用虚析构和纯虚析构,可以是子类中的析构函数被调用。
#include<iostream>
#include<string>
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 <<"小猫的构造函数在调用" << endl;
m_Name = new string(name);
}
~Cat()
{
if (m_Name != NULL)
{
delete m_Name;
m_Name = NULL;
}
cout <<"小猫的析构函数在调用" << endl;
}
void speak()
{
cout <<*m_Name<<"小猫在叫" << endl;
}
string *m_Name;
};
void test01()
{
Animal* animal = new Cat("Jack");
animal->speak();
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
6、多态案例三-电脑组装
电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include<iostream>
#include<string>
using namespace std;
//创建 cpu memory videocard 虚函数 创建Inter和Lenovo对虚函数进行实现。
//创建 computer 作为cpu memory 和 videocard的集合类 进行组装
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* c,Memory *m,VideoCard *v)
{
cpu = c;
memory = m;
videocard = v;
}
~Computer()
{
if (cpu != NULL)
{
delete cpu;
cpu = NULL;
}
if (memory != NULL)
{
delete cpu;
memory = NULL;
}
if (videocard != NULL)
{
delete cpu;
videocard = NULL;
}
}
void work()
{
cpu->calculate();
memory->storage();
videocard->display();
}
private:
Cpu *cpu;
Memory *memory;
VideoCard *videocard;
};
//具体厂商
//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, intelMem, intelCard);
computer1->work();
delete computer1;
cout << "-----------------------" << endl;
cout << "第二台电脑开始工作:" << endl;
//第二台电脑组装
Computer * computer2 = new Computer(new LenovoCPU, new LenovoMemory,new LenovoVideoCard);
computer2->work();
delete computer2;
cout << "-----------------------" << endl;
cout << "第三台电脑开始工作:" << endl;
//第三台电脑组装
Computer * computer3 = new Computer(new LenovoCPU, new LenovoMemory, new LenovoVideoCard);
computer3->work();
delete computer3;
}
int main()
{
test01();
system("pause");
return 0;
}
二、文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件 < fstream >
文件类型分为两种:
文本文件 - 文件以文本的ASCII码形式存储在计算机中
二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
ofstream:写操作
ifstream: 读操作
fstream : 读写操作
1.文本写文件
写文件步骤如下:
包含头文件
#include
创建流对象
ofstream ofs;
打开文件
ofs.open(“文件路径”,打开方式);
写数据
ofs << “写入的数据”;
关闭文件
ofs.close();
注意: 文件打开方式可以配合使用,利用|操作符
**例如:**用二进制方式写文件 ios::binary | ios:: out
#include<iostream>
using namespace std;
#include<fstream>
void test01()
{
ofstream ofs;
ofs.open("text.txt",ios::out);
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
文件操作必须包含头文件 fstream
读文件可以利用 ofstream ,或者fstream类
打开文件时候需要指定操作文件的路径,以及打开方式
利用<<可以向文件中写数据
操作完毕,要关闭文件
2.文本读文件
读文件与写文件步骤相似,但是读取方式相对于比较多
读文件步骤如下:
包含头文件
#include
创建流对象
ifstream ifs;
打开文件并判断文件是否打开成功
ifs.open(“文件路径”,打开方式);
读数据
四种方式读取
关闭文件
ifs.close();
#include<iostream>
using namespace std;
#include<fstream>
#include<string>
void test01()
{
ifstream ifs;
ifs.open("text.txt",ios::in);
if (!ifs.is_open())
{
cout <<"文件打开失败" << endl;
return;
}
string buf;
while (getline(ifs,buf))
{
cout <<buf << endl;
}
}
int main()
{
test01();
system("pause");
return 0;
}
3、二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为 ios::binary
4、二进制写文件
二进制方式写文件主要利用流对象调用成员函数write
函数原型 :ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
文件输出流对象 可以通过write函数,以二进制方式写数据
#include<iostream>
using namespace std;
#include<fstream>
#include<string>
class Person{
public:
char m_Name[64];
int m_Age;
};
//二进制写文件
void test01()
{
//创建输出流对象
ofstream ofs("person.txt",ios::out|ios::binary);
Person p = {"张三",18};
ofs.write((const char*)&p,sizeof(p));
ofs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
5、二进制读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
文件输入流对象 可以通过read函数,以二进制方式读数据
#include<iostream>
using namespace std;
#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;
}