Effective C++读书笔记(条款01-04)

条款01:View C++ as a federation of languages

作者将C++分为四个部分

  • C:C++以C为基础,C++完全兼容C,所以在C++的编程过程中部分的写法本质上依然是C,但是C语言具有的局限有没有模板(template)、异常(exceptions)、重载(overloading)
  • Object-Oriented C++:包括classes、封装(encapsulation)、继承(inheritance)、多态(polymorphism)、virtual函数等
  • Template C++:C++的泛型,使得代码的复用性大大提高
  • STL:template程序库,写算法刷题神器。

 

条款02-03:使用const,enum以及inline

在C语言中,我们一般使用宏定义#define语句来声明某些常量或是函数,但是C++中推荐使用const、enum、inline等来替代#define的使用。事实上,#define语句在使用中有诸多不足,从原理上讲,在程序编译的预处理阶段,编译器会把#define声明的代码直接插入程序中,加入编写如下代码’

#include <iostream>
#define PLUS(x) x+x
using namespace std;
int main() {
    int x = 3;
    int ans = PLUS(x) * PLUS(x);
    cout << ans;
    return 0;
}

按照期望的结果,ans = (3 + 3) * (3 + 3)应为36,然而事实的答案是15,为什么会得到这个结果?因为预处理时仅仅是将x+x简单地把PLUS(x)替换掉所以该语句实际上是

int ans = x+x * x+x;

解决方法是将每个变量两边都加上括号

#include <iostream>
#define PLUS(x) ((x)+(x))
using namespace std;
int main() {
    int x = 3;
    int ans = PLUS(x) * PLUS(x);
    cout << ans;
    return 0;
}

这样的执行结果就是36

可是,这样就高枕无忧了吗,并没有

#include <iostream>
#define CALL_WITH_MAX(a, b) ((a) > (b) ? (a) : (b))
using namespace std;
int main() {
    int a = 5, b = 0;
    cout << CALL_WITH_MAX(++a, b) << endl;          //a被累加2次
    cout << CALL_WITH_MAX(++a, b + 10) << endl;     //a被累加1次
    return 0;
}

输出结果分别为7、10,其中第一次调用时a被累加两次,这不是期望出现的结果,这可以看出#define所具有的局限性,在c++中可以用const和inline关键字来解决这个问题

const int a = 5;
#include <iostream>
using namespace std;

inline int callWithMax(const int& a, const int& b) {
    return a > b ? a : b;
}

int main() {
    int a = 5, b = 0;
    cout << callWithMax(++a, b) << endl;
    cout << callWithMax(++a, b + 10) << endl;    
    return 0;
}
//输出结果为6, 10

对于const和enum还有如下使用方法

class GamePlayer {
private:
    static const int NumTurns = 5;  //常量声明,static关键字使得多个对象分享一个该常量的实体
    int scores[NumTurns];           //使用该常量
};
class GamePlayer {
private:
    enum {NumTurns = 5};            
    int scores[NumTurns];           
};

当然也可以这样

class GamePlayer {
private:
    static const int NumTurns;      //常量声明,static关键字使得多个对象分享一个该常量的实体
    int scores[NumTurns];           //使用该常量
};
const int GamePlayer::NumTurns = 6; //cpp文件中

关于const char*、char* const以及const char* const

 

int main() {
    const char* name = "Rong-Yan Xu";
    char* name2 = "Mike";
    name[0] = 'h';                      //不允许,name为const char*型,不允许被改变(const data)
    name = name2;                       //允许,char*为non-const pointer
    return 0;
}
int main() {
    char* const name = "Rong-Yan Xu";
    char* name2 = "Mike";
    name[0] = 'h';                      //允许,name为char*型,允许被改变(non-const data)
    name = name2;                       //不允许,char*为const pointer
    return 0;
}
int main() {
    const char* const  name = "Rong-Yan Xu";
    char* name2 = "Mike";
    name[0] = 'h';                      //不允许,name为const char*型,不允许被改变(const data)
    name = name2;                       //不允许,char*为const pointer
    return 0;
}

 const成员函数

