第一章 让自己习惯C++

来源:互联网 发布:牛散持仓查询软件 编辑:程序博客网 时间:2024/06/22 23:09

条款1:视C++为一个语言联邦

       理由:它是四个次语言组成的联邦政府,每个次语言都有自己的规约。

    1、C。区块、语句、预处理器、内置数据类型、数组、指针。

    2、Object-Oriental C++ 。classes(包括构造函数和析构函数)、封装、继承、多态、虚函数、动态绑定。

    3、Template C++。泛型编程、模板元编程。

    4、STL。六大组件:容器、迭代器、算法、函数对象、适配器、分配器。


请记住:C++ 高效编程守则视状况而变化,取决于你使用C++的那一部分。


条款2:尽量以const,enum,inline替换#define 

      1、#define PK const:

   以const替换#define ,有两种特殊情况:第一是定义const指针。由于常量定义式通常放在头文件内部,因此有必要将指针声明为const。例如若要在头文件内定义一个常量,你必须写两次。

     const char *const authorName="Scott";

    第二个值得注意的是class专属常量。

    我们无法利用#define创建一个class专属常量,因为#define并不重视作用域,也不能提供封装,也没有private。

      2、#define PK enum

     enum用法如下所示:

 C++ Code 
1
2
3
4
5
6
class GamePlayer
{
private:
    enum {NumTurns = 5}; //enum hack
    int scores[NumTurns];
}

     第一,enum hack的行为某方面说比较像#define而不像const,例如取一个const地址是合法的,但是取一个enum的地址是不合法的。如果你不想让别人获得一个指针或者引用指向你的某个整数常量,enum有这个约束。

    第二,实用主义。事实上enum hack是模板元编程的基础技术。


      3、#define PK inline

    宏定义在所有实参都必须添加括号,即时这样还是会出现问题,如下代码所示。

 C++ Code 
1
2
3
4
5
#define CALL_WITH_MAX(a,b) f((a)>(b))?(a):(b)

int a = 5, b = 0;
CALL_WITH_MAX(++a, b);  //a被累加两次  f((++a)>(b))?(++a):(b)
CALL_WITH_MAX(++a, b + 10); //a被累加两次 f((++a)>(b+10))?(++a):(b+10)

        

     使用inline 既可以获得宏带来的效率以及一般函数所有可预料的行为和类型安全性。修改代码如下所示:

 C++ Code 
1
2
3
4
5
template<typename T>
inline void callWithMax(const T &a, const T &b)
{
    f(a > b ? a : b);
}
     

    请记住:对于单纯常量,最好以const对象或enums替换#define

                   对于形式函数宏,最好改用inline函数替换#define。



条款3:尽可能使用const

      1、const多才多艺,它在classes外部修饰global或者namespace作用域中的常量,或修饰文件、函数、或区块作用域中被声明为static的对象,也可以用它修饰classes内部的static和non-static成员变量。面对指针,指出指针自身,指针所指物,或两者都是const。

      2、用法一:如果你希望迭代器所指向的东西不可被改动,你需要的是const_iterator。

 C++ Code 
1
2
3
4
5
6
7
8
std::vecotr<int> vec;
const std::vector<int>::iterator iter = vec.begin();
*itr = 10//正确
++iter;    //出错

std::vector<int>::const_iterator cIter = vec.begin();
*cIter = 10//出错
++cIter;   //正确
          3、用法二:令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而不至于放弃安全性和高效性。

 C++ Code 
1
2
3
4
5
6
7
class Rational
{
    ...
};
const Rational operator*(const Rational &lhs, const Rational &rhs);

if(a *b = c) // if(a*b==c) 避免错误。隐式转换为bool类型。
       给函数定义const,可以避免键入‘==’却意外键成'='的错误。

      4、const 成员函数

     

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class TextBlock
{
public:
    ...
    const char &operator[](std::size_t pos) const
    {
        return text[pos];
    }
    char &operator[](std::size_t pos)
    {
        return text[pos];
    }
private:
    std::string text;
}

