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

1:今天的C++已经是个多重范型编程语言(multiparadigm programming language),一个同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional)、泛型形式(generic)、元编程形式(metaprogramming )的语言。所以,需要将C++视为一个由相关语言组成的联邦而非单一语言。

2:这个联邦中主要包含4个次语言:C、Object-Oriented C++(classes,封装,继承,多态等)、Template C++(泛型编程)、STL(template标准库)。

3:当你从某个次语言切换到另一个,导致高效编程守则要求你改变策略时,不要感到惊讶。

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

1:该条款的本质是:以编译器替换预处理器。

2:#define ASPECT_RATIO 1.653这样的宏定义,ASPECT_RATIO也许从未被编译器看见,因而也就没有记录到符号表中,所以调试器也有可能不认识它。可以使用下面的语句代替它:

const double AspectRatio = 1.653; 至少AspectRatio 肯定会被编译器看见,从而进入符号表。

3:class的专属整型常量可以在声明时赋初值:

class GamePlayer {
private:
static const int NumTurns = ; // 常量声明
int scores[NumTurns]; // 使用该常量
...
};

上面的代码中,NumTurns是声明而非定义,C++允许class专属的static整型常量只声明而不定义,只要不取它的地址即可。

4:一个属于枚举类型的数值可以充当ints被使用,比如一个枚举值就可以作为数组大小的定义:

class GamePlayer {
private:
enum { NumTurns = }; int scores[NumTurns]; // fine
...
};

5:使用#define定义一个看起来像函数一样的宏,虽然它不会招致函数调用带来的额外开销,但是却也不会有必要的语法检查,因此必须记住为宏中的所有实参加上小括号。此时,可以使用inline函数,inline函数具有宏一样的效率,还具有一般函数的所有可预料行为和类型安全性。

03:尽可能使用const

1:如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。const写在类型之前,或者写在类型之后,星号之前,这两种写法的意义是相同的。

2:声明迭代器为const就像声明指针为const一样(即声明一个T* const指针),表示这个迭代器不得指向不同的东西,但它所指的东西的值是可以改动的。如果你希望迭代器所指的东西不可被改动(即希望STL模拟一个const  T*指针),你需要的是const_iterator:

std::vector<int> vec;
...
// iter acts like a T* const
const std::vector<int>::iterator iter = vec.begin();
*iter = ; // OK, changes what iter points to
++iter; // error! iter is const // cIter acts like a const T*
std::vector<int>::const_iterator cIter = vec.begin();
*cIter = ; // error! *cIter is const
++cIter; // fine, changes cIter

3:让函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。比如:

class Rational {
public:
Rational(int numerator = ,int denominator = ):numerator(numerator), denominator(denominator){};
int getnumerator() const {return numerator;};
int getdenominator() const {return denominator;}; private:
int numerator;
int denominator;
}; const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.getnumerator() * rhs.getnumerator(),
lhs.getdenominator() * rhs.getdenominator());
}

让operator*返回一个const对象,就可以防止下面错误的发生(程序员的原意可能是a*b == c):

    Rational a, b, c;
...
a * b = c;

4:如果成员函数是const的,则该成员函数才可作用于const对象。

如果两个成员函数如果只是常量性(一个是const,一个非const)不同,则这是一种重载。

class TextBlock {
public:
...
const char& operator[](std::size_t position) const // 针对const对象的operator[]
{ return text[position]; } char& operator[](std::size_t position) // 针对非const对象的operator[]
{ return text[position]; } private:
std::string text;
}; TextBlock tb("Hello");
std::cout << tb[]; // 调用非const的TextBlock::operator[] const TextBlock ctb("World");
std::cout << ctb[]; // 调用const的TextBlock::operator[]

注意,上面两个operator[]的返回类型也不相同:

std::cout << tb[]; // fine, 读 non-const TextBlock
tb[] = 'x'; // fine, 写 non-const TextBlock std::cout << ctb[]; // fine, 读 const TextBlock
ctb[] = 'x'; // 错误,写 const TextBlock

