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

  C++ == C(C基本语法) + Object-Oriented C++(类,封装,继承,多态……) + Template C++(泛型编程) + STL(容器,迭代器,算法,函数对象) .

故而:

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

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

  1. #define 不被视为语言的一部分,其所定义的符号未被加入记号表(symbol table)内,宏内无法设置断点,无法被跟踪调试;

  2. #define 定义形似函数的宏(而不是定义一个常量),即使已经为宏中的所有实参加上小括号,仍有可能会遭遇麻烦(比如参数为 ++i 运算符)。

注意:

  1. 无法利用#define创建一个class专属常量(当然,宏也不能够提供任何封装性),因为#defines并不重视作用域(scope).一旦宏被定义,他就在其后的编译过程中有效(除非在某处被#undef)。

  2. 以定义类的专属常量为例,部分编译器不允许static成员(定义为static,确保此常量至多只有一个)在其声明式上(一般是位于头文件内)获得初值,所谓的“in-class 初值设定”也只允许对整数常量进行。如下:

class CostEstimate
{
private:
static const double FudgeFactor; // static class 常量声明,位于头文件(.h)内
.......
enum {NumTurns = }; // 枚举变量,用于在类声明式中提供必要的并且需要初值设定的常量
int scores[NumTurns ]; // 使用枚举常量
}; const double CostEstimate:FudgeFactor = 1.35; // static class 常量定义,位于实现文件(.cpp)内

可以使用枚举值来补偿“in class 初值设定”,其理论基础是:''一个属于枚举类型的数值可权充ints被使用"。枚举类型的行为某方面说比较像#define而不像const。例如取一个const地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。

故而:

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

  2. 对于形似函数的宏(macros), 最好改用inline函数替换#defines。

条款03 : 尽可能使用const

  const允许你指定一个语义约束,而编译器会强制实施这项约束。

  1. const修饰指针,具体如下:

char greeting[] = "Hello";
char *p = greeting; // non-const pointer,non-const data
const char *p1 = greeting; //等价于“char const * p1 = greeting;” , non-const pointer,const data
char * const p2 = greeting; // const pointer,non-const data,类似引用(指向不可更改)
const char * const p3 = greeting; // const pointer,const data

注意:对于p1指针,其所指物为const data,但这仅限对于p1指针来说是const data,如果此时用另外一个普通指针ptr同时指向p1所指物,则可以通过ptr指针修改所指物。

  2. const成员函数

  将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象。

  bitwist constness 阵营相信,成员函数只有在不更改对象之任何成员变量(static除外)时才可以说const。bitwist constness正是C++对常量性(constness)的定义,因此const成员函数不可以更改对象内任何non-static成员变量. 但却有例外:具体的说,一个更改了“指针所指物”的成员函数虽然不能算const,但如果只有指针(而非其所指物)隶属于对象,那么却可以通过定义一个普通指针ptr,指向类中成员指针所指物,则可以通过ptr修改所指物(此为上一点所描述的情况),这就导致了反直观结果:我们定义const的本意是不修改所指物。如下:

class CTextBook {
public:
...
   // 函数后面的const表明不会修改对象的non-static成员变量,仅仅对于类成员函数而言需要这个限定,非类成员函数没有对象概念
char& operator[](std::size_t position) const{return pText[position];}
private:
char * pText; //只有指针隶属于对象
}; const CTextBook cctb("Hello"); //声明一个常量对象
char * pc = &cctb[]; //调用const operator[]取得一个指针,指向cctb数据 *pc = 'J'; // cctb现在有了“Jello”这样的内容,参见 C++易混淆知识点整理 第1点

  如果需要在const成员函数内修改某些non-static成员变量的值,则可以通过对此成员变量添加一个与const相关的摆动场:mutable(可变的)。mutable释放掉non-static成员变量的bitwise constness约束。如下:

class CTextBlock{
public:
.....
std::size_t length() const;
private:
char * pText;
mutable std::size_t textLength; // 这些成员变量可能总是会被更改,即使在const成员函数内
mutable bool lengthIsValid;
}; std::size_t CTextBlock::length() const // const的含义就是不修改对象内的任何non-static成员变量
{
if(!lengthIsValid){
textLength = std::std::strlen(pText); // 如果没有给textLength指定mutable,这里不能赋值,因为指定了mutable,
                                所以即便在const成员函数内,也可以修改non-static成员变量的值
lengthIsValid = true;
}
}

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

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

