4. 类动态内存分配
4.1 C语言动态内存分配:malloc和free
4.2 C++动态内存分配:new和delete
- 思考:定义一个对象和定义一个普通变量有何区别?
- 普通变量:分配足够空间即可存放数据
- 对象:除了需要空间,还要构造/析构
- 类比:
- malloc/free:租用一个仓库->存放货物 ->货物卖完 ->退租走人
- new/delete:租一块地 ->修建大厦 (构造)->到期不用了 ->爆破拆除善后(析构) ->地归原主
- 调用默认构造函数,那么new+类名即可
- 调用带参数的构造函数,那么new+类名(参数)
4.2.1 new[ ]与delete[ ]运算符
- new[ ]:申请空间并批量构造对象数组,返回首元素指针
- delete[ ]:批量析构对象数组并释放空间,只能用于new[ ]出来的指针
4.2.2 malloc/free和new/delete的区别
4.2.3 虚析构函数
4.2.3.1 内存泄漏和内存溢出
-
内存溢出
-
内存泄漏
4.2.3.2 虚析构函数
4.3 例题
5. 异常处理
5.1 异常与bug
异常与bug的区别
- 对于Bug:程序员可控,越用心进行开发/走查/测试,Bug越少
- 对于Exception:程序员不可控,需要代码中设置针对异常情况的保护措施
- 网络应用:网络连通性、时延等不可控因素对于程序来说是异常
- 游戏:玩家打出极限操作、突破预先设置的情况是异常
- 数据分析:数据缺失值、未曾料想的特殊值等情况是异常
- 等等
- 除以0异常(不除以0就是正常的)
- 空指针异常(指针不空就是正常的)
- 数组越界异常(数组不越界就是正常的)
- int溢出异常(int不太大就是正常的)
总之,程序正确编译了,也能按照既定逻辑执行功能,但是遇到了合法范围外的数据导致程序失去确定性而中断就是异常
5.2 异常处理
异常会导致程序失去确定性,不知道怎么继续下去,于是有异常保护:
- 异常保护:引导程序继续走下去,再不济也要做好收尾工作,体面结束
5.2.1 throw和try-catch
- throw:使用throw语句主动立即抛出一个异常对象(但不一定throw才抛异常,如n/0)
- throw抛出异常后会直接去执行catch对应的语句,原来异常语句下面的不会执行
- try:保护一个语句块,后跟一个或多个catch块。try块中抛出异常不会立刻中止程序而是寻找catch处理
- catch:捕获指定异常并进行处理,处理完不会再返回try块,直接离开try-catch继续执行
5.2.2 多个catch,捕获顺序:从详到略
5.2.3 多层函数调用中抛出异常
- 当前层函数无法处理异常就结束此层,在稍外层继续抛出该异常,如果还是无法解决继续向更外层抛出异常,直到到达某层函数能够处理该异常,处理完毕后继续执行处理该异常的函数调用层
5.2.4 函数异常限定符
5.3 C++标准异常类
- 虽说我们可以手动throw,抛出什么都可以,但一些常见的程序错误是C++自身抛出的(如除以零)
- C++根据错误分类根据预设了一系列标准异常,定义在<exception>中
- 它们以父子类层次结构组织,都在名字空间 std 下
5.3.1 自定义异常类
- 自定义异常类通常继承自std:exception,并重写一些方法,比如what()
- 通常在自己编写的程序中会根据业务需求自定义异常类
5.4 例题
6. 模板
6.1 模板与泛型思想
6.1.1 函数重载为了实现代码复用做出的努力与不足
6.1.2 模板编程的目的
- 回顾:从函数 ->函数重载 ->面向对象 ->继承/多态 ->… 核心目的?
- 代码复用
- 但以上技术仍然是类型依赖的,需要为不同类型进行相应的实现,如函数重载
- 即“数据类型”信息是一个超参数,代码逻辑依赖这个超参数信息
- 模板编程:类型参数化,将类型信息也视作一个普通参数,使代码逻辑与类型信息分离
6.2 模板函数
- 用模板函数对不同类型变量进行自定义的拼接相加
- template 关键字:表明接下来定义一个模板
- typename 关键字:表明该模板参数是一个类型名——>模板参数可以不是同一个类型
- T1、T2:是两个类型形式参数,是类型占位符
- 于是在myAdd函数中就可以用T1和T2指代数据类型,编写代码逻辑
模板函数实例化
- 如果传入不支持类型的参数,将会报编译错误
模板类型推断
模板参数可以用于返回值
6.3 模板类
非类型的模板参数
模板类的声明与定义不可分离
- 普通类可以模块化:在.h中声明,在.cpp 中定义(实现)
- 但模板类不可以这样操作,必须声明同时+定义,为什么?
- 因为编译器无法事先知道类型占位符T是什么,所以必须同时写
6.4 例题
- 模板的核心思想:类型参数化,不依赖数据类型来编写程序逻辑
- 模板可用于函数和类的编写:模板函数和模板类
- template:声明模板,typename:定义模板的类型参数
7. 文件读写流
7.1 文件读写流
- 回顾:cin 和 cout 叫做标准输入输出流对象,其机制类似一条河流
- 事实上,文件I0和使用cin/cout没有本质区别,标准I0只是文件I0的一个特例
- 文件输入输出流就是这条河流的来源/终点是一个文件
从标准IO到文件IO
- 现在我们知道cout是一个对象而<<是运算符重载,含义实际上是 cout.插入(“hello”
- cout 是标准输出流对象因此输出到控制台,如果换成文件输出流对象,就实现了往文件输出,输入流同理
文件IO:打开->读/写->关闭
文件IO:fstream
- 读文件要用ifstream对象,写文件要用ofstream对象,能不能就用一个流对象,又能读又能写?
- std::fstream
#include <iostream>
#include <fstream>
int main() {
std::ifstream fin;
std::ofstream fout;
fin.open("input.txt",std::ios::in);
fout.open("output.txt",std::ios::out);
if (fin.is_open()==false||fout.is_open()==false){
std::cout<<"Error"<<std::endl;
return 0;
}
std::string name;
int id;
float score;
while (fin.eof()==false){
fin>>name>>id>>score;
fout<<id<<" "<<name<<" "<<score<<std::endl;
}
fin.close();
fout.close();
return 0;
}
7.2 二进制文件读写
- 思考:通过文本文件存储整数123456789需要多少空间?
- 在文本文件中,将被存放成字符串“123456789”,共占9个字节
- 但它本来是个int,只占4字节呀!直接把这4字节写入文件就好了
- 二进制文件:将数据在内存中的实际存储内容直接写入到文件中,读取时需要知道存储和理解的格式
- 文本文件:将数据作为一串字符逐一输出到文件中,读取时再按照ASCII码逐字符解读为文本
#include <iostream>
#include <fstream>
int main() {
std::ofstream fout("data.bin",std::ios::out|std::ios::binary);
if (!fout.is_open()){
std::cerr << "Error opening file" << std::endl;
return 1;
}
int a = 10;char b = 'a';;float c = 3.14;
fout.write(reinterpret_cast<char*>(&a),sizeof(a));
fout.write(reinterpret_cast<char*>(&b),sizeof(b));
fout.write(reinterpret_cast<char*>(&c),sizeof(c));
fout.close();
std::ifstream fin;
fin.open("data.bin",std::ios::in|std::ios::binary);
if (!fin.is_open()){
std::cerr << "Error opening file" << std::endl;
return 1;
}
int a1;char b1;float c1;
fin.read(reinterpret_cast<char*>(&a1),sizeof(a1));
fin.read(reinterpret_cast<char*>(&b1),sizeof(b1));
fin.read(reinterpret_cast<char*>(&c1),sizeof(c1));
std::cout << a1 << std::endl << b1 << std::endl << c1 << std::endl;
fin.close();
return 0;
}
7.3 输出格式控制——流操作算子
- 在C语言中,通过printf()/fprintf()函数的格式控制符控制输出的格式
- 而在C++中,文件被封装成了流对象,控制流对象的格式,就能控制输出的格式
- 通过往流对象中插入流格式控制符控制流对象的输出格式
- 如希望控制输出小数时保留小数点后两位:
常用的流格式控制符
7.4 例题
- 文件在C++中以流对象的形式存在,通过流对象读写文件
- 文件输入流:ifstream文件输出流:ofstream文件读写流:fstream
- 流对象可以构造同时打开文件,也可以先定义,再调用open打开
- 打开文件时要注意使用正确的相对路径或绝对路径
- 操作流对象之前,要通过其is open 方法判断是否打开成功
- 读文件时,通过流对象的 eof()方法判断是否已经读到文件尾了
- eof即end of file
- 通过头文件中的流操作算子控制输出格式
- 以二进制模式读写文件将直接拷贝实际存储内容,需要自行编码解码
8. 番外:C与C++
C和C++是两种非常相关的编程语言,它们之间存在许多关联,但也有很多显著的差异。
关联:
- 起源:C++是C语言的一个超集,也就是说,几乎所有的C语言代码都可以直接在C++中编译和运行。C++的创造者Bjarne Stroustrup在设计C++时,主要的目标之一就是提供一个“更好的C”,这意味着C++保持了C语言的许多特性,并增加了许多新的特性。
- 基础语法:C和C++的基本语法非常相似,包括变量声明、条件语句、循环语句等。
- 库:许多C语言的库在C++中仍然可用,尽管C++也提供了许多新的库和功能。
异同:
异:
- 类型系统:C++是一种强类型语言,提供了更复杂的类型系统,包括类和对象,而C语言主要基于函数和指针。
- 面向对象:C++是一种面向对象的语言,支持类、继承、封装和多态等概念。而C语言主要是面向过程的。
- 异常处理:C++提供了异常处理机制,而C语言没有内置的异常处理机制,通常通过错误码和错误处理函数来处理错误。
- 模板:C++支持模板,这使得用户可以创建泛型函数和类,提高了代码的重用性。而C语言没有模板的概念。
- 运算符重载:C++允许用户重载运算符,以支持自定义类型的特殊运算。而C语言不支持运算符重载。
同:
- 内存管理:在C和C++中,程序员都需要直接管理内存,包括分配和释放。这可能会导致内存泄漏等问题,需要程序员小心处理。
- 跨平台性:C和C++都是跨平台的语言,可以在多种操作系统和硬件平台上运行。
- 性能:由于C和C++都接近硬件,且没有运行时的解释或虚拟机开销,因此它们通常都能提供很高的性能。
总的来说,C和C++在许多方面都非常相似,但C++提供了更多的功能和特性,使得程序员可以编写更复杂、更灵活的程序。然而,这也增加了C++的学习曲线和复杂性。选择使用哪种语言通常取决于项目的具体需求和开发团队的技能。