文章目录

  • Ⅰ、 让自习惯C++
    • 条款01:视C++为一个语言连邦
    • 条款02:尽量以 const, enum, inline 替换 #define
      • 2.1、采用const替换#define
      • 2.2、采用enum替换#define
      • 2.3、采用 template替换 #define
    • 条款03:尽可能使用const
      • 3.1、const成员函数
      • 3.2、bitwise constness 和 logical constness
      • 3.3、在 const 和 non-const 成员函数中避免重复
    • 条款04:确定对象使用前已被初始化
      • 4.1、区分赋值和初始化
      • 4.2、不同编译单元内定义的 non-local static 对象的初始化次序

Ⅰ、 让自习惯C++

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


C++是一个多重泛型编程语言,同时支持过程形式、面向对象形式、泛型形式、元编程形式的语言。

将C++是为一个联邦,主要的此语言为4个:

  1. procedural-based C。C语言的面向过程
  2. Object-Oriented C++。面向对象
  3. Template C++。 C++ 的泛型 (generics) 编程和由 template 的强大功能带来 template metaprogramming (TMP,模板元编程)
  4. STL。template程序库,主要包括 容器、迭代器、算法以及函数对象

tips:编程过程中在前面3个次语言之间切换时,可能导致高效编程守则要求的改变

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


2.1、采用const替换#define

此处用宏定义一个常量

#define ASPECT_RATIO 1.653

记号名称ASPECT_RATIO可能未被登记到记录表(symbol table), 因为可能在编译前的预处理阶段被替换。
导致发生错误时不容易追踪该信息。

采用常量替换:

const double AspectRatio = 1.653 //大写名称通常用于宏,此处改变名称写法

常量定义式通常放在头文件

定义一个常量的 char*based字符串(不可改变指向不可改变内容)

const char* const autorName = "Scott Meyers";
//根据c++ primer观点,第一个const为底层const,保证指针指向内容不变
//第二个const为顶层const,保证指针指向地址不变//采用string
const std::string autoirName("Scott Meyers");

定义一个class的专属常量

class GamePlayer{private:static const int NumTurns = 5; //这里只是常量声明式,声明式,声明式int scores[NumTurns];         //使用它...};

  C++中 class专属常量又是static且为整数类型(eg: int,char,bool).需要特殊处理,如果未取地址,可以只声明,否则需要另外提供定义式。

const int GamePlayer::NumTurns;      //类内已经初始化赋值,无须也无法重新赋值。

#define无法为一个 class 定义一个专属变量,因为 #define 并不重视作用域。
也就是不存在所谓的 private #define

2.2、采用enum替换#define

  旧式编译器不支持 static 成员在声明式上获得初值,“in class” 初值设定也只允许对整数常量进行。
此时,需要将初值放在定义式:

class CostEstimate{private:static const double FudgeFactor;    //static class 常量声明...
};
const doubleCostEstimate::FudgeFactor = 1.35;  //位于实现文件内

  类内的数组声明式,编译器坚持必须在编译期间知道数组的大小。如果编译器不允许 “static 整数型class常量” 完成 "in class"初值设定,可改用所谓的 “the enum hack” 补偿做法。

class GamePlayer{private:enum { NumTurns = 5};      //"the enum hack"//令 NumTurns成为5的一个记号名称int socres[NumTurns];...
};

注意:enum不能取地址

2.3、采用 template替换 #define

另一个常见的 #define 误用情况是以它实现宏(macros)。

//用宏比较a和b的较大值来调用f
#define CALL_WITH_MAX(a,b) f(a)>f(b) ? (a) : (b) )

缺点: 要为实参添加小括号防止错误,还有一些不容易发现的错误

如下:

int a = 5,b = 0;
CALL_WITH_MAX(++a, b);        //a被累加了两次
CALL_WITH_MAX(++a, b+10);    //a被累加了一次

采用template函数替代

template<typename T>
inline void callWithMax(const T& a,const T& b){ //采用 pass by reference-to-const//可以传递所有类型.f(a > b ? a : b);
}

条款03:尽可能使用const


3.1、const成员函数

目的:

  1. 使class接口比较容易被理解,那些可以改动,那些不行
  2. 使“操作const对象”称为可能。pass-by-reference-to-const方式传递对象。

const函数可被重载
1.const对象只能调用 const版本
2.non-const对象可以调用 constnon-const版本,默认为non-const版本。

3.2、bitwise constness 和 logical constness

1、bitwise const阵营的人相信,成员函数只有在不改变对象的任何成员变量(static除外)时
才可以说是 const。即不改变对象内的任何一个 bit 。
这种论点好处是很容易侦测违反点。
bitwise constness正是 C++ 对常量性的定义,因此 const 成员函数不可以更改对象内的
任何non-static成员变量。

不幸的是很多不具备const性质的函数却可以通过 bitwise测试。
实例:

class CTextBlock{public: ...char& operator[](std::size_t position) const //bit const声明{ return pText[position]; }                        //但实际没起到效果
private:char* pText;
};
//此时
const CTextBlock cctb("Hello");   //声明一个常量对象
char *pc = &cctb[0];
*pc = 'J';                       //此时cctb变为 “Jello”

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

例如: CTextBlock class有可能高速缓存文本区域的长度以便询问:

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);  //error!!! const函数内lengthIsValid = true;               //不允许修改类成员}return textLength;
}

