【Effective C++ 学习笔记】Item1-Item4
2014-09-24 19:49
357 查看
【1】 视C++为一个语言联邦(View C++ as a federation of languages.)
最初: C with classes
现在: 支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional)、泛型形式(generic)、元编程形式(metaprogramming)的语言。
C++语言联邦中包含的次语言:
C 区块、语句、预处理器、内置数据类型、数组、指针没有模板、异常、重载
Object-oriented C++ classes、封装、继承、多态、virtual函数等等
Template C++
STL 它对容器、迭代器、算法以及函数对象的规约有着极佳的紧密配合与协调
C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。
【2】 尽量以const,enum,inline替换#define (prefer consts, enums, and inlines to #define.)
另一个常见的#define误用情况是以它来实现宏。
你可能知道为宏中的所有实参加上小括号,但是纵使如此:
在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”!
幸运的是你只需要写出template inline函数
对于单纯常量,最好以const 对象或者enums 替换 #defines;
对于形似函数的宏,最好改用inline 函数替换 #defines。
【3】尽可能使用const (Use const whenever possible.)
如果const在星号左边,代表被指物是常量;若果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。
STL的迭代器的作用就像个T*指针。声明迭代器为const就像是声明指针为const一样(即声明一个T* const指针),表示这个迭代器不得指向不同的东西,但是它所指向的东西的值是可以改动的。如果你希望迭代器指向的东西不可改动(即希望STL模拟一个const T*指针),你需要的是const_iterator。
再看一个例子:
只要重载了operator[]并对不同版本给予不同的返回类型,就可以令const 和 non-const TextBlocks获得不同的处理:
注意其中的声明:
同样需要注意的是,non-const operator[] 的返回类型是一个 char&,而不是char。如果是char,那么tb[0] = 'x' 就无法通过编译。
这个class不适当地将其 operator[] 声明为const成员函数,而该函数却返回了一个reference指向对象内部值。假设暂时不管这个事实,请注意,operator[]实现代码并不更改 pText。于是编译器很开心地位operator[] 产出目标代码。但是看看它允许发生什么事情:
这其中当然不该有任何错误:你创建一个常量并且设以某值,而且只对它调用const成员函数。但你终究还是改变了它的值。
有关const_cast
更加详细请点击
将某些东西声明为const可以帮助编译器侦测出错误的用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”。
当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以必满代码重复。
【4】确定对象被使用前已先被初始化 (Make sure that objects are initialized before they're used.)
通常如果你使用C part of C++而且初始化可能招致运行期成本,那么就不保证发生初始化。一旦进入non-Const parts of C++,规则有些变化。这就很好地解释了为什么array(来自Cpart of C++)不保证其内容被初始化,而vector(来自STL part of C++)却有此保证。
至于内置类型以外的任何其他东西,初始化责任落在构造函数身上。
基于赋值的构造函数首先调用default构造函数为成员设初值,然后立刻对它们赋予新值。default构造函数的一切作为因此浪费了。成员初值列(member initialization list)的做法避免了这一问题,因为初值列中针对各个成员变量而设置的实参,被拿去作为各成员变量之构造函数的实参。
对大多数类型而言,比起先调用default构造函数然后调用copy assignment 操作符,单只调用一次copy构造函数是比较高效的,有时甚至高效得多。对于内置型对象如numTimesConsulted,其初始化和赋值的成本相同,但为了一致性最好也通过成员初值列来初始化。
non-local static 变量初始化顺序不确定,带来的问题
为内置对象进行手工初始化,因为C++不保证初始化它们。
构造函数最好使用成员初值列(member initialization list),而不要在构造函数中使用赋值操作(assignment),其排列次序应该和它们在class中的声明次序相同。
为避免“跨编译单元之初始化序”问题,请以local static对象代替non-local static 对象。
最初: C with classes
现在: 支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional)、泛型形式(generic)、元编程形式(metaprogramming)的语言。
C++语言联邦中包含的次语言:
C 区块、语句、预处理器、内置数据类型、数组、指针没有模板、异常、重载
Object-oriented C++ classes、封装、继承、多态、virtual函数等等
Template C++
STL 它对容器、迭代器、算法以及函数对象的规约有着极佳的紧密配合与协调
C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。
【2】 尽量以const,enum,inline替换#define (prefer consts, enums, and inlines to #define.)
class GamePlayer{ private: static const int NumTurns = 5; //常量声明式 int scores[NumTurns]; //使用该常量 }; const int GamePlayer::NumTurns; //NumTurns的定义式旧式的编译器如果不支持上述语法的话可以将初值放在定义式,即
class CostEstimate { private: static const double FudgeFactor; //static class 常量声明 }; const double CostEstimate::FudgeFactor = 1.35; //static class 常量定义
另一个常见的#define误用情况是以它来实现宏。
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b) )
你可能知道为宏中的所有实参加上小括号,但是纵使如此:
int a = 5, b = 0; CALL_WITH_MAX(++a, b); //a被累加两次 CALL_WITH_MAX(++a, b+10); //a被累加一次
在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”!
幸运的是你只需要写出template inline函数
template<typename T> inline void callWithMax (const T& a, const T& b) { f(a > b ? a : b); }
对于单纯常量,最好以const 对象或者enums 替换 #defines;
对于形似函数的宏,最好改用inline 函数替换 #defines。
【3】尽可能使用const (Use const whenever possible.)
char greeting[] = "Hello"; char* p = greeting; const char* p = greeting; char* const p = greeting; const char* const p = greeting;
如果const在星号左边,代表被指物是常量;若果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。
STL的迭代器的作用就像个T*指针。声明迭代器为const就像是声明指针为const一样(即声明一个T* const指针),表示这个迭代器不得指向不同的东西,但是它所指向的东西的值是可以改动的。如果你希望迭代器指向的东西不可改动(即希望STL模拟一个const T*指针),你需要的是const_iterator。
vector<int> vec; ... const vector<int>::iterator iter = vec.begin(); *iter = 10; //正确,改变所指之物 ++iter; //错误,iter是const vector<int>::const_iterator cIter = vec.begin(); *cIter = 10; //错误,*cIter是const ++cIter; //没问题,改变cIter
再看一个例子:
class TextBlock { public: ... const char& operator[] (size_t position) const { return text[position]; } char& operator[](size_t position) { return text[position]; } private: string text; };
只要重载了operator[]并对不同版本给予不同的返回类型,就可以令const 和 non-const TextBlocks获得不同的处理:
cout<<tb[0]; //没问题——读一个non-const tb[0] = 'x'; //没问题——写一个non-const cout<<ctb[0]; //没问题——读一个const ctb[0] = 'x'; //错误!——写一个const
注意其中的声明:
TextBlock tb("Hello"); const TextBlocks("World");
同样需要注意的是,non-const operator[] 的返回类型是一个 char&,而不是char。如果是char,那么tb[0] = 'x' 就无法通过编译。
class CTextBlock { public: ... char& operator[](size_t position) const { return pText[position]; } private: char* pText; };
这个class不适当地将其 operator[] 声明为const成员函数,而该函数却返回了一个reference指向对象内部值。假设暂时不管这个事实,请注意,operator[]实现代码并不更改 pText。于是编译器很开心地位operator[] 产出目标代码。但是看看它允许发生什么事情:
const CTextBlock cctb("Hello"); //声明一个常量对象 char* pc = &cctb[0]; //调用const operator[] 取得一个指针,指向cctb 的数据 *pc = 'J'; //cctb现在有了"Jello"这样的内容
这其中当然不该有任何错误:你创建一个常量并且设以某值,而且只对它调用const成员函数。但你终究还是改变了它的值。
有关const_cast
class A { public: A() { m_iNum = 0; } public: int m_iNum; }; void foo() { //1. 指针指向类 const A *pca1 = new A; A *pa2 = const_cast<A*>(pca1); //常量对象转换为非常量对象 pa2->m_iNum = 200; //fine //转换后指针指向原来的对象 cout<< pca1->m_iNum <<pa2->m_iNum<<endl; //200 200 //2. 指针指向基本类型 const int ica = 100; int * ia = const_cast<int *>(&ica); *ia = 200; cout<< *ia <<ica<<endl; //200 100 }
更加详细请点击
将某些东西声明为const可以帮助编译器侦测出错误的用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”。
当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以必满代码重复。
【4】确定对象被使用前已先被初始化 (Make sure that objects are initialized before they're used.)
通常如果你使用C part of C++而且初始化可能招致运行期成本,那么就不保证发生初始化。一旦进入non-Const parts of C++,规则有些变化。这就很好地解释了为什么array(来自Cpart of C++)不保证其内容被初始化,而vector(来自STL part of C++)却有此保证。
至于内置类型以外的任何其他东西,初始化责任落在构造函数身上。
基于赋值的构造函数首先调用default构造函数为成员设初值,然后立刻对它们赋予新值。default构造函数的一切作为因此浪费了。成员初值列(member initialization list)的做法避免了这一问题,因为初值列中针对各个成员变量而设置的实参,被拿去作为各成员变量之构造函数的实参。
对大多数类型而言,比起先调用default构造函数然后调用copy assignment 操作符,单只调用一次copy构造函数是比较高效的,有时甚至高效得多。对于内置型对象如numTimesConsulted,其初始化和赋值的成本相同,但为了一致性最好也通过成员初值列来初始化。
ABEntry::ABEntry (const string& name, const string& address, const list<PhoneNumber>& phones) :theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0) { }
non-local static 变量初始化顺序不确定,带来的问题
为内置对象进行手工初始化,因为C++不保证初始化它们。
构造函数最好使用成员初值列(member initialization list),而不要在构造函数中使用赋值操作(assignment),其排列次序应该和它们在class中的声明次序相同。
为避免“跨编译单元之初始化序”问题,请以local static对象代替non-local static 对象。
相关文章推荐
- 【Effective C++学习笔记】Item5-Item12
- Effective C++学习笔记(Part One:Item 1-4)
- Effective C++学习笔记:条款3:尽量用new和delete而不用malloc和free
- Effective C++ 学习笔记:为含指针变量的类声明一个拷贝构造函数和一个赋值操作符
- 宁以pass-by-reference-to-const替换pass-by-value——effective c++学习笔记
- Effective C++ 学习笔记(2)
- 27 尽量少做类型转换——effective c++学习笔记
- Effective C++学习笔记:条款1:尽量用const和inline而不用#define
- Android ActionBar Item学习笔记
- 透彻了解inline的里里外外——effective c++学习笔记
- effective c++ 学习笔记2----auto_ptr
- 尽量使用new/delete操作符,而不是malloc/free来分配内存-------Effective C++学习笔记
- Effective C++学习笔记:尽量使用初始化而不要在构造函数里赋值
- Effective C++学习笔记:尽量用“传引用”而不用“传值”
- Effective C++ 学习笔记:让operator=返回*this的引用
- Effective c++学习笔记
- Effective C++学习笔记:写operator new和operator delete时要遵循常规
- Effective C++学习笔记:分清成员函数,非成员函数和友元函数
- ASP.NET学习笔记----ItemCommand中竟然没有绑定数据(20080326)(前面一篇CSDN有BUG)
- 为“异常安全的努力”是值得的——effective c++学习笔记