「Effective C++」第一章:让自己习惯C++

写在前面

不久前才翻阅此书,鄙人才疏学浅,不能悟其中奥妙,望轻喷。如有高见,恳请诸位不吝赐教。

——窗外小雨,属文于十一月二十三日

还不知道写什么

。。。


Chapter1–让自己习惯C++

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

这是一个很有意思的观点,总的来说就是C++有各种各样的特性,而且这些特性与其他的语言也具有相似性。概括的来说可分为4点:

  • C

  • Object-Orient C++

  • Template C++

  • STL

我在对C++有一定了解后看到这几个词,还是能产生一些模糊的感觉。也正如原文所述,在他们之间切换有很多有趣的地方:

pass-by-value通常比pass-by-reference高效,在某些情况下pass-by-reference-to-const(头晕了)更好,虽然只是为了不犯低级错误。

如果想了解更多,请购买实体书,因为我没有电子版。

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

我们分开来看

const

首先define做的是把你定义的字符替换成后面的东西,编译的时候字符很可能就丢失了。

比如#define ASDF 1.3456最后出现bug时,报错很可能会提到1.3456而不是ASDF,让人头大,

所以我们可以这么写const double ASDF = 1.3456;就没有问题了。

还有就是对前者访问地址是错误的,而后者就没有任何问题。

既然讨论到常量,不妨再多说一点(条款03会有更详细的讨论,我不过是按照原书顺序继续)

第一是常量指针,没什么好多说的,是吧

const char* const authorName = "Scott Meyers";
const std::string autherName("Scott Meyers");	// 书上认为string要更好一点,不太清楚为什么@_@

第二是class专属常量

  • 为了让常量的作用域限制于class内,必须让它成为class的一个成员
  • 为了确保常量至多只有一份实体,必须让它成为一个static成员

So,我们举个栗子(我亲手试过了,它说的是真的)

class GamePlayer {
private:
	static const int NumTurns = 5;	// 仅仅是一个声明
	int scores[NumTurns];
};

我们如果要取NumTurns的地址(其他无所谓),一定要提供一份声明

const int GamePlayer::NumTurns;	// 无需赋予初值

请把这个式子放进一个实现文件而非头文件。

但是最好的做法是静态成员都不在类定义体中初始化,而是在定义处初始化

enum

还有一种奇技淫巧是用枚举来充当常量,被称为"the enum hack"

class GamePlayer {
private:
	enum { NumTurns = 5 };
	int scores[NumTurns];
};

这样就没有办法通过指针访问NumTurns了,和#define类似,相较于static可以类中初始化

inline

define 可以实现看起来像函数的操作也不是什么稀奇的事情,但是会出bug,比如

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

更好的写法是用inline

template<typename T>
inline void callWithMax(const T& a, const T& b)
	{ f(a > b ? a : b); }

至少我们不需要再操心什么了

条款03:尽可能使用const

我相信编译器是十分支持你写const的

我们先写出一些例子

char greeting[] = "Hello";
char* p = greeting;				// non-const pointer, non-const data
const char* p = greeting;		// non-const pointer, const data
char const * p = greeting;		// non-const pointer, const data
char* const p = greeting;		// const pointer, non-const data
const char* const p = greeting;	// const pointer, const data
const std::vector<int>::iterator	// 类似一个T* const
std::vector<int>::const_iterator	// 类似一个const T*

顺便解决一个很久之前就有的疑惑

class Rational { ... };
const Rational operator* (const Rational& lhs, const Rational &rhs);

如果没有这个第一个const,会出现这样的暴行

Rational a, b, c;
(a * b) = c;	// 或if (a * b = c)

const可以防止这样的错误发生

const 成员函数

貌似这里有两种江湖流派并存,一种是bitwise const,他们宣称const中的每一个bit都不应该被修改;另一种是logical constness

前者非常有道理,但是也有漏洞。我们先来看看书上的代码

#include <iostream>
using namespace std;

class TextBlock {
public:
    TextBlock(char* const tb)
        : pText(tb) { }
    char& operator[] (std::size_t position) const
    { return pText[position]; }
    char &operator[] (std::size_t position)
    { return pText[position]; }
private:
    char* pText;
};

int main()
{
    const TextBlock ctb("World");
    std::cout << ctb[0];
    char* pc = &ctb[0];
    *pc = 'J';

    std::cout << ctb[0];

    return 0;
}