解决方案 mutable释放 non-static成员变量的bitwise constness约束

class CTextBlock{public:...std::size_t length() const;private:char *pText;mutable std::size_t textLength;    //mutable可能总是会被更改,mutable bool lengthIsValid;        //即使在const函数内};std::size_t CTextBlock::length() const{if(!lengthIsValid)    {textLength = std::strlen(pText);  //现在validlengthIsValid = true;}return textLength;}

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

假设TextBlock的 operator[] 不单单返回一个 reference,还要做各种检测,
这样会导致写出的 const 和 non-const operator[] 冗余。

解决方案 令 non-const版本调用 const版本

class TextBlock{public:...const char& operator[](std::size_t position) const //一如既往{ .........return text[position];}char& operator[](std::size_t position)             //调用 const版本{returnconst_cast<char>&(                         //将op[]返回值的const转除static_cast<const TextBlock&>(*this)        //为*this加上const[position];                              //调用 const op[];);}...
};

注意:反向操作将 const函数调用 non-const是不可取的

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

定义类Point

class Point{int x,y;
};
...
Point p;    //p成员变量有时候会被初始化(为0),有时候不会
  1. 使用 C part of C++ 而且初始化可能招致运行期成本,那么不保证初始化
  2. non-C parts of C++,则有所变化,如 vector(STL part of C++)保证初始化,
    array(C part of C++) 不保证内容初始化。

最佳处理办法: 永远在使用对象前先将其初始化。对于无任何成员的内置类型,你必须手工完成此事。例如:

int x = 0;                              //对int初始化
const char* text = "A C-style string  //对指针初始化
double d;
std::cin >> d;                            //调用输入流初始化

4.1、区分赋值和初始化

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;
};
ABEntry::ABEntry(const std::string& name, const std::string& address,  const std::list<PhoneNumber>& phones)
{theName = name;           //这些都是赋值theAddress = address;  //而非初始化thePhone = phones;numTimesConsulted = 0;
}

  对象的初始化发生在进入构造函数之前,故以上的 theName, theAddress 和 thePhones均不是初始化,而是赋值。初始化发生在某些对象的default构造函数调用时,此时未进入ABEntry构造函数体,而对于 numTimesConsulted这类内置类型,赋值动作的时间点不确定。

采用 **member initialization list (成员初值列)**来为对象的成员初始化。

ABEntry::ABEntry(const std::string& name, const std::string& address,  const std::list<PhoneNumber>& phones):theName(name),            //此处均为初始化,如果theAddress(address), //类成员没有在指定,编译器thePhones(phones),     //调用默认构造函数numTimesCosulted(0)       {}

如果成员变量为 const 或 references,则一定需要初值,不能被赋值。

C++成员初始化次序
base classes 更早由于其 drived classes 被初始化,而 class 的成员变量按照成员声明次序被初始化

4.2、不同编译单元内定义的 non-local static 对象的初始化次序

local static对象: 寿命从构造直至程序结束为止,因此不包括 stackheap-based对象。这种对象包括 global对象 、定义于namespace作用域内的对象、在classed内、在函数内、以及在 file作域内被声明为 static的对象。
函数内的 static对象称为为 local static对象(它们相对函数而言为 local),其他 static对象称为 non-local static对象。它们的析构函数会在 main()结束时自动调用。

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

问题在于C++对于定义于不同编译单元内的 non-local static对象的初始化次序并无明确定义

实例:

class FileSystem{        //编译单元A
public:...std::size_t numDisk() const;...
};
extern FileSystem tfs;  //预备给用户使用的对象
/*----------------------------------------------------------*/
class Directory{public:Directory( params); //编译单元B...
};
Directory::Directory( params)
{...std::size_t disks = tfs.numDisks();...
}

假设,采用 创建一个 Driectory对象

Directory tempDir( params);

  此时 tempDir构造函数调用了 tfs函数对象,故 tfs必须先于 tempDir初始化,否则后果很严重。以下是解决方案:

采用 local static对象替换 non-local static对象
这是 Singleton模式的一个常见手法

这个手法基础在于:C++保证,函数内的local static对象会在“该函数被调用期间”
“首次遇上该对象的定义式”时被初始化。如果函数从未调用,则不会生成对象,
也免去了多余的构造函数析构函数的调用。