注意,non-const operator[]返回类型是reference to char,不是char,如果它返回char,则像tb[0] = ‘x’; 这样的语句就不合法。

5:成员函数如果是const意味什么?这有两个流行概念:bitwise constness(又称physical constness)和logical constness。

bitwise const阵营的人相信,成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是const。也就是说它不更改对象内的任何一个bit。这种论点很容易侦测违反点:编译器只需寻找成员变量的赋值动作即可。Bitwise constness正是C++对常量性的定义,因此const成员函数不可以更改对象内任何non-static成员变量。

但是,某种情况下,成员函数虽然不具备const性质却能通过bitwise测试。也就是一个更改了“指针所指物”的成员函数虽然不能算是const,但如果只有指针(而非其所指物)隶属于对象,那么称此函数为bitwise const且不会引发编译器错误。比如下面的例子:

class CTextBlock {
public:
char& operator[](std::size_t position) const
{ return pText[position]; } private:
char *pText;
}; const CTextBlock cctb("Hello"); // const对象
char *pc = &cctb[]; // 调用 const operator[] 得到一个指向cctb内部数据的指针
*pc = 'J'; // cctb 的值现在是 "Jello"

尽管operator[]声明为const成员函数,但是它却返回一个reference指向对象内部值。该函数不改变pText,因此编译器认为它是bitwise const的,编译通过。而实际上最终却改变了const对象的值。

这种情况导出所谓的logical constness。这一派拥护者主张一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此。比如:

class CTextBlock {
public:
...
std::size_t length() const; private:
char *pText;
std::size_t textLength;
bool lengthIsValid;
}; std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // 错误,在const成员函数内不能为textLength
lengthIsValid = true; // 和 lengthIsValid 赋值
} return textLength;
}

Length函数的实现不是bitwise const,因此编译器不同意,编译报错。

解决该问题的方法是使用mutable关键字,mutable释放掉non-static成员变量的bitwise constness约束:

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

此时就可以通过编译了。

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

1:永远在使用对象之前先将它初始化,对于内置类型,必须手工完成此事。

2:在构造函数中,尽量使用成员初始化列表初始化所有成员。这样做有时候绝对必要(比如具有const或reference的类),而且往往比赋值更高效。

3:不同编译单元内定义的non-local static对象的初始化次序是未定的。

所谓static对象,其寿命从被构造出来直到程序结束为止,这种对象包括global对象、定义于namespace作用域内的对象、在classes内、在函数内、以及在file作用域内被声明为static的对象。函数内的static对象称为local static对象,其他static对象称为non-local static对象。

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

如果某编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化,因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。

编译单元A内,有如下的定义:

class FileSystem {
public:
...
std::size_t numDisks() const;
...
}; extern FileSystem tfs;

而编译单元B内有下面的定义:

class Directory {                       // created by library client
public:
Directory( params );
...
}; Directory::Directory( params )
{
...
std::size_t disks = tfs.numDisks(); // use the tfs object
...
} Directory tempDir( params ); // directory for temporary files

现在,除非tfs在tempDir之前初始化,否则tempDir的构造函数会用到尚未初始化的tfs。

幸运的是一个小小的设计便可完全消除这个问题。唯一需要做的是:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为statIc)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说,non-local static对象被local static对象替换了。

这个手法的基础在于:C++保证,函数内的local state对象会在“该函数被调用期间,首次遇上该对象之定义式”时被初始化。

所以,上面的例子可以改成:

class FileSystem { ... };          

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

这么修改之后,这个系统程序的客户完全像以前一样地用它,唯一不同的是他们现在使用tfs ()和tempDir()而不再是tfs和tempDir。也就是说他们使用函数返回的“指向static对象”的references,而不再使用static对象自身。

