编写高质量代码——“零星”总结(续2)

new delete与new[]delete[]必须配对使用

//注意,由于内置数据类型没有构造、析构函数,所以针对内置数据类型时,释放和内存使用delete或delete[]的效果是一样的。

例如:
int *pArray = new int[10];
...//processing code
delete pArray;  //等同于delete[] pArray;

虽然针对内置类型,delete和delete[]都能正确地释放所申请的内存空间,但是如果申请的是一个数组,建议还是使用delete[]形式。

===================================

new内存失败后的正确处理
//新的标准里,Test-for-NULL处理方式不再被推荐和支持:抛出一个bad_alloc exception (异常)
//推荐使用 try{ p = new int[SIZE]; } catch ( std::bad_alloc ) { ... } 
//只有负责内存申请的 operator new 才会抛出异常 std::bad_alloc

===================================

new内存失败后的正确处理

了解new_handler的所作所为

借助工具监测内存泄漏问题

小心翼翼地重载operator new operator delete

用智能指针管理通过new创建的对象

===================================

使用内存池技术提高内存申请效率与性能

//当程序中需要对相同大小的对象频繁申请内存时,常会采用内存池(Memory Pool)技术来提高内存申请效率,减少内存碎片的产生。

----------------------------------
//基本原理(“池”):应用程序可以通过系统的内存分配调用预先一次性申请适当大小的内存块(Block),并且将它分成较小的块(Smaller Chunks),之后每次应用程序会从先前已经分配的块(chunks)中得到相应的内存空间,对象的分配和释放的操作都可以通过这个“池”完成。只有当“池”的剩余空间太小,不能满足应用程序需要时,应用程序才会在调用系统的内存分配函数对其大小进行动态扩展。(和malloc差不多,进行一次的系统调用,分配33页,不够再申请)

----------------------------------

//经典的内存池实现原理:指针变量pMem把所有申请的内存块(MemBlock)串成一个链表。指针变量pFree把所有自由的内存结点(FreeNode)串成一个链表。 内存块在申请之初就被划分为了多个内存结点,每个结点的大小为ItemSize(对象的大小),共计MemBlockSize/ItemSize个。
//每次分配的时候从MemBlock链表中取一个结点给用户,不够时继续向系统申请大块内存。
//在释放内存时,只须把要释放的结点添加到自由内存链表pFree中即可。
//在MemPool对象析构时,可完成对内存的最终释放。

===================================

明晰class与struct之间的区别

//面向过程的编程认为,数据和数据操作应该是分开的。

//面向对象的C++认为,数据和数据的操作是一个整体。
===================================
了解C++悄悄做的那些事
//所有的类都有着一个类似的中枢骨干(“Big Three”):一个或多个构造函数 + 一个析构函数 + 一个拷贝赋值运算符
//=delete        =default
//对于类,编译悄悄完成的事:隐式产生默认构造函数、拷贝构造函数、拷贝赋值运算符和析构函数。
//为了能够实例化,编译器“强制”使其大小由 0 变成 1。
===================================

明智地拒绝对象的复制操作
//禁止对象复制操作(拷贝构造函数 和 赋值操作符):
方式一、通过类的访问控制private(一方面阻止了编译器越俎代庖的声明和定义,另一方面禁止对复制操作函数的显示调用)
如下:
class CStudent{
public :
    ...
private :
    CStudent ( const  CStudent&  other );
    CStudent operator=( const  CStudent&  rhs );
}//但是,成员函数和友元函数仍可以访问,C++赋予了它们调用 private 成员函数的权利。
---------------------------------

方式二、只声明不定义,那么成员函数和友元函数对它们的调用,编译器会明令禁止(声明而不定义成员函数是合法的,但是使用未定义成员函数的任何尝试将导致链接失败),即实现了类复制的完全禁止。
================================

小心,自定义拷贝函数(拷贝构造函数和赋值运算符)
//自定义拷贝函数是一种良好的编程风格,可以阻止编译器形成默认的拷贝函数,但应该保证拷贝一个对象的 All Parts:所有数据成员及所有的基类部分

==========

======================

多态基类的析构函数应该为虚
//C++中指出:当一个派生类对象通过使用一个基类指针删除,且这个基类有一个非虚的析构函数时,C++将不会调用整个析构链,只是调用了基类的析构函数,即“部分析构”。

--------------------------------

//解决:将基类的析构函数设置为虚,用基类指针删除一个派生类对象时,C++会正确地调用整个析构链,销毁整个对象。

--------------------------------
//使用虚析构函数要遵守的原则:(基)类中至少有一个虚函数。反之,如果类中有虚函数,它就该有一个虚析构函数。
//如果一个类不包含虚函数,不要将析构函数声明为虚。因为虚函数的实现要求对象携带额外的信息,会导致对象的大小增加。

--------------------------------

//普通继承:自动调用基类和派生类的析构函数。
//多态:有多态,基类一定有虚成员函数,有虚成员函数,析构函数就要声明为virtual。

================================

绝不让构造函数为虚

//假设构造函数是虚函数,那么就需要通过虚函数表来调用应该调用的函数,但此时面对的是一块 raw 内存,虚函数表尚未建立,不能支持虚函数机制(虚函数表由构造函数建立),故构造函数不能为虚函数。

================================

默认参数在构造函数中给你带来的喜与悲
//如果不合理的使用默认参数,将会导致重载函数的二义性。

================================

特殊的自增自检运算符重载
//T& operator++();

const T operator++(int);/* 后缀形式返回一个const对象:1.不违背运算符的自然语义 2.后缀返回一个临时对象,返回值不是原对象实体(应禁止) */

================================

合理地使用 inline 函数来提高效率

//内联函数具有与宏定义相同的代码效率,但是其他方面却优于宏定义。因为内联函数还遵循函数的类型和作用域规则,所以它能像一般的函数一样进行调用与调试。

//在大多数C++程序中,内联是一个编译时行为。因为编译器只有首先了解了函数的大致情况后,才能在函数调用出用函数体代替之,因此内联函数一般情况下都被定义在头文件中。两种如下方式:
1)显式方式:在函数定义之前添加 inline 关键字
2)隐式方式:将函数定义于类的内部
C++标准规定:如果在类内部定义了函数体的函数,则默认其为内联函数,而不管是否有 inline 关键字。这种方式在定义类中存取私有变量的函数时,应用很广泛,具有较高的效率。
//内联函数,注意:
1)内联函数的定义必须出现在内联函数第一次被调用之前(一般会置于头文件中)。
2)在内联函数内不允许用循环语句和开关语句,函数不能过于复杂(递归函数)。
3)依据经验,内联函数适合于只有 1-5 行的小函数。
4)不要对构造/析构函数进行内联。
5)大多开发环境下不支持内联调试,为了调试方便,不要将内联优化放在调试阶段之前。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值