class FileSystem{...};                   //同前
FileSystem& tfs()                       //定义一个local static对象
{                                       //返回一个referencestatic FileSystem fs;return fs;
}
class Directory{...};                   //同前
Directory::Directory( params)
{...std::size_t disk = tfs().numDisks();...
}
Directory& tempDir()                    //同前,返回一个 reference to tfs
{static Directory td;return td;
}

以上的 reference-returing 函数往往十分单纯:定义并初始化一个local static,然后返回它。

注意:这些内含 static对象的事实使他们在多线程有不确定性。任何一种 non-const static对象,无论是local还是non-local,多线程环境下的等待某事发生都会有麻烦。

解决方案:在程序单线程启动阶段手工调用所有reference-returing函数,可以消除与初始化有关的“竞速形势”。

Effective C++读书笔记 第1章相关推荐

  1. Effective Java 读书笔记----第三章

    第三章 对于所有通用的方法 主要讲的是对Object类的非final方法(equals,hashCode,toString,clone和finalize)覆盖的一些规则 1.覆盖equals时请遵守通 ...

  2. 《深度探索C++对象模型》读书笔记第五章:构造析构拷贝语意学

    <深度探索C++对象模型>读书笔记第五章:构造析构拷贝语意学 对于abstract base class(抽象基类),class中的data member应该被初始化,并且只在constr ...

  3. 《Java编程思想》读书笔记 第十三章 字符串

    <Java编程思想>读书笔记 第十三章 字符串 不可变String String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以及包含修 ...

  4. Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据

    Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据 Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据 7.1 程序数据的命名 PL/SQL要求在给数据结构命名的时候应 ...

  5. Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理

    Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理 Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理 ACID原则:即一个事务具有原子性.一致性. ...

  6. PMP读书笔记(第9章)

    大家好,我是烤鸭:     今天做一个PMP的读书笔记. 第九章 项目资源管理 项目资源管理 项目资源管理的核心概念 项目资源管理的趋势和新兴实践 裁剪考虑因素 在敏捷或适应型环境中需要考虑的因素 9 ...

  7. PMP读书笔记(第2章)

    大家好,我是烤鸭:     今天做一个PMP的读书笔记. 第二章 项目运行环境 2.1 概述 2.2 事业环境因素 2.2.1 组织内部的事业环境因素 2.2.2 组织外部的事业环境因素 2.3 组织 ...

  8. PMP读书笔记(第1章)

    大家好,我是烤鸭:     今天做一个PMP的读书笔记. 第一章 引论 1.1 概述指南和目的 1.1.1 项目管理标准 1.1.2 通用词汇 1.1.3 道德与专业行为规范 1.2 概述指南和目的 ...

  9. 《Linux内核设计与实现》 第八周读书笔记 第四章 进程调度

    20135307 张嘉琪 第八周读书笔记 第四章 进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统.只有 ...

最新文章

  1. python3 设置默认编码_Python3的字符编码乱码问题解决思路
  2. Oracle存储结构_文件
  3. 【Tomcat Eclipse】8080端口被占用问题记录
  4. jsp调用controller方法_SpringMVC五大核心组件及调用过程
  5. 【渝粤教育】国家开放大学2018年春季 0609-21T中级财务会计(1) 参考试题
  6. MAC安装配置maven环境变量
  7. l2-029 特立独行的幸福 (25分)_霜降后盆栽幸福树,调整4个地方,不用再怕掉叶子了...
  8. QQ网页链接打开本地QQ.exe原理
  9. python关于组合数据类型_python组合数据类型
  10. Bailian2944 单词替换【字符串流】
  11. mac os x 系统安装 genymotion android 模拟器
  12. MySql递归查询上级,下级
  13. NeatUpload使用方法
  14. 【第十届“泰迪杯”数据挖掘挑战赛】B题:电力系统负荷预测分析 Baseline
  15. VMware安装和卸载时出现无法访问你试图使用的功能所在的网络位置该怎么办(Windows Installer CleanUp)
  16. 用python帮别人写了个文字识别程序
  17. vue2服务端渲染 php,详解如何使用Vue2做服务端渲染
  18. 跟小白学python网络爬虫实例2
  19. python import illegal instruction
  20. 使用kubecm管理k8s多集群环境

热门文章

  1. Codeforces Round #236 (Div. 2) C. Searching for Graph(水构造)
  2. 题解P3711:【仓鼠的数学题】
  3. js操作符类型转换大全
  4. 这是一篇测试博文的文章
  5. 直接调用内置数据源连接对话框(C#/VB.NET2005源码)
  6. [转]CPU/GPU/TPU/NPU...XPU都是什么鬼
  7. python操作docx入门教程
  8. 学习笔记_vnpy实战培训day04_作业
  9. linux基本管理命令,linux常用命令与基本管理
  10. 阿里数据总监手把手教学:如何面向企业做一次有价值的数据分析