《More Effective C++》学习笔记(一)

序.在不支持新特性的编译器上实现bool类型

1.使用global的enum枚举类型

enum bool {false, true};

    缺点:false和true实际上是int类型,在某些情况下会出现意料之外的情况,如:

void f(int);
void f(bool);
int x,y;
f( x < y );

提示:x < y 的结果理应是bool类型,因为是比较的结果; 

当你希望 f 匹配的的是f(bool)时,实际上匹配的是f(int)

2.使用typedef定义bool

typedef int bool;
const bool false = 0;
const bool true = 1;

    优点:移植到支持bool类型的编译器平台后,行为不会改变;   

    缺点:当函数进行重载时无法区分bool 和 int,因为 int == bool ?

条款 1 : 仔细区别 pointers 和 references

    1.1 references 必须指向某个对象,初始化的时候必须有初值;

    1.2 pointers目的是用来指向(代表)一个对象或者不指向(代表)某个对象,初始化时允许不存在初值;

    1.3 将references引用至null的行为会导致不可预期的行为

char *pc = 0;
char &rc = *pc;    

    1.4 references 必须指向某个对象这一事实意味着可以不需在reference使用之前进行测试有效性,更具效率;

    1.5 pointers 可以被重新赋值, reference只能指向(代表)初始化时的初值,另一种程度上,reference比较像是指向(代表)的对象的别名; 

string s1("S1");
string s2("S2");
string *ps = &s1;
string& rs = s2;
ps = &s2;            //ps指向 s2
rs = s1;             //等同于 s2 = s1, s2的值现在是"S1"

    1.6 使用的情况: 当考虑指向的对象不确定或者会改变的时候使用pointers,当指向的对象是确定的而且不会改变时,那么就是用reference,特殊情况下,当实现operator[]操作符时,需要特别的返回“能被当作assignment赋值对象”的东西:

vectoe<int> v(10);
v[5] = 10;
//如果operatorp[]返回的是pointer,那么上述的语句就应该是如下的样子,所以应该返回reference,有个例外,见条款30
*v[5] = 10;

条款 2:最好使用C++转型操作符

    旧式转型的缺点:并不能很好的表达转换的目的, 而且因为写法的原因,难以识别是否使用了强制转型;

    C++新的转型操作符(cast operators):

    2.1 static_cast<type>(expression);

      基本上拥有和 c 旧式转型相同的威力和意义,以及相同的限制,例如不能将一个struct转换成int,或者double转换成pointer,不能移除常量性;

    2.2 const_cast<type>(expression);

       用于改变表达式的常量性(constness)或变易形(volatileness),通过const_cast强调,唯一改变的是某表达式的常量性和变易性;

    2.3 dynamic_cast<type>(expression);

       用于执行继承体系中的“安全的向下跨系转型动作“,也就是可以利用dynamic_cast,将“指向base class object的pointers 或 reference”转型为“指向derived(或sibling base)class objects 的pointers 或 reference”,并得知转型是否成功,如果转型失败,会得到一个null指针(当转型对象是pointers)或一个exception(当转型对象是reference)

class Widget{...};
class SpecialWidget: public Widget{...};
Widget *pw;
void update(SpecialWidget* psw);
update(dynameic_cast<SpecialWidget*>(pw));

void updateViaRef(SpecialWidget&);
updateViaRef(dynameic_cast<SpecialWidget&>(*pw));

       dynameic_cast只能用于继承体系中,无法应用在缺乏虚函数的类型上(条款24),也不能改变类型的常量性;

    2.4 reinterpret_cast<type>(expression);

        这个操作符的结果是与编译平台相关,所以不具备有移植性,reinterpret_cast 的常用用途是转换“函数指针”类型:

typedef void (*FuncPtr) ();    //FuncPtr是个函数指针,指向某个函数,该函数入参为空,返回值为void
FuncPtr funcPtrArray[10];      //funcPtrArray是个函数指针数组,大小为10

