缠丝猫 发表于 2022-6-25 08:52:59

C++ 深度解析教程(十八)拾遗:令人迷惑的写法、技巧:自定义内存管理、异

六十八、拾遗:令人迷惑的写法

1、令人迷惑的写法



[*]下面的程序想要表达什么意思?
https://img-blog.csdnimg.cn/0c6563cdba7b498c808fb37b39f2cecc.png


[*]历史上的原因。。。   

[*]早期的C++直接复用class关键字来定义模板
[*]但是泛型编程针对的不只是类类型
[*]class关键字的复用使得代码出现二义性

[*]typename诞生的直接诱因   

[*]自定义类类型内部的嵌套类型
[*]不同类中的同一个标识符可能导致二义性
[*]编译器无法辨识标识符究竟是什么

2、编程实验:模板中的二义性

https://img-blog.csdnimg.cn/c40068bb68d14cf4948a19f105c5259d.png
3、令人迷惑的写法



[*]typename的作用:   
[*]在模板定义中声明泛指类型
[*]明确告诉编译器其后的标识符为类型

4、下面的程序想要表达什么意思?

https://img-blog.csdnimg.cn/71966085f7154301b238c540e08fbe38.png
5、令人迷惑的写法



[*]try ... catch 用于分隔正常功能代码与异常处理代码
[*]try ... catch 可以直接将函数实现分隔为2部分
[*]函数声明和定义时可以直接指定可能抛出的异常类型
[*]异常声明成为函数的一部分可以提高代码可读性


[*]函数异常声明的注意事项   

[*]函数异常声明是一种与编译器之间的契约
[*]函数声明异常后就只能抛出声明的异常   

[*]抛出其它异常将导致程序运行终止
[*]可以直接通过异常声明定义无异常函数


6、编程实验:新的异常写法

https://img-blog.csdnimg.cn/9520aed3f6cc45dbba5f1d2d1121740c.png
7、小结



[*]class可以用来在模板中定义泛指类型(不推荐)
[*]typename是可以消除模板中的二义性
[*]try...catch可以将函数体分成2部分
[*]异常声明能够提供程序的可读性
六十九、技巧:自定义内存管理

1、笔试题

统计对象中某个成员变量的访问次数
2、遗失的关键字



[*]mutable是为了突破const函数的限制而设计的
[*]mutable成员变量将永远处于可改变的状态
[*]mutable在实际的项目开发中被严禁滥用


[*]mutable的深入分析   

[*]mutable成员变量破坏了只读对象的内部状态
[*]const成员函数保证只读对象的状态不变性
[*]mutable成员变量的出现无法保证状态不变性

3、编程实验:成员变量的访问统计

https://img-blog.csdnimg.cn/7bde645c177a416c963ba68cb8eebf94.png
4、面试题

new关键字创建出来的对象位于什么地方?
5、被忽略的事实



[*]new / delete的本质是C++预定义的操作符
[*]C++对这两个操作符做了严格的行为定义   

[*]new :   

[*]获取足够大的内存空间(默认为堆空间)
[*]在获取的空间中调用构造函数创建对象

[*]delete:   

[*]调用析构函数销毁对象
[*]归还对象所占用的空间(默认为堆空间)




[*]在C++中能够重载new / delete操作符   

[*]全局重载(不推荐)
[*]局部重载(针对具体类进行重载)

[*]重载new / delete的意义在于改变动态对象创建时的内存分配方式


[*]new / delete的重载方式
https://img-blog.csdnimg.cn/08b15445221b478f91f284db2fe10cc7.png
6、编程实验:静态存储区中创建动态对象

https://img-blog.csdnimg.cn/5733c7ebef32437a87ee2685b26d6710.png
7、面试题

如何在指定的地址上创建C++对象?
8、设计思路



[*]解决方案   

[*]在类中重载new / delete操作符
[*]在new的操作符重载函数中返回指定的地址
[*]在delete操作符重载中标记对应的地址可用

9、编程实验:自定义动态对象的存储空间

https://img-blog.csdnimg.cn/58cb6a43b33446d191ee9baaf5c509c2.png
10、被忽略的事实



[*]new[] / delete[]与new / delete完全不同   

[*]动态对象数组创建通过new[]完成
[*]动态对象数组的销毁通过delete[]完成
[*]new[] / delete[]能够被重载,进而改变内存管理方式

[*]new[] / delete的重载方式
https://img-blog.csdnimg.cn/92d051fab37a4481b0e2d825de74fe9e.png


[*]注意事项   

[*]new[]实际需要返回的内存空间可能比期望的要多
[*]对象数组占用的内存中需要保存数组信息(数组长度)
[*]数组信息用于确定构造函数和析构函数的调用次数

11、编程实验:动态数组的内存管理

https://img-blog.csdnimg.cn/f3a79fac2de14e28ade401acb3a11d0b.png
12、小结



[*]new / delete的本质为操作符
[*]可以通过全局函数重载new / delete(不推荐)
[*]可以针对具体的类重载new / delete
[*]new[] / delete[] new / delete 完全不同
[*]new[] / delete[]也是可以被重载的操作符
[*]new[]返回的内存空间可能比期望的要多
七十、异常处理深度解析

1、异常处理深度解析



[*]问题   

[*]如果在main函数中抛出异常会发生什么?

[*]如果异常不处理,最后会传到哪里?
https://img-blog.csdnimg.cn/0c3649e7f8a947caaa99a1454939f322.png


[*]下面的代码输出什么?
https://img-blog.csdnimg.cn/80684cd6bdc640919fe4242ac894ea53.png
2、编程实验:异常的最终处理?

