<Effective C++>读书笔记

<Effective C++>读书笔记,第1张

<Effective C++>读书笔记 条款02  尽量以const,enum,inline替换#define 

        这个条款被作者认为改成"宁以编译器替代预处理器"更好,我们要先知道编译阶段和预处理阶段都干了什么

 (1) 预处理
使用 -E 选项:gcc -E hello.c -o hello.i
预处理阶段的过程有:头文件展开,宏替换,条件编译,去掉注释等。
(2) 编译
使用 -S 选项:gcc -S hello.c -o hello.s
编译阶段的工作是通过词法分析和语法分析将高级语言翻译成机器语言,生成对应的汇编代码。
(3) 汇编
使用 -c 选项:gcc -c hello.c -o hello.o
汇编阶段将源文件翻译成二进制文件。
(4) 链接 gcc hello.o -o a.out
链接过程将二进制文件与需要用到的库链接。连接后便可以生成可执行文件。
 

可以看出,在预处理的时候就进行宏替换,也就是将宏定义做文本替换,但由于预处理没有类型检查,所以经常会出现一些我们预期之外的错误的行为。

解决的办法是以一个常量替换上述的宏(#define),值得注意的是class专属常量,观察以下代码 

class GemePlayer  {
    private:
        static const int NumTurns = 5;
        int scores[NumTurns];
        ...
};

为了将常量的作用域限制于class内,必须让它成为class的一个成员;为此确保常量只有一份实体,所以必须让它成为一个static成员.

而你看到的是NumTurns的是声明而非定义,由于C++要求你对所使用的任何东西都提供一个定义,即在类外定义以下代码

const int GamePlayer :: NumTurns;

但如果它是个class专属常量又是static且为整数类型(如int、bool、char等),允许在类内初始化且只能是常量。所以可以不需要以上的定义代码.

对于形似函数的宏,最好改用inline函数来替换#define

由于形似函数的宏没有类型检查,只在预处理阶段进行文本替换,会出现意想不到的可怕后果,使用template inline可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性.

谨记

1.对于单纯常量,最好以const对象或enums替换#define

2.对于形似函数的宏,最好改用inline函数来替换#define

条款03:尽可能使用const

关键字const多才多艺,它可以修饰常量,文件,函数,区块作用域里被声明为static的对象

修饰指针的,无非就那几种,注意const与*的相对位置就ok啦,这里我不再赘述.比较陌生的还是为STL的迭代器加上const,该怎么加呢,我们首先要清楚迭代器的作用像T*指针,声明迭代器为const就像声明指针为const一样,具体表现有以下代码

using namespace std;
...
vector vec;
...
const vector::iterator iter = vec.begin(); // iter的作用像是T* const
*iter = 10;  // 正确
iter ++;     // 错误

vector::const_iterator cIter = vec.begin(); // iter的作用像是const T*
*cIter = 10; // 错误
cIter ++;    // 正确

令返回值为一个常量,往往会降低客户错误造成的意外,也不至于放弃安全性与高效性,比如

class Rational {...};
const Rational operator* (const Rational& lhs,const Rational rhs);
// 可以防止客户或者测试员写错代码
// 例如: 
Rational a,b,c;
(a*b) = c; // 这样的 *** 作编译器会提前告诉你出错了.

所以,将operator*的回传值声明为const可以预防那个"没意思的赋值动作",这就是返回值加上const的原因.

Effective C++中指出,对成员函数加上const到底意味着什么?

有一种观念是不更改对象内的任何一个bit,另一种是不可以更改对象内任何non-static成员变量,考虑以下代码

using namespace std;
...
class TextBlock  {
    public:
        ...
        char& operator[](size_t position) const  {
// 有一点必须要指出,两个成员函数如果只是常量性不同,可以被重载 ! ! ! 许多人漠视了这一点
            return text[position];
        }
        char& operator[](size_t position)  {
            return text[postion];
        }
    private:
        string text;
};
​
const CTextBlock cctb("Hello");
char* pc = &cctb[0];

*pc = 'J';   // 编译通过

我们可以看到return值为 char&的const成员函数,它的意思不会引发编译器异议,因为函数体内确实没有任何改动对象的语句,但是我如果这样来改动const对象呢

这样一来确实改动了const对象,因为编译器强制实施第二种观念,也就是不可以更改对象内任何non-static成员变量.但我们这里只是修改了对象的某些bits.

对于某些成员变量,若我们想要它总是可变的,即便在const对象里,可以在该成员变量前加上mutable.

最后提到的一点是,如果const与non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以减少代码量,如以下代码

class TextBlock  {
    public:
        ...
    const char& operator[] (size_t position) const  {
        ...
        ...
        ... //     这里有很多很复杂的代码
    }
    char& operator[] (size_t position)  {
        return const_cast(static_cast(*this)[position]);
        // 两次强转
    }
    ...
};

其中static_cast是为了给*this加上const(因为const成员函数的this指针是const *this),接着使用const_cast是为了移除返回类型的const.

而反向做法是不允许的(令const调用非const成员函数),我认为,non-const函数里可以写入const函数的内容,但const函数不能写入non-const函数的内容呀!

谨记

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

2.便以及强制实施bitwise constness,但你编写程序时应该使用"概念上的常量性"

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

条款42:了解typename的双重意义

我们在写类模板或函数模板时,typename与class的意义一样

template
// 等同于template

然而C++并不总是把class与typename视为等价,我们来看看typename的另一种含义,假设我们有个template function,代码如下(这段代码不是有效的代码)

template
void print2nd(const C& container)
{
    if (container.size() >= 2)  {
        C::const_iterator iter(container.begin());  // 嵌套从属名称
        ++iter;
        int value = *iter;
        std::cout << value;
    }
}

对于这个侯捷老师翻译的嵌套从属名称,我先来说说我理解的从属名称,我们声明一个变量 iter,它的类型是C::const_iterator,而这个类型是关于的,也就是依赖与模板C,所以它称之为从属名称,嵌套呢?我认为,iter 是C类里的const_iterator类型,所以它就是一个嵌套名称.合起来就是嵌套从属名称

Effective C++ 书里指出:如果解析器在template中遭遇一个嵌套从属名称,它便假定这个名称不是个类型,除非你告诉它,否则默认为不是类型,那我们以解析器的角度来理解这段代码,解析器完全有可能认为const_iterator是C中的静态数据成员!

如果你这么写代码

template
void print2nd(const C& container)
{
    C::const_iterator* x;  // 若x为全局变量
    ...
}

 那么解析器可以将它理解为C类里的const_iterator变量与x相乘!这听起来有点荒唐,因为我们明知道C::const_iterator是个类型,x只是指向这类型的指针.解决之道是在嵌套从属名称前加上typename,相当于我们告诉C++说C::const_interator是一个类型.

typename只被用来验明嵌套从属类型名称;其他名称不该有它的存在,而且有两个例外,不能放在嵌套从属名称之前

(1) typename不可以出现在base classes list内的嵌套从属名称之前

(2) 也不可以在member initialzation list中作为base class修饰符

template
class Derived : public base::Nested  {  // 不允许typename
    public:
        explict Derived(int x) : base::Nested(x)  { // 不允许
            typename base :: Nested temp;  // 必须
        }
};

在这里做出我的理解:                                                                                                                                 (1) 基类列表一定是类型名,不需要再告诉编辑器                                                                                 (2) 初始化列表一定是成员变量名,不可以加上typename

谨记: 

1.声明template参数时,前缀关键字class和typename可互换

2.使用关键字typename标识嵌套从属名称;但不得在base class lists(基类列) 或member initialization list ( 成员初值列 ) 内以它作为base class修饰符

注 : 一部分摘自该文章 C++ 基础概念、语法和易错点整理_Tyler_Zx的博客-CSDN博客

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4951435.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-13
下一篇 2022-11-13

发表评论

登录后才能评论

评论列表(0条)

保存