C++学习笔记(13)
学习是一件任重而道远的事情,与其焦虑不如动手起来,借助平台记录自己学习笔记,希望和大家多多交流,今天又是努力成为程序媛的一天!
17.类和对象
17.7 多态
17.7.1 多态的基本概念
多态分为两类:
静态多态:函数重载和运算符重载属于静态多态 复用函数名
动态多态:派生类结合虚函数实现运行时多态
静态多态和动态多态的区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
#include<iostream>
using namespace std;
class Animal {
public:
virtual void speak() {
cout << "Animal is speaking" << endl;
}
};
class Cat :public Animal{
public:
//重写 函数返回值类型 函数名 参数列表 完全相同
virtual void speak() {//这里的virtual可写可不写
cout << "Cat is speaking" << endl;
}
};
class Dog :public Animal {
public:
void speak() {
cout << "Dog is speaking" << endl;
}
};
//地址早绑定 在编译阶段确定函数地址
// 成员函数如果没有被声明为virtual,则其解析过程发生在编译时而非运行时
//如果想让猫执行说话 那么这个函数地址不能提前绑定 需要在运行阶段进行绑定 地址晚绑定
void Speak(Animal& animal) {//父类引用指向子类对象 //C++中允许父子类之间类型转换 无需强制转化 父类的指针或引用可以直接指向子类对象
animal.speak();//传入的是Animal数据类型 无论指向哪个子类 都是调用父类的属性
//父类加上virtual后输出的就是Cat is speaking
}
void test() {
Cat cat;
Speak(cat);//等价于Animal& animal = cat
}
int main() {
test();
system("pause");
return 0;
}
注意:C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数自动称为虚函数,因此,子类重写该虚函数时,可加可不加virtual
-
动态多态满足条件:
- 1.有继承关系
- 2.子类重写父类虚函数
- 3.父类指针或引用指向子类对象使用
动态多态理解:基类派生类和动态绑定
父类的引用指向类的对象
多态原理分析:c++多态的原理
C++覆盖(虚函数的实现原理)
几点注意:
1.父类指针或引用可以引用基类类型对象,也可以引用派生类类型对象,无论实际对象具有哪种类型,编译器都会把其当作基类类型对象,这样是安全的,因为每个派生类对象都拥有基类子对象,而且任何可以在基类上做的操作也可以通过派生类对象使用。所以,调用非虚函数,无论实际对象什么类型,都执行基类类型定义的函数。
2.如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本
17.7.2 多态案例——计算器类
分别用普通写法和多态技术设计实现两个操作数进行运算的计算器类
- 多态的优点:
- 1.代码组织结构清晰
- 2.可读性强
- 3.利于前期和后期扩展和维护
#include<iostream>
using namespace std;
#include<string>
//普通写法——计算器
class Calculator {
public:
int Result(string oper) {
if (oper == "+") {
return num1 + num2;
}
else if (oper == "-") {
return num1 - num2;
}
else if (oper == "*") {
return num1 * num2;
}
else {
return 0;
}
//如果想拓展新的功能,需要修改源码
//实际开发中 提倡开闭原则
//开闭原则:对扩展进行开发 对修改进行关闭
}
int num1;
int num2;
};
//多态的优点
//1.组织结构清晰
//2.可读性强
//3.对于前期和后期的扩展和维护性高
//利用多态实现
//计算机抽象类
class AbstractCalculator {
public:
virtual int getRe() {
return 0;
}
int m_num1;
int m_num2;
AbstractCalculator* a;
};
//加法计算器
class AddCalculator :public AbstractCalculator{
public:
virtual int getRe() {
return m_num1 + m_num2;
}
AddCalculator* m_a;
};
//减法计算器
class SubCalculator:public AbstractCalculator {
public:
int getRe() {
return m_num1 - m_num2;
}
};
//乘法
class MulCalculator :public AbstractCalculator {
public:
int getRe() {
return m_num1 * m_num2;
}
};
void test() {
cout << sizeof("+") << endl;//占两个字符 所以不能用switch,因为它只能是整型或者字符型或者枚举
}
void test01() {
Calculator c1;
c1.num1 = 10;
c1.num2 = 20;
int re = c1.Result("*");
cout << c1.num1 << "*" << c1.num2 << "=" << re << endl;
}
void test02() {
//加法运算
//父类指针或引用指向子类对象
AbstractCalculator* a = new AddCalculator;
a->m_num1 = 40;
a->m_num2 = 25;
cout << a->m_num1 << "+" << a->m_num2 << "=" << a->getRe() << endl;
delete a;
//减法运算
AbstractCalculator* abc = new SubCalculator;
abc->m_num1 = 50;
abc->m_num2 = 20;
cout << abc->m_num1 << "-" << abc->m_num2 << "=" << abc->getRe() << endl;
delete abc;
}
int main() {
//test();
//test01();
test02();
system("pause");
return 0;
}
17.7.3 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现毫无意义,主要调用子类重写的内容,因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include<iostream>
using namespace std;
class Animal {
public:
//只要有一个纯虚函数 这个类就称为抽象类
//特点:
//1.无法实例化对象
//2.抽象类的子类必须要重写父类的纯虚函数 否则也属于抽象类
virtual void speak() = 0;
};
class Cat :public Animal {
public:
virtual void speak() {
cout << "Cat is speaking" << endl;
}
};
class Dog :public Animal {
public:
void speak() {
cout << "Dog is speaking" << endl;
}
};
void test() {
Cat cat;
Animal& animal = cat;
animal.speak();//Cat is speaking
}
void test01() {
//Animal a;
//new Animal;//抽象类无法实例化对象 栈堆区都是
}
int main() {
test();
system("pause");
return 0;
}
17.7.4 多态案例二——制作饮品
#include<iostream>
using namespace std;
class AbDriking {
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 AbDriking {
public:
void Boil() {
cout << "Pure Water" << endl;
}
virtual void Brew() {
cout << "Brew coffee" << endl;
};
virtual void PourInCup() {
cout << "Put in a cup" << endl;
};
virtual void PutSomething() {
cout << "Put Milk" << endl;
};
};
//制作茶叶
class Tea :public AbDriking {
public:
void Boil() {
cout << "Pure Water" << endl;
}
virtual void Brew() {
cout << "Brew tea" << endl;
};
virtual void PourInCup() {
cout << "Put in a cup" << endl;
};
virtual void PutSomething() {
cout << "Put lemon" << endl;
};
};
void Makeit(AbDriking *abs) {
abs->MakeDrink();
delete abs;
}
void test() {
//制作coffee
Makeit(new Coffee);
//制作tea
Makeit(new Tea);
}
int main() {
test();
system("system");
return 0;
}
17.7.5 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区 那么父类指针在释放时无法调用子类的析构代码
解决方式:将父类中的析构函数改为虚析构或纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要具体的函数实现
区别:
- 如果是纯虚析构 该类属于抽象类 无法实例化对象
#include<iostream>
using namespace std;
class Animal {
public:
Animal() {
cout << "Animal的构造函数调用" << endl;
}
virtual ~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 << "Cat的构造函数调用" << endl;
m_Name = new string(name);
}
~Cat() {
if (m_Name != NULL) {
cout << "Cat的析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
virtual void speak() {
cout << *m_Name << "Cat is speaking" << endl;
}
string *m_Name;
};
class Dog :public Animal {
public:
void speak() {
cout << "Dog is speaking" << endl;
}
};
void test() {
//父类指针析构时,不会调用子类析构函数 导致子类如果有堆区数据会导致内存泄漏
Animal* animal = new Cat("Tom");
animal->speak();
delete animal;//删除指针不是把指针删了 而是其所指向的变量删除
}
int main() {
test();
system("pause");
return 0;
}
*问题:*在堆区建立对象,父类指针指向子类对象时,delete父类指针时,并没有走子类的析构函数,导致内存泄漏
解决方法:将父类析构函数变成虚析构,加virtual,利用虚析构可以解决父类指针释放子类对象不干净的问题
利用纯虚析构:
class Animal {
public:
Animal() {
cout << "Animal的构造函数调用" << endl;
}
/*virtual ~Animal() {
cout << "Animal的析构函数调用" << endl;
}*/
virtual ~Animal() = 0;//直接纯虚析构会报错 因为父类也可能有堆区数据 所以不管纯虚析构还是虚析构都要有函数实现
virtual void speak() = 0;
};
Animal::~Animal() {
cout << "Animal的纯虚析构的调用" << endl;
}
总结:
1.虚析构和纯虚析构就是用来解决通过父类指针释放子类对象问题
2.如果子类无堆区数据 ,可以不写为虚析构和纯虚析构
3.有纯虚析构函数的类也属于抽象类
17.7.6 多态案例——电脑组装
案例描述:
电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include<iostream>
using namespace std;
class CPU {
public:
//抽象计算机类
virtual void calculate() = 0;
};
class VideoCard {
public:
//抽象显卡类
virtual void display() = 0;
};
class Memory {
public:
//抽象存储类
virtual void storage() = 0;
};
class IntelCPU :public CPU {
public:
void calculate() {
cout << "This is CPU of Intel" << endl;
}
};
class IntelVideoCard :public VideoCard {
public:
void display() {
cout << "This is VideoCard of Intel" << endl;
}
};
class IntelMemory :public Memory {
public:
void storage() {
cout << "This is Memory of Intel" << endl;
}
};
class LenovoCPU :public CPU {
public:
void calculate() {
cout << "This is CPU of Lenovo" << endl;
}
};
class LenovoVideoCard :public VideoCard {
public:
void display() {
cout << "This is showID of Lenovo" << endl;
}
};
class LenovoMemory :public Memory {
public:
void storage() {
cout << "This is save of Lenovo" << endl;
}
};
class Computer {
public:
//构造函数传入三个零件指针
Computer(CPU* cpu, VideoCard* vc, Memory* mem) {
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
~Computer() {
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;
}
}
//提供工作的函数
//调用每个零件工作的接口
void work() {
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
private:
CPU* m_cpu;
VideoCard* m_vc;
Memory* m_mem;
};
void test1() {
//第一台电脑零件
cout << "----------------first one-----------------" << endl;
CPU* Intelcpu = new IntelCPU;
VideoCard* Intelvc = new IntelVideoCard;
Memory* Intelmemory = new IntelMemory;
//创建firstone
Computer *computer1 = new Computer(Intelcpu, Intelvc, Intelmemory);
computer1->work();
delete computer1;
cout << "----------------Second one-----------------" << endl;
Computer* computer2 = new Computer(new IntelCPU, new LenovoVideoCard, new LenovoMemory);
computer2->work();
delete computer2;
cout << "----------------Third one------------------" << endl;
Computer* computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);
computer3->work();
delete computer3;
}
int main() {
test1();
system("pause");
return 0;
}
18. 文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束就都会释放,可以通过文件将数据持久化
C++对文件操作需要包含头文件< fstream >
-
文件类型分为两种:
- 1,文本文件 - 文件以文本的ASCII码形式存储在计算机中
- 2,二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们
-
操作文件的三大类:
- 1,ofstream:写操作
- 2,ifstream:读操作
- 3,fstream:读写操作
18.1文本文件
18.1.1 写文件
- 步骤:
1.包含头文件#include<fstream>
2.创建流对象ofstream ofs;
3.打开文件ofs.open("文件路径”,打开方式);
4.写数据ofs<<"写入的数据“;
5.关闭文件ofs.close();
选择文件打开方式:
文件打开方式可以配合使用,利用操作符|
举例:用二进制写文件ios::binary | ios::out
#include<iostream>
using namespace std;
#include<fstream>//包含头文件
void writefile() {
ofstream ofs;//创建流对象
//指定文件打开方式
ofs.open("test.txt", ios::out);
//写数据
ofs << "姓名:XXX" << endl;
ofs << "性别:男" << endl;
//关闭文件
ofs.close();
}
int main() {
writefile();
system("pause");
return 0;
}
控制台只有任意键继续的结果,不过在项目写的同文件夹里出现了一个新的test文本文件,写入了程序中写的内容
这里文件打开方式中文件路径如果不指定就默认和程序同目录下
18.1.2 读文件
- 步骤:
1.包含头文件#include<fstream>
2.创建流对象ifstream ifs;
3.打开文件并判断文件是否打开成功ifs.open("文件路径”,打开方式);
4.读数据:四种方式读取
5.关闭文件ifs.close();
#include<iostream>
using namespace std;
#include<fstream>//包含头文件
#include<string>
void readfile() {
ifstream ifs;//创建流对象
//指定文件打开方式
ifs.open("test.txt", ios::in);
//判断是否打开成功用is_open() 打开成功就返回真,打开失败就返回假
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) {//EOF=end of file
// cout << c;//不要加endl;会乱码
//}
//关闭文件
ifs.close();
}
int main() {
readfile();
system("pause");
return 0;
}
运行结果:
18.2二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为ios::binary
18.2.1 写文件
主要利用流对象调用成员函数write
函数原型:ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
#include<iostream>
using namespace std;
#include<fstream>
//二进制写文件
//可以操作不止int,double等数据类型,也可以操作自定义数据类型
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);可以直接与上一步骤结合一起
//4.写文件
Person p = { "Wang",20 };//初始化列表 前提 必须都是公共属性
//ofs.write((const char*)&ofs, sizeof(ofs));
ofs.write((const char*)&p, sizeof(Person));//类型强制转换 指向p的地址可以理解为指向p的指针 强制转换为char的指针
//5.关闭文件
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
18.2.2 读文件
主要利用流对象调用成员函数read
函数原型:istream& read(char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
#include<iostream>
using namespace std;
#include<fstream>
//二进制读文件
//可以操作不止int,double等数据类型,也可以操作自定义数据类型
class Person {
public:
char m_Name[64];
int m_Age;
};
void test01() {
//1.添加头文件
//2.创建流对象
ifstream ifs;
//3.打开文件 判断文件是否打开成功
ifs.open("person.txt", ios::in | ios::binary);
if (!ifs.is_open()) {
cout << "文件打开失败" << endl;
return;
}
//4.读文件
Person p;
ifs.read((char*)&p, sizeof(Person));
//5.关闭文件
ifs.close();
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;//姓名:张三 年龄:20
}
int main() {
test01();
system("pause");
return 0;
}
第十三篇笔记到此结束,C++基础学习会持续更新在C++学习笔记合集中,当作学习笔记复习,如果能帮助其他小伙伴就更好了。
笔记是看黑马程序C++时做的记录,笔记中如果有错误和改进的地方,欢迎大家评论交流,up up up!!!
学习原视频来自:黑马程序员C++从0到1