第四天 C++核心编程(3)

4.7 多态

4.7.1 多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
#include<iostream>
using namespace std;
class Animal
{
public:
Animal();
~Animal();
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat:public Animal
{
public:
Cat();
~Cat();
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog:public Animal
{
public:
Dog();
~Dog();
void speak()
{
cout << "小狗在说话" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void dospeak(Animal& an)
{
an.speak();
}
void test01()
{
Cat c;
dospeak(c);
Dog d;
dospeak(d);
}
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
Dog::Dog()
{
}

Dog::~Dog()
{
}

Cat::Cat()
{
}

Cat::~Cat()
{
}
Animal::Animal()
{
}

Animal::~Animal()
{
}
int main()
{
test01();
system("pause");
return 0;
}

从零开始学编程---C++篇(4)_多态

总结:

多态满足条件

  • 有继承关系
  • 子类重写父类中的虚函数

多态使用条件

  • 父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写

从零开始学编程---C++篇(4)_ios_02

4.7.2多态案例一 计算器类

案例描述:

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

普通实现:

#include<iostream>
using namespace std;
class Calculator
{
public:
Calculator();
~Calculator();
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;
}
}

public:
int m_num1;
int m_num2;

};

Calculator::Calculator()
{
}

Calculator::~Calculator()
{
}
void test01()
{
Calculator c;
c.m_num1 = 10;
c.m_num2 = 10;
int ret=c.getResult("+");
int ret2 = c.getResult("-");
int ret3 = c.getResult("*");
cout << "ret=" << ret << endl;
cout << "ret2=" << ret2 << endl;
cout << "ret3=" << ret3 << endl;

}
int main()
{
test01();
system("pause");
return 0;
}

从零开始学编程---C++篇(4)_ios_03

多态实现:

//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
#include<iostream>
using namespace std;
class AbstractCalculator
{
public:
AbstractCalculator();
~AbstractCalculator();
virtual int result()
{
return 0;
}
public:
int m_num1;
int m_num2;

};

AbstractCalculator::AbstractCalculator()
{
}

AbstractCalculator::~AbstractCalculator()
{
}
class AddCalculator:public AbstractCalculator
{
public:
AddCalculator();
~AddCalculator();
int result()
{
return m_num1 + m_num2;
}


};

AddCalculator::AddCalculator()
{
}

AddCalculator::~AddCalculator()
{
}
class SubCaculator:public AbstractCalculator
{
public:
SubCaculator();
~SubCaculator();
int result()
{
return m_num1 - m_num2;
}
};

SubCaculator::SubCaculator()
{
}

SubCaculator::~SubCaculator()
{
}
class MulCaculator:public AbstractCalculator
{
public:
MulCaculator();
~MulCaculator();
int result()
{
return m_num1 * m_num2;
}
};

MulCaculator::MulCaculator()
{
}

MulCaculator::~MulCaculator()
{
}
void test01()
{
AbstractCalculator* abc = new AddCalculator;
abc->m_num1 = 10;
abc->m_num2 = 10;
cout << "m_num1+m_num2=" << abc->result() << endl;
delete abc;
abc = new SubCaculator;
abc->m_num1 = 20;
abc->m_num2 = 10;
cout << "m_num1-m_num2=" << abc->result() << endl;
delete abc;
abc = new MulCaculator;
abc->m_num1 = 10;
abc->m_num2 = 10;
cout << "m_num1*m_num2=" << abc->result() << endl;
delete abc;
}
int main()
{
test01();
system("pause");
return 0;
}

从零开始学编程---C++篇(4)_多态_04

总结:C++开发提倡利用多态设计程序架构,因为多态优点很多

4.7.3 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数语法:​​virtual 返回值类型 函数名 (参数列表)= 0 ;​

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
//纯虚函数
//类中只要有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也属于抽象类
#include<iostream>
using namespace std;
class Base
{
public:
Base();
~Base();
virtual void func() = 0;

};

Base::Base()
{
}

Base::~Base()
{
}
class Son:public Base
{
public:
Son();
~Son();
void func()
{
cout << "调用函数func()" << endl;
}

};

Son::Son()
{
}

Son::~Son()
{
}
void test()
{
Base* base = NULL;
base = new Son;
base->func();
delete base;
}
int main()
{
test();
system("pause");
return 0;
}

从零开始学编程---C++篇(4)_ios_05

4.7.7 多态案例二 制作饮品

案例描述:

制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料

利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

#include<iostream>
using namespace std;
class MakeDrinking
{
public:
MakeDrinking();
~MakeDrinking();
virtual void boil() = 0;
virtual void brew() = 0;
virtual void pourInCup() = 0;
virtual void putSomething() = 0;
void dodd()
{
boil();
brew();
pourInCup();
putSomething();
}

};

MakeDrinking::MakeDrinking()
{
}

MakeDrinking::~MakeDrinking()
{
}
class Tea:public MakeDrinking
{
public:
Tea();
~Tea();
virtual void boil()
{
cout << "煮沸农夫山泉" << endl;
}
virtual void brew()
{
cout << "冲泡茶叶" << endl;
}
virtual void pourInCup()
{
cout << "倒入杯中" << endl;
}
virtual void putSomething()
{
cout << "加入柠檬片" << endl;
}


};

Tea::Tea()
{
}

Tea::~Tea()
{
}
class Coffee:public MakeDrinking
{
public:
Coffee();
~Coffee();
virtual void boil()
{
cout << "煮沸百岁山" << endl;
}
virtual void brew()
{
cout << "冲泡咖啡粉" << endl;
}
virtual void pourInCup()
{
cout << "倒入杯中" << endl;
}
virtual void putSomething()
{
cout << "加入牛奶" << endl;
}

};

Coffee::Coffee()
{
}

Coffee::~Coffee()
{
}
void Making(MakeDrinking* md)
{
md->dodd();
delete md;
}
void test()
{
Making(new Tea);
cout << "-----------------" << endl;
Making(new Coffee);
}
int main()
{
test();
system("pause");
return 0;
}

从零开始学编程---C++篇(4)_ios_06

4.7.5 虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

​virtual ~类名(){}​

纯虚析构语法:

​virtual ~类名() = 0;​

​类名::~类名(){}​

#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
Animal();
virtual void speak() = 0;
virtual ~Animal()=0;

};

Animal::Animal()
{
cout << "Animal构造函数的调用" << endl;
}

Animal::~Animal()
{
cout << "Animal纯虚析构函数的调用" << endl;

}
class Cat:public Animal
{
public:
Cat();
Cat(string s);
~Cat();
virtual void speak()
{
cout << *m_name << "小猫正在说话" << endl;
}
public:
string* m_name;
};

Cat::Cat()
{
}
Cat::Cat(string s)
{
cout<< "Cat构造函数调用!" << endl;
m_name = new string(s);

}
Cat::~Cat()
{
cout << "cat析构函数的调用" << endl;
if (m_name!=NULL)
{
delete m_name;
m_name = NULL;
}
}
void test01()
{
Animal* an = new Cat("tom");
an->speak();
delete an;
}
int main()
{
test01();
system("pause");
return 0;
}

从零开始学编程---C++篇(4)_bc_07

4.7.6 多态案例三 电脑组装

案例描述:

电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储)

