1、多态:
多态是C++面向对象三大特性之一
多态分为两类
静态多态和动态多态区别:
总结:
多态满足条件
多态使用条件
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
自用:
#include
#include
using namespace std;
class animal {
public:
virtual void say(){
cout << "动物在说话" << endl;
}
};
class cat :public animal {
public:
virtual void say() {
cout << "小猫在说话" << endl;
}
};
class dog :public animal {
public:
void say() {
cout << "小狗在说话" << endl;
}
};
void dosay(animal &animal) {//没有实现动态多态时,函数地址早绑定,在编译时就确定地址为animal
//实现动态多态后,函数地址晚绑定,在运行时确定地址为传入的cat.
//静态多态就是普通的函数重载和运算符重载
animal.say();
}
int main() {
cat c1;
dog d1;
dosay(c1);
return 0;
}
老师:
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;
}
2、
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
抽象类:
类中有个纯虚函数就是抽象类。
案例实现:
#include
#include
using namespace std;
class Drink {
public:
virtual void PreWater() = 0;
virtual void PreSth() = 0;
virtual void Pull() = 0;
virtual void AddSth() = 0;
void MakeDrink() {
PreWater();
PreSth();
Pull();
AddSth();
}
};
class Coffee :public Drink{
public:
void PreWater() {
cout << "将水煮沸" << endl;
}
void PreSth() {
cout << "准备咖啡粉" << endl;
}
void Pull() {
cout << "将水倒入" << endl;
}
void AddSth() {
cout << "加入牛奶和糖" << endl;
}
};
class Tea :public Drink {
public:
void PreWater() {
cout << "将水煮沸" << endl;
}
void PreSth() {
cout << "准备茶叶" << endl;
}
void Pull() {
cout << "将水倒入" << endl;
}
void AddSth() {
cout << "加入枸杞" << endl;
}
};
void DoDrink() {
Drink* drink = new Coffee;//通过对象指针访问类中的方法
drink->MakeDrink();
delete drink;
cout << "-------------" << endl;
drink = new Tea;
drink->MakeDrink();
delete drink;
}
int main() {
DoDrink();
return 0;
}
3、
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
虚析构和纯虚析构区别:
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
class Animal {
public:
Animal()
{
cout << "Animal 构造函数调用!" << endl;
}
virtual void Speak() = 0;
//析构函数加上virtual关键字,变成虚析构函数
//virtual ~Animal()
//{
// cout << "Animal虚析构函数调用!" << endl;
//}
virtual ~Animal() = 0;
};
//类外实现纯虚函数的内容编写
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()
{
cout << "Cat析构函数调用!" << endl;
if (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
public:
string *m_Name;
};
void test01()
{
Animal *animal = new Cat("Tom");
animal->Speak();
//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
//怎么解决?给基类增加一个虚析构函数
//虚析构函数就是用来解决通过父类指针释放子类对象
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
总结:
1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类
3、案例:
案例描述:
电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include
#include
using namespace std;
class CPU {
public:
virtual void calculate() = 0;
virtual ~CPU() {}
};
class VideoCard {
public:
virtual void display() = 0;
virtual ~VideoCard() {}
};
class Memory {
public:
virtual void storage() = 0;
virtual ~Memory() {}
};
// A厂商生产的零件
class ACPU : public CPU {
public:
void calculate() override {
cout << "A的CPU正在工作" << endl;
}
};
class AVideoCard : public VideoCard {
public:
void display() override {
cout << "A的显卡正在工作" << endl;
}
};
class AMemory : public Memory {
public:
void storage() override {
cout << "A的内存条正在工作" << endl;
}
};
// B厂商生产的零件
class BCPU : public CPU {
public:
void calculate() override {
cout << "B的CPU正在工作" << endl;
}
};
class BVideoCard : public VideoCard {
public:
void display() override {
cout << "B的显卡正在工作" << endl;
}
};
class BMemory : public Memory {
public:
void storage() override {
cout << "B的内存条正在工作" << endl;
}
};
class PC {
public:
PC(CPU* cpu, VideoCard* vc, Memory* mem)
: m_cpu(cpu), m_vc(vc), m_mem(mem)
{
cout << "创建主机" << endl;
}
void work() {
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
~PC() {
// 如果PC类不负责对象的释放,可以不删除这些指针
//释放CPU零件
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
//释放显卡零件
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
//释放内存条零件
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
cout << "销毁主机" << endl;
}
private:
CPU* m_cpu;
VideoCard* m_vc;
Memory* m_mem;
};
void PC01() {
CPU* cpu1 = new ACPU;
VideoCard* vc1 = new BVideoCard;
Memory* mem1 = new AMemory;
PC pc1(cpu1, vc1, mem1);
pc1.work();
// 手动删除分配的内存
// delete cpu1;
// delete vc1;
// delete mem1;
}
//int main() {
// PC01();
// return 0;
//}
4、文件
(1)文本文件的读写操作:
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件 < fstream >
文件类型分为两种:
操作文件的三大类:
写文件步骤如下:
包含头文件
#include
创建流对象
ofstream ofs;
打开文件
ofs.open(“文件路径”,打开方式);
写数据
ofs << “写入的数据”;
关闭文件
ofs.close();
文件打开方式:
打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
注意: 文件打开方式可以配合使用,利用|操作符
**例如:**用二进制方式写文件 ios::binary | ios:: out
总结:
读文件与写文件步骤相似,但是读取方式相对于比较多
读文件步骤如下:
包含头文件
#include
创建流对象
ifstream ifs;
打开文件并判断文件是否打开成功
ifs.open(“文件路径”,打开方式);
读数据
四种方式读取
关闭文件
ifs.close();
总结:
#include
#include
#include //必须包含的头文件
using namespace std;
void test01() {
ofstream ofs;//创建流对象
ofs.open("text.txt", ios::out);//前面存放的是文件路径,后面的是打开方式
ofs << "测试输入是否成功" << endl;
ofs.close();//关闭文件
}
void test02() {
ifstream ifs;//创建流对象
ifs.open("text.txt", ios::in);
if (!ifs.is_open()) {
cout<<"文件打开失败" << endl;
}//打开文件并判断是否打开成功
//四种方式读文件:
//char buf[1024] = { 0 };
//while (ifs >> buf) {//每行进行读,读完返回“假”,退出while循环
// cout << buf << endl;
//}
//char buf[1024] = { 0 };
//while (ifs.getline(buf, sizeof(buf))) {//getline()表示读取多少行,第一个参数为数组首地址也即数组名
// //第二个参数为最多读取多少字节,直接sizeof()进行读取
// cout << buf <
二进制文件:
以二进制的方式对文件进行读写操作
打开方式要指定为 ios::binary
二进制方式写文件主要利用流对象调用成员函数write
函数原型 :ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
总结:
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
二进制文件相比文本文件也就是多了两个函数,其他大体大差不差,另要注意的是(const char *)和(char *)转换的都是地址,需要加取址符&
#include
#include
#include
using namespace std;
class Person {
public:
string name;
int age;
};
void test01(){
ofstream ofs;
ofs.open("test2.txt", ios::out | ios::binary);
Person p1 = { "jack" ,18};
ofs.write((const char*)&p1, sizeof(p1));
ofs.close();
}
void test02() {
ifstream ifs;
ifs.open("test2.txt", ios::in | ios::binary);
if (!ifs.is_open()) {
cout << "文件打开失败" << endl;
return;
}
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << p.name << p.age << endl;
ifs.close();
}
void test03(){
fstream file;
file.open("text3.txt", ios::out | ios::binary);
int a = 100;
file.write((const char*)&a, sizeof(a));
file.close();
file.open("text3.txt", ios::in | ios::binary);
if (!file.is_open()) {
cout << "文件打开失败" << endl;
return;
}
file.read((char*)&a, sizeof(a));
cout << a << endl;
file.close();
}
int main() {
//test01();
//test02();
test03();
return 0;
}