int doSomething();
funcPtrArray[0] = &doSomething;    //错误,类型不匹配
funcPtrArray[0] = reinterpret_cast<FuncPtr>(doSomething);    //现在可以通过编译了

      某些情况下转型的操作会导致不正确的结果(条款31),应该尽量避免将函数指针转型;

      可以利用宏来仿真static_cast,const_cast,reinterpret_cast

#define static_cast(TYPE, EXPR) ((TYPE)(EXPR))
#define const_cast(TYPE, EXPR) ((TYPE)(EXPR))
#define reinterpret_cast(TYPE, EXPR) ((TYPE)(EXPR))

条款 3: 绝对不要以多态方式(polymorphically)处理数组

      继承的特性是可以使用指向基类的指针或引用来操作派生类,这样的指针和引用,其行为可以称为是多态的(就像它们具有多重类型似的);

假设有如下的继承体系:

class BST{...};
class BalanceBST: public BST{...};

情况1:当在多态的情况下使用指针算术运算时(array[ i ] 代表的是 *( array + i ) , 入参的数组其实“退化”为指针了)

BST BSTArray[10];
BalancedBST bBSTArray[10];
void printBSTArray(ostream& s, const BST array[], int numElements){
    for(int i = 0; i < numElements; ++i)
        s << array[i];
}
printBSTArray(cout, bBSTArray, 10);

这种情况下,array[ i ]的,编译器假设数组每一个元素的大小是sizeof( BST )与实际大小不一样,实际大小为派生类的大小,也就是sizeof( BalanceBST );

情况2:当尝试通过一个基类指针(基类指针指向由派生类组成的数组)

void deleteBSTArray(ostream& logstream, BST array[]){
    logstream << "Delete array at address "
              << static_cast<void*>(array) << '\n';
    delete[] array;
}

在delete[] array; 会产生“指针算术表达式”的存在,对数组中每一个元素以构造顺序相反的顺序调用destructor

for(int i = sizeof(array)/sizeof(BST) - 1; i >= 0 ; --i){    //sizeof(array)/sizeof(BST) 计算数组元素个数,非原书上的计算方式或编译器的计算方式,只是一种笔者表达
    array[i].BST::BST();
}

这种情况下,和情况1的情况基本相同,析构的类型和实际类型并不一致,会导致未定义的结果(结果不可预见);

条款4:非必要不提供default constructor

     当类的对象在没有外来的信息(输入)的情况下进行初始化,产生的对象将没有任何的意义,则不必提供默认的构造函数;

     当类缺乏默认构造函数会出现的问题:

     情况 1:

          在产生数组的情况时,无法为数组的对象指定构造函数的自变量;

          假设存在如下的类:

class EquipmentPiece{
public:
   EquipmentPiece(int IDNumber);
   ...
};
EquipmentPiece bestPieces[10];        //错误,无法调用EquipmentsPiece ctors
EquipmentPiece *bestPieces = new EquipmentPiece[10];     //错误

        这种情况下可以解决的方法:

        方法一: 使用non-heap(栈内存)数组

int ID1,ID2,...,ID10;
EquipmentPiece bestPiece [] = {
  EquipmentPiece(ID1),
  EquipmentPiece(ID2),
  ...
  EquipmentPiece(ID10)
};
        缺点:这种方法没法使用在堆内存中

       方法二: 使用指针数组,而非“对象数组”

typedef EquipmentPiece* PEP;        //pointer to EP  == PEP
PEP bestPieces[10];                 //指针数组(栈内存)
PEP *bestPieces = new PEP[10];      //同样的(内存)
for(int i = 0; i < 10; ++i)          //数组每个指针指向一个对象
    bestPieces[i] = new EquipmentPiece( ID Number);

        这种方法存在一定的缺点:

            1. 需要记得将对象的所有对象删除,如果忘记会出现内存泄漏的问题;

            2. 需要的内存总量比较大,因为指针自身的存在也是需要空间的(这个可以使用plcaement new 解决,见条款8);

    情况2:不适用于template-based container classes(容器类模板),因为被实例化的“目标类型”必须得有一个默认构造函数,在模板内几乎总是会产生一个以“templates类型参数”作为类型而架构起来的数组(初始化时会需要使用到默认构造函数)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值