TextBlock tb("hello");  //调用non-const
cout << tb[0];
tb[0] = '1';

const TextBlock ctb("hello");  //调用const
cout << ctb[0];
ctb[0] = '1'//出错


        我们有些数据还是希望在const成员函数进行修改,那么应该怎么办呢?简单的解法是:利用C++的一个与const相关的摆动场:mutable(可变的),mutable释放掉non-static成员变量的的bitwise constness约束。用法如下所示:

     

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class CTextBlock
{
public:
    std::size_t length() const;
private:
    char *pText;
mutable std:
    mutable szie_t textLength;
    mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const
{
    if(!lengthIsValid)
    {
        textlength = std::strlen(pText); //可行
        lengthIsValid = true//可行
    }
    return textLength;
}


      4、在const和non-cosnt 成员函数中避免重复。

    两个代码中有一定的关联性质。实现的代码如下所示:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class TextBlock
{
public:
    ...
    const char &operator[](std::size_t pos) const  //一如既往
    {
        ...
        ...
        return text[pos];
    }
    char &operator[](std::size_t pos)  //现在只调用const op[]
    {
        return
            const_cast<char &>(                     //将op[]返回值的const转除
                static_cast<const TextBlock &>(*this)  //为*this 加上const
                [pos]                          //调用const op[]
            );
    }
    ...
};
      这份代码有两个转型动作,这里讲*this从其原始类型TextBlock&转型为const TextBlock& .是的,我们使用转型操作为它加上const。第一次转型用来为了*this添加const,第二次则是const operator[ ]的返回值中移除const。


请记住:

    1、将某些东西声明为const可能帮助编译器侦测出错误用法。const 可被施加于任何作用域内的对象、函数参数、函数返回类型,成员函数体。

    2、编译器强制实施bitwise constness ,但你编写程序是应该使用“概念上的常量”。

    3、当const和non_const成员函数有着实质性等价实现时,令non-const 版本调用const 版本可避免代码重复。


条款4:确定对象被使用前已被初始化

 C++ Code 
1
2
3
4
5
6
7
ABEntry::ABEntry(const std::string &name, const std::string &address,
                 const std::list<Phonenum> &phones):
    theName(name),
    theAddress(address),
    thePhones(phones),
    numTimeConsulted(0)
{}
    在成员初始值替换赋值动作。也可以调用函数实现值的初始化。这种初始化带来的效果是取出“不明确行为”。

    许多classes拥有多个构造函数,每个构造函数有自己的成员初始值列。如果这种classes存在许多成员变量或者base class ,多个成员初始值列的存在就会导致不收欢迎的重复和无聊的工作。这种情况下可以合理的在初始值列中遗漏那些“赋值表现像初始化一样好的”变量,改用他们的复制操作,并将那些复制操作移往某个函数中,实现伪初始化。

    C++有着十分固定的“成员初始化次序”。base classes更早于derived calsses 被初始化,而class成员变量总是以其声明次序被初始化。

    下面还有一个问题是:不同编译单元内定义之non-local static 对象的初始化次序。解决这个问题的做法是:将每个non-local static 对象搬到自己的专属函数内,这些函数返回一个reference指向它所含的对象,然后用户调用这些函数,而不直接指涉这些对象。单例模式。具体实现如下:

     

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class FileSystem
{
    ...
};
FileSystem &tfs()
{
    static FileSystem fs;
    return fs;
}

class Diretory
{
    ...
};
Diretory::Diretory(params)
{
    ...
    std::size_t disks = tfs().numDisks;
    ...
}
Diretory &tempDir()
{
    static Diretory td;
    return td
}


请记住:
   1、为内置型对象进行手工初始化,因为C++不保证初始化他们;
   2、构造函数最好使用成员初始化列,而不要在构造函数本体内使用赋值操作。初始值列列出的成员变量,其排列次序应该和她们在class中声明次序相同。
   3、为免除“跨编译单元支初始化次序”问题,请以local static对象替换non-local static对象。






0 0
原创粉丝点击