Effective C++第一章
来源:互联网 发布:百度seo排名公司 编辑:程序博客网 时间:2024/05/16 06:40
为了理解C++,将其分为四个次语言:
当从某次语言切换到另一个,可能会高效编程守则会要求你改变策略。例如对内置类型而言,pass by value通常比pass by reference高效,但当你从C到面向对象时,由于用户自定义构造函数和析构函数的存在,pass by reference to const往往更好,运用template C++尤其如此。但是STL中,迭代器和函数对象都是在C指针上塑造出来的,所以对STL的迭代器和函数对象而言,旧式的C pass by value守则再次适用。
对于单纯常量,最好以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'; //okconst 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 Objective-C 2.0第一章
- effective C++(第一章 从C转向C++)
- Effective Objective-C 2.0总结(第一章)
- effective C++ 第一章:从C转向C++
- 《Effective C++》第一章:让自己习惯C++
- 【Effective Objective-C 2.0读书笔记】第一章:熟悉Objective-C
- effective objective-c 2.0 笔记 第一章 :熟悉Objective-C
- 肥兔读书笔记之Effective C#(第2版) 第一章
- Effective Object-C 2.0 第一章(条目1和2)
- Effective Object-C 2.0 第一章(条目3)
- Effective Object-C 2.0 第一章(条目5)
- Effective Object-C 2.0 第一章(条目4)
- Effective C++ 第一章(让自己习惯C++)
- Effective stl 第一章 容器
- Effective STL读书笔记-第一章
- Effective c++ 第一章总结
- Effective C++第一章
- Effective Objective-- 第一章
- Unable to load native-hadoop library for your platform
- MongoDB初探系列之三:MongoDB用户权限操作
- 紫书章七例题 1——UVA 725 Division
- mybatis foreach方法遍历对象
- IntelliJ使用指南—— 导入Eclipse的Web项目
- Effective C++第一章
- Intellij idea2016创建Maven的web项目
- 微信企业号自建应用
- ArrayBlockingQueue
- solr智能提示 suggest(针对索引的某个字段建立索引词库做智能提示)
- C# 获取文件名,路径
- [leetcode] 495. Teemo Attacking
- linux中struct class
- VS2015程序出现缺少dll文件解决方法