Class TextBook{
public:
.....
const char& operator[](std::size_t position) const
{
.....
.....
return text[position];
} char& operator[](std::size_t position)
{
return
const_cast<char&>( // 将op[]返回值的const移除(常量性移除)
static_cast<const TextBook&>(*this) // 为*this加上const,使之能够调用到const op[]
[position]
);
}
}

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

  2. 编译器强制实施bitwise constness ,但你编写程序时应该使用“概念上的常量性”(conceptual constness).

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

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

  如果使用C part of C++而且初始化可能招致运行期成本,那么就不保证发生初始化。一旦进入non-C part of C++,规则有些变化。这就很好的解释了为什么array(来自C part of C++)不保证其内容被初始化,而vector(来自STL part of C++)却有此保证。

  最佳的处理方法就是:永远在使用对象之前先将它初始化。

  1. 对于无任何成员的内置类型,你必须手工完成初始化。

  2. 至于内置类型以外的任何其他东西,初始化责任落在构造函数(constructors)身上。规则很简单:确保每一个构造函数都将对象的每一个成员初始化。

注意:

  别混淆了赋值和初始化(使用成员初值列)。例如:

class PhoneNumber{ ...... };
class ABEntry {
public:
ABEntry (const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones );
private:
std::string& theName;
std::string& theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
}; ------------------------ Method1 : // 这些都是赋值,而非初始化
--------------------------------------------------
ABEntry :: ABEntry (const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones )
{
theName = name; // 这些都是赋值,而非初始化
theAddress= address;
thePhones= phones ;
numTimesConsulted= ;
} -------------------------- Method2 : // 这些才是初始化-----------------------------------------
ABEntry :: ABEntry (const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones )
: theName(name), // 这些都是初始化
theAddress(address),
thePhones(phones) ,
numTimesConsulted()
{ } // 构造函数本地不必有任何动作

C++ 规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。

  Method2和Method1的最终结果相同,但通常效率较高。基于赋值的那个版本(Method1)首先调用default构造函数为theName,theAddress和thePhones设初值,然后立刻再对他们赋予新值。default构造函数的一切作为因此浪费了。成员初值列的做法(Method2)避免了这一问题,因为初值列中针对各个成员变量而设的实参,被拿去作为各个成员变量值构造函数的实参。本例中的theName以name为初值进行copy构造,等等。
  对于内置类型对象如numTimesConsulted,其初始化和赋值的成本相同,但为了一致性最好也通过成员初值列来初始化。但注意,如果成员变量是const或references,它们就一定需要初值,不能被赋值(const或reference的值不能被修改,也就不可能在后面被赋值,所以只能在初始化的时候给予初值)。

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

    non-local static对象:函数内的static对象称为local static对象(对于函数而言是local),其他static对象称为non-local static对象。

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

 问题:如果某编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化,因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。原因在于,决定它们的初始化次序相当困难,非常困难,根本无解。

  解决方案:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不是直接指涉这些对象。换句话说,non-local static对象被local static对象替换了。Design Patterns可能认出来了,这是Singleton模式(单例模式)的一个常见实现手法。

  上面解决方案的理论基础是:C++保证,函数内的local static对象会在“该函数被调用期间” , “首次遇上该对象之定义式”时被初始化。所以如果你以“函数调用”(返回一个reference指向local static对象)替换“直接访问non-local static对象”,你就获得了保证,保证你所获得的那个reference将指向一个历经初始化的对象。同时,如果你从未调用non-local static对象的“仿真函数”,就绝对不会引发构造和析构成本。如下:

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

故而:
  1. 为内置型对象进行手工初始化,因为C++不保证初始化它们(C part of C++);

  2. 构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。

   3. 为避免“跨编译单元之初始化次序”问题,请以local static 对象替换 non-local static对象。

