目录
多态
多态的基本概念
多态是C++面向对象三大特性之一
简单理解和cin>>有关,因为多个子类继承父类函数后,每个子类进行什么操作是不同的,那么他应该怎么调用函数呢?
多态分为两类
-
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
-
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
-
静态多态的函数地址早绑定 - 编译阶段确定函数地址
-
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
-
这里重点记一下,在函数中如何调用父类中函数的,
-
注意一下,两个子类继承一个父类
给父类创建一个调用函数,再来个两个子类创建调用,依次按照顺序。
这样想要调用父类,直接调用即可,想调用继承父类的子类,只要通过父类去调用子类就可以了
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 >==
文件类型分为两种:
-
文本文件 - 文件以文本的ASCII码形式存储在计算机中
-
二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
-
ofstream:写操作
-
ifstream: 读操作
-
fstream : 读写操作
文本文件
写文件
写文件步骤如下:
-
包含头文件
#include <fstream>
-
创建流对象
ofstream ofs;
-
打开文件
ofs.open("文件路径",打开方式);
-
写数据
ofs << "写入的数据";
-
关闭文件
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类
-
打开文件时候需要指定操作文件的路径,以及打开方式
-
利用<<可以向文件中写数据
-
操作完毕,要关闭文件
读文件
读文件与写文件步骤相似,但是读取方式相对于比较多
读文件步骤如下:
-
包含头文件
#include <fstream>
-
创建流对象
ifstream ifs;
-
打开文件并判断文件是否打开成功
ifs.open("文件路径",打开方式);
-
读数据
四种方式读取
-
关闭文件
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;
}