Effective C++第一章
2017-03-09 15:47
218 查看
为了理解C++,将其分为四个次语言:
次语言 | |
---|---|
面向对象 | class(析构函数,构造函数)、封装、继承、多态、virtual函数…. |
Template C++ | C++泛型编程部分 |
STL | 是一个template程序库,容器、迭代器、算法以及函数对象.. |
C | 区块(blocks)、语句、预处理器、内置数据类型、数组。指针等统统来自C。但C语言的局限:没有模板、没有异常、没有重载… |
对于单纯常量,最好以const对象或enums替换#defines
例如#define RATIO 1.653可以替换为
const double Ratio = 1.653;因为#define不被视为语言的一部分,所以记号名称
RATIO可能没进入记号表,但作为语言常量的
Ratio肯定会被编译器看到,当然就会进入记号表。
以常量替换#define,有两种特殊情况
定义常量指针(指向常量的指针(变量)),由于常量定义式通常被放在头文件内,因此有必要将指针(不只是指针所指之物)声明为const。
关键词const出现在
*左边,被指物是常量;在
*号右边指针是常量;如果出现在
*两边则被指物和指针都是常量。
在C/C++中,常量指针(non-const pointer,const data)这样声明:
1)const int *p; 2)int const *p; //两种写法意义相同
在C/C++中,指针常量(const pointer , non-const data)这样声明:
int a; int *const b = &a; //const放在指针声明操作符的右侧
还有指物和指针都是常量:
const char* const p; //const pointer , const data
class专属常量
为了将常量的作用域限制于class内,你必须让它成为class的一个成员,而为确保此常量至多只有一份实体,必须让其成为一个static成员。
class GamePlayer{ private: static const int NumTurns = 5; //常量声明式(非定义式) int scores[GameTurn]; ... };
使用
enum hack的技巧,其思想就是把
GameTurn定义为一个枚举常量。上面的代码可以写为:
class GamePlayer { private: enum {GameTurn = 10}; int scores[GameTurn]; };
enum hack的优点:
enum定义的常量,并不是在预编译的时候进行替换,而是在编译时,从enum定义的常量存储区取定义的常量值。因此,不会导致 “不必要的内存分配”。
enum hack的行为某方面比较像#define而不像const。例如,取const地址是合法的,但是取值enum的地址是不合法的,#define的地址也不合法,如果你不想让别人获得一个point或reference指向你的某个整数常量,enum实现这个约束。
对于形似函数的宏,最好改用inline函数替换#defines
#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))//宏中所有的实参加上小括号
可以改用inline函数
template<typename T> inline void CallWithMax(const T& a,const T& b){ f(a>b? a:b); }
强大的const
指针常量,常量指针迭代器(因为STL迭代器以指针为根据塑模出来的,声明迭代器为const就像声明指针为const一样)
std::vector<int> vec1; ... const std::vector<int>::iterator iter = vec1.begin();//像T* const *iter = 10; //ok ++iter; //wrong ! iter is const ... std::vector<int>::const_iterator cIter = vec1.begin();//像const T* *cIter = 10; //wrong ! *cIter is const ++cIter; //ok
函数声明
令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而不至于放弃安全性和高效性;const参数就像local const对象一样。
class rational{...}; const rational operator*(const rational& lhs,const rational& rhs);
将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。这类成员函数的重要性在于:
可知哪个函数可以改动对象内容,而哪个不可以
使得“操作const对象”成为可能
两个成员函数如果只是常量性不同,可以被重载,如下:
class TextBlock{ public: ... const char& operator[](std::size_t position) const{return text[position];} //operator[] for const对象 char& operator[](std::size_t position) {return text[position];} //operator[] for non-const对象 private: std::string text; }; TextBlock tb("hello"); std::cout << tb[0]; //调用non-const TextBlock::operator[] tb[0] = 'x'; //ok const TextBlock ctb("world"); std::cout << ctb[0]; //调用const TextBlock::operator[] ctb[0] = 'x'; //wrong
其中,当const和non-const成员函数有实质等价的实现时,
利用non-const operator[]调用const operator[]是可以避免代码重复的安全做法,过程中需要转型将常量性转除。
但是const成员函数调用non-const 成员函数是一种错误行为,因为const成员函数承诺不改变其对象的逻辑状态,但non-const成员函数没有这种承诺,如果const函数内调用non-const函数可能会使得承诺不改动的对象被改动。
确定对象被使用前已先被初始化
通常如果使用C part of C++而且初始化可能招致运行期成本,那么就不保证发生初始化。一旦进入non-C parts of C++,规则有些变化。这就是为什么array(来自C part of C++)不保证其内容被初始化,但vector(STL part of C++)却有此保证。读取未初始化的值会导致不明确的行为。
最佳处理方法:对象被使用前先初始化:
首先,对于无任何成员的内置类型,必须手工完成此事。
其次,内置类型以外的任何其它,初始化责任落在构造函数身上。确保每一个构造函数都将对象的每一个成员初始化。
class ABEntry{ public: ABEntry(const std::string& name, const std::string& address, const std::list<int>& phones); //声明式 private: std::string theName; std::string theAddress; std::list<int> thePhones; int num; //num是内置类型 }; //(1)利用copy赋值法(首先调用default构造函数为theName、theAddress、thePhones设初值,然后再赋予新值。) ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<int>& phones){ theName = name; theAddress = address; thePhones = phones; num = 0; } //(2)成员初值列(初值列中针对各个成员变量而设的实参,被拿去作为各成员变量之构造变量的实参) ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<int>& phones){ theName(name);//theName以name为初值进行copy构造法 theAddress(address);//同上 thePhones(phones);//同上 num(0); } //对大多数类型而言,(2)是比(1)高效的。但对于内置类型对象(1)和(2)成本相同,但为了一致性,最好也通过成员初值列来初始化。如果成员变量是const或reference,它们就一定需要初值,不能被赋值。
建议总是使用成员初始值列,并总是列出所有成员变量。
class可能拥有多个构造函数,每个构造函数有自己的成员初值列,多份成员初值的存在就会导致不受欢迎的重复。这种情况下,可以合理地在初值列中遗漏那些“赋值表现像初始化一样好”的成员变量,改用它们的赋值操作,并将那些赋值操作移往某个函数(通常是private),供所有构造函数调用。这种做法在“成员变量的初值由文件或数据库读入”时特别有用。
C++有十分固定的成员初始化次序:父类早于子类;class的成员变量总是以声明次序被初始化,即使在初值列中以不同于声明次序出现,也不会有影响。
最后,为免除“跨编译单元之初始化次序”问题,请以local static 对象替换non-local static对象。
所谓所谓static对象,包括
global对象
定义于namespace作用域的对象
在class内、函数内、在file作用域内被声明为static的对象。
其中,函数内的static对象成为local static对象,其它static对象称为non-local static对象。static对象的寿命从被构造出来直到程序结束时被自动销毁,也就是它们的析构函数在main函数结束时被自动调用。
所谓编译单元
当一个c或cpp文件在编译时,预处理器首先递归包含头文件,形成一个含有所有必要信息的单个源文件,这个源文件就是一个编译单元。这个编译单元会被编译成为一个与cpp 文件名同名的目标文件(.o或是.obj) 。简单说,一个编译单元就是一个经过预处理的cpp文件。
C++对定义于不同编译单元内的non-local static 对象的初始化并无明确定义
class FileSystem{ //编译单元1 public: ... std::size_t numDisks() const; ... }; extern FileSystem tfs; //预备给编译单元2使用的对象
class Directory{ //编译单元2 public: Directory(param); ... }; Directory::Directory(param){ ... std::size_t disks = tfs.numDisks(); //使用tfs对象 ... }
现在编译单元2创建一个Directory对象,
Directory tempDir(param);
tfs和tempDir是定义于不同编译单元内的non-local static 对象,因此问题在于tfs是否在tempDir之前先被初始化?
解决方法:将每一个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个指向local static对象的reference。调用这些函数来替换“直接访问non-local static对象”。这是Singleton模式的一个常见实现手法。
class FileSystem{...}; FileSystem& tfs() { static FileSystem fs; return fs; } class Directory{...}; Directory::Directory(param){ ... std::size_t disks = tfs().numDisks(); //上面是std::size_t disks = tfs.numDisks(); ... } Directory& tempDir() { static Directory td; return td; }
C++保证,函数内的local static对象会在“该函数被调用期间”“首次遇见该对象之定义式”时被初始化。
但另一角度看,这些函数“内含static对象”的事实使它们在多线程系统中带有不确定性。任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。处理的一种做法就是:在程序的单线程启动阶段手工调用所有reference-return函数,这可消除与初始化有关的“竞速形势”
相关文章推荐
- effective C++ 第一章:从C转向C++
- Effective C++ 第一章(让自己习惯C++)
- Effective C++第一章 让自己习惯C++
- effective C++(第一章 从C转向C++)
- Effective c++ 第一章 让自己习惯C++
- Effective C++ 第一章学习笔记
- Effective C++ 第一章 理解&总结
- effective c++ 第一章:让自己习惯c++
- 《Effective C++ 改善程序与设计的55个具体做法》——第一章笔记
- Effective C++第一章
- Effective c++ 第一章 让自己习惯C++
- 《Effective C++》第一章:让自己习惯C++
- Effective C++第一章:让自己习惯C++
- php学习第一章:PHP基础语法(二)之PHP运算
- 【HttpClient4.5中文教程】【第一章 :基础】1.3 HttpClient执行上下文
- 20170919学习笔记Selenium 2 第一章自动化测试基础
- effective C++ 条款 51:编写new和delete时需固守常规
- 第一章 绪论
- 数据结构第一章实验
- 第一章软件工程概论