Effective C++: 01让自己习惯C++的更多相关文章

  1. Effective C++ 1.让自己习惯C++

    //条款01:视C++为一个语言联邦 // 1:C++主要包含的语言为: // A:C.说到底C++仍然以C为基础.区块(blocks).语句.预处理器.内置数据类型.数组.指针等均来自于C.许多时候 ...

  2. 【Effective C++】让自己习惯C++

    条款01:视C++为一个语言联绑 C++的四个语言层次: C:C++是以C为基础的.基本数据类型.语句.预处理器.数组.指针等统统来自C. Oject-Oriented C++:面向对象这一特性包含了 ...

  3. More Effective C++: 01基础议题

    01:仔细区别 pointers 和 references 1:没有所谓的null reference,但是可以将 pointer 设为null.由于 reference 一定得代表某个对象,C++ ...

  4. Effective Java 01 Consider static factory methods instead of constructors

    Advantage Unlike constructors, they have names. (BigInteger.probablePrime vs BigInteger(int, int, Ra ...

  5. 《Effective C++》让自己习惯C++:条款1-条款4

    条款1:视C++为一个语言联邦 可以将C++分为4个层次: 1.C:C++实在C语言的基础上发展而来的. 2:Object-Oriented C++:C++面向对象. 3:Template C++:C ...

  6. Effective Java Index

    Hi guys, I am happy to tell you that I am moving to the open source world. And Java is the 1st langu ...

  7. 深入浅出c++之---this指针

    前言:C语言中的数组指针和指针数组 数组指针,是指向数组的指针的缩写:指针数组,是存放指针的数组的缩写.其实很多时候,往往因为简写和缩写带给我们很多困惑.我曾想过不用简称去学习,但在很多时候,我们查询 ...

  8. 我要好offer之 C++大总结

    0. Google C++编程规范 英文版:http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml 中文版:http://zh-g ...

  9. 《Effective C++》第1章 让自己习惯C++-读书笔记

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

随机推荐

  1. 【Streaming】Storm内部通信机制分析

    一.任务执行及通信的单元 Storm中关于任务执行及通信的三个概念:Worker(进程).Executor(线程)和Task(Spout.Bolt) 1.  一个worker进程执行的是一个Topol ...

  2. jqGrid列的统计

    $("#List").jqGrid({ url: "${pageContext.request.contextPath}/cbfx/getCbhzList.do" ...

  3. Linux时间设置命令

    1.date: 语法格式:date [-u] [-d datestr] [-s datestr] [--utc] [--universal] [--date=datestr] [--set=dates ...

  4. Leetcode447.Number of Boomerangs回旋镖的数量

    给定平面上 n 对不同的点,"回旋镖" 是由点表示的元组 (i, j, k) ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序). 找到所有回旋镖的 ...

  5. jquery ajax跨越

    JSONP是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问 1.jsonp之$.ajax js $.ajax({ ...

  6. 前端(jQuery)(1)-- JQuery简介及语法

    1.jQuery库简介 1.1.jQuery: jQuery库可以通过一行简单的标记被添加到网页中 1.2.什么是jQuery: jQuery是一个JavaScript函数库(由于底层封装的问题,不需 ...

  7. Hackerrank--Savita And Friends(最小直径生成树MDST)

    题目链接 After completing her final semester, Savita is back home. She is excited to meet all her friend ...

  8. JavaScript如何实现字符串拼接操作

    实际应用中,目标字符串的生成可能需要多个数据的拼接. 由于应用频繁,几乎是所有编程语言都必须掌握的操作,当然每种语言具有各自特点. 本文将通过代码实例详细介绍一下JavaScript如何实现字符串拼接 ...

  9. 小希的迷宫 HDU - 1272 (并查集)

    思路: 当图中的集合(连通子图)个数为1并且边数等于顶点数-1(即改图恰好为一棵生成树)时,输出Yes. 此题的坑:(1) 如果只输入0 0算作一组数据的话答案应该输出Yes (2) 输入数据可能并不 ...

  10. DjangoORM查询、分页、ckeditor

    查询数据 Django的批量查询(查询所有,或者条件查询)返回的是queryset对象. Queryset对象是一个惰性对象,在不执行 1.排序 2.循环 3.截取 操作的情况下,不会遍历序列的内容. ...