https://img-blog.csdnimg.cn/44c3906256094eaab494199a60f648e7.png
3、异常处理深度解析



[*]如果异常无法被处理,terminate()结束函数会被自动调用
[*]默认情况下,terminate()调用库函数abort()终止程序
[*]abort()函数使得程序执行异常而立即退出
[*]C++支持替换默认的terminate()函数实现


[*]terminate()函数的替换   

[*]自定义一个无返回值无参数的函数   

[*]不能抛出任何异常
[*]必须以某种方式结束当前程序

[*]调用set_terminate()设置自定义的结束函数   

[*]参数类型为void (*)()
[*]返回值为默认的terminate()函数入口地址


4、编程实验:自定义结束函数

https://img-blog.csdnimg.cn/7fc8bb62ac0c4fd8bda9208fd3464844.png
5、面试题

如果析构函数中抛出异常会发生什么情况?
6、编程实验:析构函数抛出异常

https://img-blog.csdnimg.cn/fc9ff45dde2947f4a3fb125abbf9128d.png
7、小结



[*]如果异常没有被处理,最后terminate()结束整个程序
[*]terminate()是整个程序释放系统资源的最后机会
[*]结束函数可以自定义,但不能继续抛出异常
[*]析构函数中不能抛出异常,可能导致terminate()多次调用
七十一、函数的异常规格说明

1、函数的异常规格说明



[*]问题
如何判断一个函数是否会抛出异常,以及抛出哪些异常?


[*]C++提供语法用于声明函数所抛出的异常
[*]异常声明作为函数声明的修饰符,写在参数列表后面
https://img-blog.csdnimg.cn/1a65a2d2a5d5497c838d6737918ef0d9.png


[*]异常规格说明的意义   

[*]提示函数调用者必须做好异常处理的准备
[*]提示函数的维护者不要抛出其它异常
[*]异常规格说明是函数接口的一部分

2、问题



[*]如果抛出的异常不在声明列表中,会发生什么?


[*]下面的代码输出什么?
https://img-blog.csdnimg.cn/cd166de6cfb946b29143248a9cf0fd08.png
3、编程实验:异常规格之外的异常

https://img-blog.csdnimg.cn/fe0f5a0f585744f2a216c2784e7dabb6.png
4、函数的异常规格说明



[*]函数抛出的异常不在规格说明中,全局unexpected()被调用


[*]默认的unexpected()函数会调用全局的terminate()函数


[*]可以自定义函数替换默认的unexpected()函数实现
[*]注意:不是所有的C++编译器都支持这个标准行为


[*]unexpected()函数的替换   

[*]自定义一个无返回值无参数的函数   

[*]能够再次抛出异常      

[*]当异常符合触发函数的异常规格说明时,恢复程序执行
[*]否则,调用全局terminate()函数结束程序


[*]调用set unexpected ()设置自定义的异常函数   

[*]参数类型为void (*)()
[*]返回值为默认的unexpected()函数入口地址


5、编程实验:自定义unexpected()函数

https://img-blog.csdnimg.cn/ad06f9187a244b759adb162bfc2d9bf7.png
6、小结



[*]C++中的函数可以声明异常规格说明


[*]异常规格说明可以看作接口的一部分
[*]函数抛出的异常不在规格说明中,unexpected()被调用
[*]unexpected()中能够再次抛出异常   

[*]异常能够匹配,恢复程序的执行
[*]否则,调用terminate()结束程序


七十二、动态内存申请的结果

1、动态内存申请的结果



[*]问题   

[*]动态内存申请一定成功吗?

[*]常见的动态内存分配代码
https://img-blog.csdnimg.cn/218a7f755a054ce7a5c97fbd2f074c42.png


[*]必须知道的事实!   

[*]malloc函数申请失败时返回NULL值
[*]new关键字申请失败时(根据编译器的不同)   

[*]返回NULL值
[*]抛出std::bad_alloc异常


2、问题



[*]new 语句中的异常是怎么抛出来的?
[*]new 关键字在C++规范中的标准行为   

[*]在堆空间申请足够大的内存   

[*]成功:      

[*]在获取的空间中调用构造函数创建对象
[*]返回对象的地址

[*]失败:      

[*]抛出std::bad_alloc异常



[*]new关键字在C++规范中的标准行为   

[*]new在分配内存时   

[*]如果空间不足,会调用全局的new_handler()函数
[*]new_handler()函数中抛出std::bad_alloc异常

[*]可以自定义new_handler()函数   

[*]处理默认的 new内存分配失败的情况


[*]new_handler()的定义和使用
https://img-blog.csdnimg.cn/7067a90dc8714ccd9b621a691d96e5a6.png
3、问题



[*]如何跨编译器统一new的行为,提高代码移植性?
[*]解决方案   

[*]全局范围(不推荐)   

[*]重新定义new / delete 的实现,不抛出任何异常
[*]自定义new_handler()函数,不抛出任何异常

[*]类层次范围   

[*]重载new / delete,不抛出任何异常

[*]单次动态内存分配   

[*]使用nothrow参数,指明new不抛出异常


4、编程实验:动态内存申请

https://img-blog.csdnimg.cn/adbb19a338b340ce8704eb7680ad320f.png
5、动态内存申请的结果



[*]实验结论   

[*]不是所有的编译器都遵循C++的标准规范
[*]编译器可能重定义new的实现,并在实现中抛出bad_alloc异常
[*]编译器的默认实现中,可能没有设置全局的new_handler()函数
[*]对于移植性要求较高的代码,需要考虑new的具体细节
[*]vs2010 new实现方式


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: C++ 深度解析教程(十八)拾遗:令人迷惑的写法、技巧:自定义内存管理、异