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

时间:2023-02-22 17:09:24

章节回顾:

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

《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

《Effective C++》第3章 资源管理(1)-读书笔记

《Effective C++》第3章 资源管理(2)-读书笔记

《Effective C++》第4章 设计与声明(1)-读书笔记

《Effective C++》第4章 设计与声明(2)-读书笔记

《Effective C++》第5章 实现-读书笔记

《Effective C++》第8章 定制new和delete-读书笔记


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

为了理解C++,你必须认识其主要的次语言。总共有四个:

(1)C

C++仍是以C为基础。

(2)Object-Oriented C++
classes(类)(包括构造函数和析构函数),encapsulation(封装),inheritance(继承), polymorphism(多态),virtual
functions (dynamic binding)(虚拟函数(动态绑定))等。

(3)Template C++

这是C++的generic programming(泛型编程)部分

(4)STL

STL 是一个 template library(模板库)。

C++并不是一个带有一组守则的一体语言:它是四个次语言组成的联邦*,每个次语言都有自己的规约。记住这四个次语言你就会发现C++容易了解多了。

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

-------------------------------------------------------------------------------------------------------------------

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

该条款最好称为:“尽量用编译器而不用预处理”,因为#define不被视为语言的一部分。

#define ASPECT_RATIO 1.653

编译器永远也看不到ASPECT_RATIO这个符号名,因为在源码进入编译器之前,它会被预处理程序去掉,于是ASPECT_RATIO不会加入到符号列表中。

解决的办法是:不用预处理宏,定义一个常量:

const double AspectRatio = 1.653;    //大写名称通常用于宏,所以这里改变名称写法

说明:

(1)作为语言常量,AspectRatio会被编译器看到,记入符号表。

(2)使用常量可能比使用#define导致较小量的码。因为预处理器盲目地用1.653置换ASPECT_RATIO导致目标代码中存在多个1.653的拷贝。如果使用常量AspectRatio,就不会产生多于一个的拷贝。

要把常量限制在类中,首先要使它成为类的成员;为了保证常量最多只有一份拷贝,还要把它定义为静态成员:

class GamePlayer
{
private:
static const int NUM_TURNS = ; // constant declaration
int scores[NUM_TURNS]; // use of constant
...
};

说明:

(1)语句是NUM_TURNS的声明,而不是定义。

(2)C++要求对所使用的任何东西提供一个定义式。如果它是class专属常量且为static整数类型(例如:ints,chars,bools等),只要不取它们的地址可以只声明并使用而无须定义。

(3)NUM_TURNS的定义如下:

const int GamePlayer::NUM_TURNS;

由于class常量已在声明时获得初值,因此定义时可不设初值。

(4)没有办法使用#define来创建一个类属常量,因为#defines不考虑作用域。一旦宏被定义,它就在其后编译过程中有效(除非后面某处存在#undefed)。

旧一点的编译器认为类的静态成员在声明时定义初始值是非法的。可以在定义赋值:

class EngineeringConstants                // header file
{
private:
static const double FUDGE_FACTOR;
...
};
// this goes in the class implementation file
const double EngineeringConstants::FUDGE_FACTOR = 1.35;

如果在编译器需要FUDGE_FACTOR的值例如,作为数组维数,是不行的。因为编译器必须在编译器间知道数组的大小。可以用enum解决:

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

说明:取一个enum地址是非法的。

一个普遍的#define指令的用法是用它来实现那些看起来像函数而又不会导致函数调用的宏。

#define max(a,b) ((a) > (b) ? (a) : (b))

注意:写宏时要对每个参数都要加上括号,否则会造成调用麻烦。但也会造成下面的错误:

int a = , b = ;
max(++a, b); // a 的值增加了2次
max(++a, b+); // a 的值只增加了1次

你可以用普通函数实现宏的效率,再加上可预计的行为和类型安全。

template<typename T>
inline const T& max(const T& a, const T& b)
{
return a > b ? a : b;
}

请记住:

(1)对于单纯常量,最好以const或enums替换#defines。

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

-------------------------------------------------------------------------------------------------------------------

条款03:尽可能使用const

const允许你告诉编译器某值保持不变,并获得编译器帮助,确保这条约束不被违反。在classes的外部,可以将它用于 global(全局)或namespace(命名空间)范围的 constants(常量),或修饰文件、函数、或区块作用域中被声明为static对象。修饰classes内部的static和non-static成员。修饰指针自身,指针所指物。

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; // const pointer,non-const data
const char * const p = greeting; // const pointer,const data

说明:当指针指向的内容为常量时,const放在类型之前和类型之后意义相同。

void f1(const Widget *pw); // f1 takes a pointer to a constant Widget object
void f2(Widget const *pw); // so does f2

对于STL迭代器来说:

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

const 成员函数

将const实施于成员函数的目的是确认该成员函数可作用于const对象身上。两个函数如果只是常量性不同,可以被重载。