Effective C++ —— 让自己习惯C++(一)的更多相关文章

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

    条款一:视C++为一个语言联邦 为了理解C++,你必须认识其主要的次语言.幸运的是总共只有四个: C:C++是由C语言继承而来的,必然对C有很好的兼容性,这一部分主要包括C中的一些语言,库函数等.但当 ...

  2. Effective C++ —— 构造/析构/赋值运算(二)

    条款05 : 了解C++默默编写并调用哪些函数 编译器可以暗自为class创建default构造函数.copy构造函数.copy assignment操作符,以及析构函数. 1. default构造函 ...

  3. C++易混淆知识点整理

    // 1 /////////////////////////////////////////////////////////////////////// // 常量指针:,指针可修改,变量不可修改(只 ...

  4. #pragma init_seg

    先进后出原则,最先初始化的最后析构! 1.C++中全局对象.变量的构造函数调用顺序是跟声明有一定关系的,即在同一个文件中先声明的先调用.对于不同文件中的全局对象.变量,它们的构造函数调用顺序是未定义的 ...

  5. [.NET] 《Effective C#》快速笔记(一)- C# 语言习惯

    <Effective C#>快速笔记(一)- C# 语言习惯 目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 ...

  6. 《Effective C#》快速笔记(一)- C# 语言习惯

    目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 is 或 as 操作符而不是强制类型转换 四.使用 Conditional ...

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

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

  8. Effective C++(第三版)笔记 ---- 第一部分让自己习惯C++

    内容从侯捷译版的<Effective C++>(第三版)摘录 条款一 C++作为一个多种范式融合的语言,可以看成是语言的联邦,它包含了一下四种主要的次语言: C.C++以C为基础,很多时候 ...

  9. seven habits of highly effective people 高效能人士的七个习惯

    习惯的模型 : dependent 依赖  -- independent 独立自主 --interdependent  互相依赖 1: be  proactive 主动积极 what you can ...

随机推荐

  1. php ldap添加与修改

    /** * ldap 备份 * @param int $cardid * @param string $username * @param string $password 未加密密码 * @retu ...

  2. 基于jQuery商品分类选择提交表单代码

    分享一款基于jQuery商品分类选择提交表单代码.这是一款基于jQuery实现的商品信息选择列表表单提交代码. 在线预览   源码下载 实现的代码: <div class="yList ...

  3. 【Unity】同时打开多个项目工程

    需求:学习Unity时经常会边参考别人的工程边写自己的Demo工程,需要同时打开多个项目工程. 网上查询的方式大约有两种: 在Edit->Preferences->Editor Analy ...

  4. ExtJs 常用小技巧备忘录

    1. ExtJs 给fieldLabel与fieldInput添加样式{给Input标签加入图标}http://www.w3school.com.cn/cssref/pr_background.asp ...

  5. busybox内置ftp服务器用法

    参考:http://blog.chinaunix.net/uid-20564848-id-74041.html 最新的busybox已集成ftp服务器层需ftpd,使用方法如下: 方法一:# tcps ...

  6. jffs2根文件系统制作

    http://www.eetop.cn/blog/html/98/510998-20964.html 作者:刘洪涛,华清远见嵌入式学院高级讲师,ARM公司授权ATC讲师. JFFS2是Flash上应用 ...

  7. 一站式学习Wireshark(三):应用Wireshark IO图形工具分析数据流

    基本IO Graphs: IO graphs是一个非常好用的工具.基本的Wireshark IO graph会显示抓包文件中的整体流量情况,通常是以每秒为单位(报文数或字节数).默认X轴时间间隔是1秒 ...

  8. MySQL做为手动开启事务用法

    START TRANSACTION;INSERT INTO `t1` (t, t1) VALUES('124', NOW());ROLLBACK;COMMIT;

  9. CentOS下安装Gitlab

    环境 Requirements 软件 版本 CentOS 6.6 Python 2.6 Ruby 2.1.5 Git 1.7.10+ Redis 2.0+ MySQL   GitLab 7-8-sta ...

  10. Eclipse 中link一个异地的Folder

    Eclipse 中link一个外地的Folder New -> Folder -> Click "Advanced" --> Check "Link t ...