const char& operator[](size_t pos) {    //传回的char引用可读不可写
        return text[pos];
    }
    char& operator[](size_t pos) {          //传回的char引用可读可写
        return text[pos];
    }

 值得一提的是,若制定函数返回类型为基本类型(非引用也非指针),就没有必要再返回值类型前加上const,因为即使没有加上const,你在改变返回值时,也只是再改变一个副本,这种改动没有意义。

另外,声明了non-const的返回值,就要拿一个non-const去接,声明了const的返回值就要拿一个const接

 

const修饰成员函数时,代表该函数内不允许修改成员函数的任何属性,即使该属性并没有被声明为const型,而mutable关键字可以破除这个限制

class GamePlayer {
private:
    char* pText;
    size_t slength;
    bool valid;
public:
    size_t length() const;
};

size_t GamePlayer::length() const {
    if(!valid) {
        slength = strlen(pText);        //不合法,const成员函数不允许改变对象内的任何值
        valid = true;                   //同上
    }
    return slength;
}

tips:

  • C++程序中应尽可能使用const来强制约束某些不该被改变的量,以免出现意料之外的错误

条款04:Make sure that object are initialized before they are used

确定一个对象被使用前已经先被初始化了

默认初始化的情况:

class Point {
    int x, y;       //在某些编译器下会默认初始化为0,某些情况下不会
};

struct Node{
    int x, y;       //在某些编译器下会默认初始化为0,某些情况下不会
};

int main() {
    int x;          //在某些编译器下会默认初始化为0,某些情况下不会
    return 0;
}

可见是否被默认初始化的情况并不被确定,而对某些未初始化的指针或者对象进行调用可能会发生爆炸,故需要学习一些初始化的方法。

对于C++的对象而言,其初始化的任务落在了构造函数上(constructor)上,一般的情况下,我可能会这么写一个构造函数(java出身。。)

GamePlayer::GamePlayer(char *pText, size_t slength, bool valid) { //是赋值而不是初始化
    this->pText = pText;
    this->slength = slength;
    this->valid = valid;
}

这样的写法是赋值而不是初始化,区别就是赋值是已经构造出这个对象之后在对里面的成员进行赋值,而初始化是在构造对象时,根据传进来的参数对成员进行初始化。初始化代码如下。

GamePlayer::GamePlayer(char *pText, size_t slength, bool valid) : pText(pText), slength(slength), valid(valid) {
    //do nothing
}

C++有十分固定的成员初始化顺序(父类先于子类,类的内部以成员声明次序初始化)

不同编译单元定义非本地的对象时初始化的行为

如下代码:

class GamePlayer {
private:
    char* pText;
    mutable size_t slength;
    mutable bool valid;
public:
    size_t length() const;
};

extern GamePlayer gp;               //预备给别的客户的对象,客户在别的编译单元内

class Client {                      //另一个编译单元的客户
public:
    Client();
};

Client::Client() {
    size_t x = gp.length();         //调用时无法确定gp是否被初始化
}

C++没有定义不同单元内非本地对象的初始化顺序,所以client在调用gp的函数时无法确定gp是否被初始化,解决方法如下

class GamePlayer {
private:
    char* pText;
    mutable size_t slength;
    mutable bool valid;
    static GamePlayer g;
public:
    size_t length() const;
    static GamePlayer& gp() {
        return g;
    }
};

class Client {                      //另一个编译单元的客户
public:
    Client();
};

Client::Client() {
    size_t x = GamePlayer::gp().length();         //调用时无法确定gp是否被初始化
}

类似于单例模式的做法

tips:

  • 总是在初值列中列出所有成员变量,以免还得记住哪些成员变量可以无需初值
  • 内置型对象进行手工初始化,C++不能保证初始化它们
  • 构造函数最好使用成员初值列,而不使用赋值,初值列的排列次序应该和它们在class中的声明次序相同
  • 为了免除“跨编译单元的初始化次序”问题,用本地的static对象来替代非本地(non-local)的static对象

 

参考文献

Effective C++

C++ Primer

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值