C/C++重难点总结系列(三)

21.关于malloc/free 和 new/delete的区别

(1)new/delete是C++中的操作符,而malloc/free是C库中的函数

(2)对于非内置类型,new不仅分配动态空间,还会调用构造函数创建对象并初始化成员,同理delete也会调用析构函数销毁对象并释放空间。而malloc/free只能分配和释放动态内存。因此,对于内置类型,考虑到效率优先使用malloc/free,对于自定义类型,只能使用new/delete。

22.关于何时需要自定义拷贝控制成员

 拷贝控制成员包括:拷贝构造函数、赋值运算符、析构函数

(1)通常如果一个类需要一个自定义的析构函数,那么可以肯定也需要自定义的拷贝构造函数和赋值运算符,三者应视作一个整体。

(2)包含动态分配成员的类,需要提供自定义拷贝构造函数并重载赋值运算符。(因为需要提供自定义析构函数来释放动态成员变量,由(1)知(2)成立)

23.关于阻止拷贝

某些类的拷贝操作没有意义,应当在定义类的时候就阻止拷贝,如iostream不应让多个对象读写相同的io流,故拷贝操作应该阻止。阻止的方法有两种:

(1)声明为删除函数:(C++11)

NoCopyClass(const NoCopyClass &) = delete;
NoCopyClass& operator=(const NoCopyClass &) = delete;

(2)将拷贝控制成员声明成private但不定义(传统方法,不推荐)

24.关于析构和构造在继承体系中的情况

(1)继承体系中处于根结点的基类必须定义一个虚析构函数

(2)构造函数(包括拷贝构造)需要显式调用基类的构造函数(拷贝构造函数),析构函数不需要,会自动调用。

(3)构造和析构的调用顺序相反:构造函数先调用基类的构造再调用派生类的构造,而析构函数先调用派生类的析构,

再调用基类的析构。

(4)只有构造函数可以继承:using MyBaseClass::MyBaseClass;//派生类中作此声明后会继承基类的所有非默认构造函数

(编译器自动生成代码)。

25.关于直接管理动态内存的问题

直接使用new/delete管理动态内存的常见问题如下:

(1)忘记delete。只分配未释放会导致内存泄露,大量严重的内存泄露会导致程序崩溃。直接使用new/delete管理的时候,

应尽量避免返回堆空间的函数作为接口。

(2)使用释放过的对象。内存释放后指针处于空悬状态(悬空指针),继续使用将是未定义的,可能会访问非法内存出现随机值。

(3)同一块内存delete多次。当两个及以上指向同一动态对象的指针(共享指针)存在时,容易发生这种多次释放的情况。

(4)分配与释放不匹配。如多次new只有一次delete(分配和释放次数不匹配),又如使用new Type[]时未用 delete []Type 释放。

改进方法:

(1)每次new过的内存,都要检查指针的有效性。

(2)尽量使用智能指针管理动态内存。shared_ptr会关联一个引用计数器,当计数值为0时会自动释放,防止泄露并避免多次释放。

(3)delete之后将指针置空。空指针至少比悬空指针安全,悬空指针和野指针一样是产生未定义的错误。但这种方法对于指向同一对象的多个指针往往保护有限。

26.关于野指针和指针越界问题

(1)避免野指针:要么指针定义的时候直接初始化,指向一个明确的对象;要么给指针动态分配内存。若定义时不确定指向,可先赋值NULL(C++为nullptr),但使用前一定要给一个确定的指向(赋值或动态分配),否则函数传参时传入空指针也是未定义的!

(2)指针越界:指针作用于数组时可能会越界访问。指针指向首元素的前一个位置是非法的,但指向末尾元素的后一个位置是合法的,不过不能解引用访问,否则访问越界。

27.关于二维数组与指针数组传参

不管是什么数组,数组传参只需抓住一点:数组传参实际是传递指向数组首元素的指针

(1)二维数组传参:因为二维数组的首元素是数组,故应传入指向数组的指针(一维数组指针)。如:

void func(int matrix[][10]);//直接用二维数组原型
void func(int (*matrix)[10]);//与直接传二维数组等价,上式会隐式转化为此式。
(2)指针数组传参:因为指针数组的首元素是指针,故应传入指向指针的指针(二级指针)。如:

void func(int *ptrArr[]);//直接传指针数组
void func(int **ptrArr);//与直接传指针数组等价,上式会隐式转化为此式
注:(1)和(2)中的不等价!含义不同。相对于二维数组传参,指针数组传参更灵活,每个元素可以长短不一。

28.关于宏的陷阱

(1)宏不是函数:宏定义中最好把所有的参数均用括号括起来(包括最外层),防止直接展开产生优先级错误。并且尽量避免使用++、--,以免计算多次。如:

#define abs(x)  (((x)>=0)?(x):(-x))

(2)宏不要作类型定义:不要用宏做类型定义,应使用typedef。如:

#define T1 struct Exp*
typedef struct Exp* T2;
(3)宏不要声明常量:尽量用const 和 enum 替代。

29关于编译器合成的成员函数

(1)若未声明构造、析构、拷贝构造、赋值符。编译器会自动生成一个合成版本(default版),但有局限性:

a.析构函数是非虚的。   b.成员中有const和引用时不会产生赋值符。
(2)若不想拷贝/赋值对象,应明确阻止拷贝,否则会产生合成版本。

30.关于函数模板和函数重载

(1)均属于编译期多态(静态多态),函数重载在调用时通过函数匹配机制来选择对应的函数,函数模板在实例化时自动生成特定类型的函数代码。

(2)函数重载的形参类型和个数都可以不同,函数模板是形参类型不同但个数相同。故函数模板不能完全代替函数重载。

(3)函数模板的实参类型是类类型时,必须对模板定义中用到的运算符进行重载。




  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ctrlturtle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值