上面这段代码我运行测试过了,诡异的是没有任何报错,但是最后一个std::cout没有任何输出,所以代码肯定是出问题了。

虽然我们宣称const是不可被修改的,但是我们终究还是修改了它的值。

logical const的表现算是对这种错误的容忍

class TextBlock {
public:
    TextBlock(char* const tb)
        : pText(tb) { }
public:
    std::size_t length() const;
private:
    char* pText;
    mutable std::size_t textLength;
    mutable bool lengthIsValid;
};
std::size_t TextBlock::length() const
{
    if (!lengthIsValid)
    {
        textLength = strlen(pText);
        lengthIsValid = true;
    }
    return textLength;
}

在const和non-const成员函数中避免重复

一句话就是我们不喜欢复制粘贴,重复的代码我们不想写两遍(维护起来也非常困难)

所以我们可以通过转型(虽然不推荐,详见条款27),请看

class TextBlock {
public:
    TextBlock(char* const tb)
        : text(tb) { }
public:
    const char& operator[] (std::size_t position) const
    {
        // ...
        return text[position];
    }
    char& operator[] (std::size_t position)
    {
        return
            const_cast<char&>(
                static_cast<const TextBlock&>(*this)
                    [position]
            );
    }
private:
    char* text;
};

如你所见,这份代码有两个转型动作,而不是一个。

我们打算让non-const operator[]调用其const兄弟,但是只是单纯使用[],会递归调用自己。

所以我们必须明确指出是const operator[]。

虽然没有办法直接这么做,但是我们可以把*this转化为const *this(太秀了???)

是的,static_cast<const TextBlock&>(*this)[position]把*this转成了const,而const_cast<char&>()把返回值转成了非const。

反过来行不行呢?用const调用非const?大懒达如是问到。

不行,因为使用const本身就是一个承诺,承诺绝不改变对象逻辑状态,但是non-const没有这个承诺,我们不应该定下规矩后又自己打破。

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

哈哈,这么快就到条款04了。

当然任何人都会告诉你一件事,就是“永远在使用对象之前先将它初始化“。

但是我们还是要先理解什么是初始化。

我们先来看一个构造函数的例子:

ABEntry::ABEntry(const std::string& name, const std::string& address,
                 const std::list<PhoneNumber>& phones)
{
    theName = name;			// 这些都是赋值语句
    theAddress = address;	// 而非初始化
    thePhones = phones;		// 潜在的意思就是这些语句的执行时间是不确定的
    numTimesConsulted = 0;	// 不保证一定在你所看到的那个赋值动作的时间点之前获得初值
}

然而用成员初值列初始化就好的多

ABEntry::ABEntry(const std::string& name, const std::string& address,
                 const std::list<PhoneNumber>& phones)
    : theName(name),
	  theAddress(address),	// 执行copy构造
	  thePhones(),			// 执行default构造
	  numTimesConsulted(0)	// 是初始化
{ }

这样效率会更高,而且不会有那些不明确的值出现。

(当然,书上还提了很多建议,比如内置类型也要初始化,对于赋值表现的和初始化一样好的变量,可以把赋值操作移到private的一个函数中,供所有构造函数使用。这些就不细说了。)

C++有着十分固定的“成员初始化次序”。

  • base classes 更早于derived classes被初始化

  • class的成员变量总是以声明的次序被初始化(即使在成员初值列中顺序不同)

    译注:例如初始化array时需要指定大小,因此代表大小的那个成员变量必须先有初值

  • “不同编译单元内定义之non-local static对象”的初始化次序

    所谓static对象,其寿命从被构造出来知道程序结束为止。

    所谓编译单元,是指产出单一目标文件的那些源码。基本上它是单一源文件加上其所含入的头文件。

    很可惜,C++对定义于不同编译单元内的non-local static对象的初始化次序并无明确定义。

    举例就不多说了,解决方法是:

    将每个non-local static对象搬到自己的专属函数内(该对象在此函数中被声明为static)。

    这些函数将返回一个reference指向它所含的对象(所以Singleton是什么?)

小结

ok,这样,第一章就讲完了,看完后感觉很爽啊,也不知道为什么。

超级想自己写点东西有没有!

然后发现自己写的和乐色一样。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值