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. 一些hbase的shell查询语句

    华为bids(不想吐槽)种种原因只能用hbase shell查询,在此记录下自己探索的hbase shell 免得下次要用还得去找 scan 'ogg_sel_ioc_sv_product_name_ ...

  2. TZOJ 5963 Increasing Sequences(线性DP)

    描述 Given a string of digits, insert commas to create a sequence of strictly increasing numbers so as ...

  3. H5C3--边框图片

    类似于android的.9图片,目的是为了防止图片因为内容的扩展而导致图片拉伸失真. <!DOCTYPE html> <html lang="en"> &l ...

  4. light7结合jquery实现开关按钮

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http ...

  5. Objective-C头文件导出工具class-dump

    首先,这个工具是开源的.作者网站:http://stevenygard.com/projects/class-dump/ 用途: 分析库文件或可执行文件,得到Objective-C类和部分C结构体的信 ...

  6. JS简单实现:根据奖品权重计算中奖概率实现抽奖的方法

    本文主要介绍:使用 JS 根据奖品权重计算中奖概率实现抽奖的方法. 一.示例场景 1.1.设置抽奖活动的奖项名称 奖项名称:["一等奖", "二等奖", &qu ...

  7. 公司-广告-WPP:WPP

    ylbtech-公司-广告-WPP:WPP WPP集团 (LSE:WPP) (NASDAQ:WPPGY),是世界上最大的传播集团,总部位于英国伦敦.WPP集团拥有 60 多个子公司,主要服务于本地.跨 ...

  8. day38 05-Spring的BeanFactory与ApplicationContext区别

    ApplicationContext怎么知道它是一个工厂呢? BeanFactory也可以做刚才那些事情,只不过ApplicationContext对它有扩展.ApplicationContext间接 ...

  9. GYM 101981E(开关反转性质)

    要点 做法是删去连续的k个0或k个1,连消.消消乐的那种,网上博主用个栈\(O(n)\)就很优秀地操作了这个过程 原因是有性质:比如k=3,101000贪心地翻就能翻成000101,所以连续的k个可以 ...

  10. Leetcode15.3Sum三数之和

    给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以包含重复的三元组. ...