class TextBlock {
public:
...
const char& operator[] (std::size_t position) const // operator[] for const objects
{ return text[position]; }
char& operator[] (std::size_t position) // operator[] for non-const objects
{ return text[position]; }
private:
std::string text;
}; TextBlock tb("Hello");
const TextBlock ctb("World");
std::cout << tb[]; // fine — reading a non-const TextBlock
tb[] = 'x'; // fine — writing a non-const TextBlock
std::cout << ctb[]; // fine — reading a const TextBlock
ctb[] = 'x'; // error! — writing a const TextBlock

为了避免重复,可以利用转型修改代码。根据const版本的operator[]实现其non-const版本。

class TextBlock {
public:
...
const char& operator[](std::size_t position) const
{
...
...
...
return text[position];
}
char& operator[](std::size_t position) // now just calls const op[]
{
return
const_cast<char&>( // cast away const on
// op[]'s return type;
static_cast<const TextBlock&>(*this) // add const to *this's type;
[position] // call const version of op[]
);
}
...
};

注意:令const 版本调用non-const版本来避免重复是错误的。一个 const成员函数承诺绝不会改变它的逻辑状态,但是一个non-const成员函数不会做这样的承诺。从一个const成员函数调用一个non-const成员函数,将面临承诺不会变化的对象被改变的风险。即const成员函数调用non-const成员函数是一种错误行为。

请记住:

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

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

-------------------------------------------------------------------------------------------------------------------

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

读取一个未初始化的值会引起未定义行为。因此,永远在使用对象之前先将它初始化。对于无任何成员的内置类型,你必须手工完成此事。

int x = ;                                // manual initialization of an int
const char * text = "A C-style string"; // manual initialization of a pointer
double d;
std::cin >> d; // "initialization" by reading from an input stream

对于内置类型以外的东西,由构造函数初始化,确保将对象的每一个成员都初始化。重要的是不要把赋值和初始化混淆。

class PhoneNumber { ... };
class ABEntry { // ABEntry = "Address Book Entry"
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 num TimesConsulted;
};
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
{
theName = name; // these are all assignments,
theAddress = address; // not initializations
thePhones = phones;
numTimesConsulted = ;
}

这样做虽然使得ABEntry对象具有了你所期待的值,但不是最好的做法。C++规定对象的成员变量的初始化动作发生在进入构造函数本体之前。效率较高的写法是:

ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
: theName(name) ,
theAddress(address) , // these are now all initializations
thePhones(phones) ,
numTimesConsulted()
{} // the ctor body is now empty

说明:

(1)版本1首先调用default构造函数为theName,theAddress和thePhones设初值,然后立即再对它们赋予新值。default构造函数的一切作为因此浪费了。

(2)版本2的成员初始化列表的做法避免了这个问题。初始化列表中针对各个成员变量而设的实参,被拿去作为各成员变量构造函数的实参。theName以name为初值进行拷贝构造,theAddress以address为初值进行拷贝构造, thePhones以phones为初值进行拷贝构造。

(3)对于大多数类型来说,只调用一次拷贝构造函数的效率比先调用一次default构造函数再调用一次copy assignment operator(拷贝赋值运算符)的效率要高(有时会高很多)。

(4)对于内置类型对象如numTimesConsulted,其初始化和赋值成本相同,但为了一致性最好也通过成员初始化列表来初始化。

(5)当想要构造一个default构造函数时,也可以使用成员初始化列表。

ABEntry::ABEntry()
:theName() , // call theName's default ctor;
theAddress() , // do the same for theAddress;
thePhones() , // and for thePhones;
numTimesConsulted() // but explicitly initialize
{}

当然,编译器会为用户自定义类型成员变量自动调用default构造函数,如果那些成员变量没有在成员初始化列表中被指定初值。

(6)如果成员变量是const或引用,即使内置类型也一定需要初值,不能被赋值。

(7)C++有固定的成员初始化次序:基类早于派生类,class成员变量总是以其声明次序被初始化。

static对象初始化问题:

所谓static对象,其寿命从构造出来直到程序结束为止。包括:global对象、定义于namespace作用域内的对象、在class内、函数内、以及在file作用域内被声明为static的对象。函数内的static对象称为local static对象,其他static对象称为non-local static对象。程序结束时static对象会被自动销毁,它们的析构函数会在main()结束时被自动调用。

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

改进方法:将每个non-local static对象搬到static函数中。C++保证,函数内的local static对象会在“该函数被调用期间”首次遇上该对象之定义式时被初始化

