文章目录
- 异常
- 概念:
- **抛出异常:**
- 关键字:
- **捕获异常:**
- **栈解旋:**
- **异常的接口声明:**
- **异常对象的生命周期:**
- 1 传递异常对象
- 【不使用】
- 2 传递异常对象指针
- 【不使用】
- 3 传递异常对象引用
- 【**最优解**】
- **异常的多态:**
- 标准异常库:
- 自定义异常类:【了解】
- 最需注意的点:
- 拷贝构造
- 析构函数
- 必须手动回收
- 野指针与空指针
- 虚函数与纯虚函数
- 虚析构造与纯虚析构
- 类模版
异常
概念:
程序中因硬件或代码编写时考虑不足导致的程序崩溃
硬件问题: 不予处理
代码编写考虑不足: 要处理
分类:
- 编译时错误: 语法错误导致
运行时错误: 考虑不足导致
抛出异常:
关键字:
throw : 抛出
语法:
throw 数 据;
捕获异常:
语法:
try { } catch(数据类型1 变量名1) { } catch(数据类型2 变量名2) { } ......
示例:
#include <iostream> using namespace std; void myDiv(int n01, int n02) { //什么情况下抛出异常 if (n02 == 0) { // throw 1; throw 'a'; // 这里抛出的 就是catch 接收的值 } } int main(int argc, char const *argv[]) { try { cout << "1111" << endl; myDiv(10, 0); cout << "222" << endl; } catch (int e) { cout << "int 除0了" << endl; } catch (char e) { cout << "char 除0了" << endl; } return 0; }
注意:
> 如果在try中出现异常,其try中剩余代码将不在执行,进入对应的catch中
> catch中变量的值就是抛出异常时throw后的数据
> catch可以有多个
栈解旋:
只能解旋栈区的东西 堆区的没戏 new的 都得自己去释放
概念:
> 当try中出现异常,其异常代码之上创建的对象都会被释放 > 其释放顺序与创建顺序相反 > 这种情况称为栈解旋 注意: new创建的对象在堆区,无法自动释放
#include <iostream>
using namespace std;
class Data
{
public:
Data()
{
cout << "构造函数" << endl;
}
~Data()
{
cout << "析构函数" << endl;
}
};
class Data02
{
public:
Data02() { cout << " Data02 构造函数" << endl; }
~Data02() { cout << " Data02 析构函数" << endl; }
};
int main(int argc, char const *argv[])
{
try
{
//Data *d01 = new Data();
//Data02 *d02 = new Data02();
Data02 d02;
Data d01;
throw 1;
}
catch (int e)
{
cout << "xxxx" << endl;
}
return 0;
}
异常的接口声明:
语法:
返回值类型 函数名(形参列表) throw (可能抛出的异常1 , 可能抛出的异常2 ,...) { 函数体; }
注意:
如果 throw() 就是里面没东西
说明当前函数没用异常
throw() == noexcept 没有异常
// 异常的接口声明 #include <iostream> using namespace std; // 此时在VSCode会显示红色,但是语法没有问题 void myDiv(int x, int y) throw(int, char) { if (y == 0) { throw 1; } cout << x / y << endl; } int main(int argc, char const *argv[]) { try { myDiv(10, 0); } catch (int e) { } catch (char e) { } return 0; }
异常对象的生命周期:
1 传递异常对象
【不使用】
缺点: 占用内存大
此时会触发拷贝构造,会形成一个新的异常对象,就得销毁这两个对象
示例:
#include <iostream> using namespace std; class MyException { public: MyException() { cout << "构造函数被调用" << endl; } MyException(const MyException &e) { cout << "拷贝构造被调用" << endl; } ~MyException() { cout << "析构函数被调用" << endl; } }; int main(int argc, char const *argv[]) { try { // MyException():创建了MyException的一个对象,该对象没有对象名,称为匿名对象 throw MyException(); } catch (MyException e) { } return 0; }
2 传递异常对象指针
【不使用】
缺点:会造成内存泄漏
传递异常对象,创建一次 但是不销毁 因为没delete
#include <iostream> using namespace std; class MyException { public: MyException() { cout << "构造函数被调用" << endl; } MyException(const MyException &e) { cout << "拷贝构造被调用" << endl; } ~MyException() { cout << "析构函数被调用" << endl; } }; int main(int argc, char const *argv[]) { try { // 传递的是指针 throw new MyException(); } catch (MyException *e) { } return 0; }
3 传递异常对象引用
【最优解】
传递异常对象引用,只会创建一次,而且可以自动销毁
示例:
#include <iostream> using namespace std; class MyException { public: MyException() { cout << "构造函数被调用" << endl; } MyException(const MyException &e) { cout << "拷贝构造被调用" << endl; } ~MyException() { cout << "析构函数被调用" << endl; } }; int main(int argc, char const *argv[]) { try { // 传递的是异常对象的引用 throw MyException(); } catch (MyException &e) { } return 0; }
异常的多态:
注意:
1 抛出的子类异常,可以被父类异常类型接收 2 抛出的子类异常,catch 中 有父类异常与子类异常类型,此时按代码顺序书写接收,建议先子后父
示例
#include <iostream> // 异常的多态 using namespace std; class MyException {}; class NullException : public MyException {}; int main(int argc, char const *argv[]) { try { throw NullException(); } catch (NullException &e) { cout << "NullException" << endl; } catch (MyException &e) { cout << "MyException" << endl; } return 0; }
标准异常库:
概述:
由c++提供的一套异常相关的类
自定义异常类:【了解】
步骤:
-
1 自定义异常类 使其继承于 exception 获得其子类
-
2 定义一个变量记录异常信息
-
3 定义该类的构造函数,拷贝构造,析构函数【只有析构需要判断是否为空,拷贝不用会重复释放野指针出现段错误】
-
4 重写 what 函数
const char* what() const noexcept { return 步骤2定义的变量 }
-
注意
编译使用需加 -std=c++11
最需注意的点:
拷贝构造
何时触发调用:
当 对象A以对象B进行初始化
如:
class Data {};
Data b; //创建对象
Data a = b; // 将b 赋值给a 对象b 以对象a进行初始化 就是a b 都是单独的
method(Data d)
{
}
method(b);//Data d = b; 将 b 赋值 给 d 触发拷贝构造
Data method()
{
static Data d;
return d;
}
Data c = method();//Data c = d 将d 赋值给 c
析构函数
调用时机: 对象销毁前
- 生命周期
- 局部变量:随着所在的函数的调用而生成,随着所在函数的执行完毕而销毁
- 成员变量:随着所在的对象的创建而生成,随着所在的对象销毁而销毁
- 全局变量:随着所在的程序启动而生成,随着程序的关闭而销毁
- 静态局部变量:随着所在函数的第一次调用而生成,随着所在程序的执行完毕而销毁
- 静态成员变量:随着所在的类的加载而生成,随着所在程序的执行完毕而销毁
- 静态全局变量:随着所在的程序启动而生成,随着程序的关闭而销毁
堆区开辟的内存
必须手动回收
class Data
{
};
int *method()
{
int *num = (int *)calloc(1, 4);
char *str = (char *)calloc(50, 1);
// Data d;
Data *d = new Data();
return num;
}
int main()
{
int *p = method();
}
野指针与空指针
> 指针存储的地址是随机的 有可能指向 堆区 有可能指向栈区 或者其他区 是不可控的 因为栈区的会自动释放 所以当指向栈区的时候程序不报错 但是这是不可控的 > 空指针存储的地址是 NULL 注意:对象的成员变量的值默认为 随机数 所以 一定注意 拷贝函数的时候不要判断是否不为空并释放 因为 成员变量默认是随机数 所以就不是 空的 你一旦释放 因为是随机的所以指针就是野指针 释放野指针就会触发重复释放的核心段错误 所以 写的时候 只有 析构的时候需要进行判断 而且要注意继承的情况
class Data { public: int x; char *str; Data() : x(0), str(NULL) { } Data(int x, char *str) : x(x) { int len = strlen(str) + 1; this->str = (char *)calloc(len, 1); strcpy(this->str, str); } Data(const Data &d) { this->x = d.x; int len = strlen(d.str) + 1; this->str = (char *)calloc(len, 1); strcpy(this->str, d.str); } ~Data() { if (str != NULL) { free(str); str = NULL; } } }; Data d1; cout << d1.x << endl; Data d2(10, "张三");
虚函数与纯虚函数
虚函数:
有函数体,所在的类,可以创建对象,正常继承,子类重写父类虚函数,子类对象转换
为父类对象后调用该函数执行的子类重写的该函数
纯虚函数:没有函数体,所在的类不能直接创建对象,可以继承,但是子类要么也是抽
象类,要么重写其所有纯虚函数重写的纯虚函数也是虚函数
虚析构造与纯虚析构
-
应该释放的是父 放父 子也释放
-
放子 只释放了父 子本身没释放
类模版
class 类名 : public 父类名
{
private:
成员变量
public:
无参构造函数
有参构造函数
基本类型 用 =
指针类型 考虑要不要深拷贝
拷贝构造
基本类型 用 =
指针类型考虑要不要深拷贝
virtual 析构函数
释放深拷贝在堆区的空间
get const
set 特有函数
}