将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

#include<iostream>
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();
}

//提供析构函数 释放3个电脑零件
~Computer()
{

//释放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;
}
}

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 << "-----------------------" << endl;
cout << "第二台电脑开始工作:" << endl;
//第二台电脑组装
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++篇(4)_多态_08

5.文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放

通过文件可以将数据持久化

C++中对文件操作需要包含头文件 < fstream >

文件类型分为两种:

  1. 文本文件 - 文件以文本的ASCII码形式存储在计算机中
  2. 二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

操作文件的三大类:

  1. ofstream:写操作
  2. ifstream: 读操作
  3. fstream : 读写操作

5.1 文本文件

5.1.1 写文件

写文件步骤如下:

  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<fstream>
using namespace std;
void test()
{
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close();
}
int main()
{
test();
system("pause");
return 0;
}

从零开始学编程---C++篇(4)_ios_09

总结:

  • 文件操作必须包含头文件 fstream
  • 读文件可以利用 ofstream ,或者fstream类
  • 打开文件时候需要指定操作文件的路径,以及打开方式
  • 利用<<可以向文件中写数据
  • 操作完毕,要关闭文件
5.1.2 读文件

读文件与写文件步骤相似,但是读取方式相对于比较多

读文件步骤如下:

  1. 包含头文件
    #include <fstream>
  2. 创建流对象
    ifstream ifs;
  3. 打开文件并判断文件是否打开成功
    ifs.open("文件路径",打开方式);
  4. 读数据
    四种方式读取
  5. 关闭文件
    ifs.close();
#include <fstream>
#include <string>
#include<iostream>
using namespace std;
void test01()
{
ifstream ifs;
ifs.open("test.txt", ios::in);

if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}

//第一种方式
//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)
{
cout << c;
}

ifs.close();


}

int main() {

test01();

system("pause");

return 0;
}

从零开始学编程---C++篇(4)_bc_10

总结:

  • 读文件可以利用 ifstream ,或者fstream类
  • 利用is_open函数可以判断文件是否打开成功
  • close 关闭文件

5.2 二进制文件

以二进制的方式对文件进行读写操作

打开方式要指定为 ==ios::binary==

5.2.1 写文件

二进制方式写文件主要利用流对象调用成员函数write

函数原型 :​​ostream& write(const char * buffer,int len);​

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

#include <fstream>
#include <string>
#include<iostream>
using namespace std;
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;
}

从零开始学编程---C++篇(4)_bc_11

总结:

  • 文件输出流对象 可以通过write函数,以二进制方式写数据
5.2.2 读文件

二进制方式读文件主要利用流对象调用成员函数read

函数原型:​​istream& read(char *buffer,int len);​

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

#include <fstream>
#include <string>
#include<iostream>
using namespace std;
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;
}

从零开始学编程---C++篇(4)_多态_12

文件输入流对象 可以通过read函数,以二进制方式读数据