class FileSystem { ... };                // as before
FileSystem& tfs() // this replaces the tfs object; it could be
{ // static in the FileSystem class
static FileSystem fs; // define and initialize a local static object
return fs; // return a reference to it
}
class Directory { ... }; // as before
Directory::Directory(params) // as before, except references to tfs are
{ // now to tfs()
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir() // this replaces the tempDir object; it
{ // could be static in the Directory class
static Directory td; // define/initialize local static object
return td; // return reference to it
}

请记住:

(1)为内置类型对象进行手工初始化,因为C++不保证初始化它们。

(2)构造函数最好使用初始化列表,而不要在构造函数体内使用赋值操作。初始化列表中列出的成员变量排列次序与class中声明次序相同。

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

-------------------------------------------------------------------------------------------------------------------

《Effective C++》第1章 让自己习惯C++-读书笔记的更多相关文章

  1. effective java 第2章-创建和销毁对象 读书笔记

    背景 去年就把这本javaer必读书--effective java中文版第二版 读完了,第一遍感觉比较肤浅,今年打算开始第二遍,顺便做一下笔记,后续会持续更新. 1.考虑用静态工厂方法替代构造器 优 ...

  2. 《Effective C&plus;&plus;》第3章 资源管理(2)-读书笔记

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

  3. 《Effective C&plus;&plus;》第3章 资源管理(1)-读书笔记

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

  4. 《TCP&sol;IP详解卷1:协议》第4章 ARP:地址解析协议-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  5. 《TCP&sol;IP详解卷1:协议》第19章 TCP的交互数据流-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  6. 《移山之道》第十一章:两人合作 读书笔记 PB16110698 第六周(~4&period;15)

     本周在考虑阅读材料时,我翻阅了<移山之道>,正好看到这一章:两人合作,心想:正好,我们正值结对作业的紧要关头,书中两人合作的宝贵经验和教诲应当对我们有很大帮助.于是,我开始一边在ddl苦 ...

  7. Javascript模式(第五章对象创建模式)------读书笔记

    一 命名空间模式 1 命名空间模式的代码格式 var MYAPP={ name:"", version:"1.0", init:function(){ } }; ...

  8. 《C&plus;&plus;primer》v5 第3章 字符串、向量和数组 读书笔记 习题答案

    本章问题 1.char *p="hello world";与char p[]="hello world"的问题. 简单说前者是一个指向字符串常量的指针,后者是一 ...

  9. 第二章 搭建Android开发环境--读书笔记

    俗话说,工欲善其事,必先利其器,对于Android驱动开发来说,首先我们要做的就是搭建Android开发环境,我们首先要配置Linux驱动的开发环境,接着还得配置开发Android应用程序以及Andr ...

随机推荐

  1. Linux下提示 bash&colon; xxx command not found

    今天在虚拟机上安装了CentOS5.5,发现运行一些很正常的诸如:init,shutdown,fdisk 等命令时,悍然提示: bash: xxx command not found. 那么,首先就要 ...

  2. WPF自定义控件与样式&lpar;10&rpar;-进度控件ProcessBar自定义样

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: Pro ...

  3. &lbrack;bzoj 1027&rsqb;&lbrack;JSOI2007&rsqb;合金(解析几何&plus;最小环)

    题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1027 分析: 首先因为一个合金的和为1,所以考虑2个材料合金能否合成一个需求合金的时候 ...

  4. 支付宝也要上&quot&semi;服务号&quot&semi;&quest;斗战微信继续升级

    微信着实火了一阵,特别是微信服务号,在商户和消费者之间形成闭环,O2O领先了一步,作为曾经的电商支付老大支付宝来说是一种心头之痛,迫于压力,支付宝将于5月在支付宝钱包中上线服务窗功能,同时支付宝钱包将 ...

  5. 算法教程(3)zz

    First off, we can use our Line-Point Distance code to test for the "BOUNDARY" case. If the ...

  6. spring&lowbar;150803&lowbar;service

    实体类: package com.spring.model; public class DogPet { private int id; private String name; private in ...

  7. Linux正則表達式-反复出现的字符

    星号(*)元字符表示它前面的正則表達式能够出现零次或多次.也就是说,假设它改动了单个字符.那么该字符能够在那里也能够不在那里,而且假设它在那里,那可能会不止出现一个.能够使用星号元字符匹配出如今引號中 ...

  8. JavaScript闭包的一些理解

    原文:JavaScript闭包的一些理解 简单一点的说:闭包就是能够读取其他函数内部变量的函数.那如何实现读取其它函数内部变量呢,大家都知道在JavaScript中内部函数可以访问其父函数中的变量,那 ...

  9. caffe源码 池化层 反向传播

    图示池化层(前向传播) 池化层其实和卷积层有点相似,有个类似卷积核的窗口按照固定的步长在移动,每个窗口做一定的操作,按照这个操作的类型可以分为两种池化层: 输入参数如下: 输入: 1 * 3 * 4 ...

  10. python的re模块详解

    一.正则表达式的特殊字符介绍 正则表达式 ^ 匹配行首 $ 匹配行尾 . 任意单个字符 [] 匹配包含在中括号中的任意字符 [^] 匹配包含在中括号中的字符之外的字符 [-] 匹配指定范围的任意单个字 ...