从5月11日开始看effective c++,半年没看c++了,现在看居然还能看懂QAQ(我属于小白类型,连入门都算不上),把一些自己看完之后的重点总结写一下,便于今后可以回过来看看自己有什么收获。话不多说,开始今天的第一篇!!
第一章:让自己习惯c++
条款01:视C语言为一个语言联邦(View C++ as a federation of languages)
说实话这个主要就是让我们了解一下c++这门语言,讲的就是四点:1. C C++是以C为基础;2. Object-Oriented C++ (C with class),在原有C基础上加上面向对象的特性,封装、继承、多态…等;3. Template C++ (泛型编程);4. STL 一个特殊的template程序库,懂得都懂这东西0.0。
以上四个次语言的切换遵守编程策略
请记住:
c++ 高效编程守则视状况而变化,取决于你使用C++的哪一部分。
条款02:尽量以const、enum、inline替换#define(Prefer consts、enums、and inline to #define)
#define ASPECT_RATIO 1.653
换成
const double AspectRation =1.653;
宏定义被预处理器拿走,记号名称ASPECT_RATIO可能没有进入记号表,编译器就会发出错误,而 AspectRation为一个语言常量,肯定会被编译器看到,一定会进入记号表。对浮点常量而言,const比define导致较少量的码。
这个const的位置不同有不同含义
const char* const authorName=“ABC”;
第一个const指向"ABC",第二个指向char*指针。
再者#define为"全局"的,一旦定义,整个编译过程有效,如果需要class专属常量(只在此class中有效)需要使用const
class ConstEstimate{
private:
static const double FudgeFactor; //声明
//static const int NumTurns=5; //错误->用于数组声明时发生错误
enum { NumTurns=5 }; //正确
int scores[NumTurns]
...
};
const double CostEstimate::FudgeFactor=1.35; //定义
//const int ConstEstimate::NumTurns=5; //错误
这里可能会发生错误,在编译期间需要知道数组的大小,改用"the enum hack"。
#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); //a被累加一次
a的递增次数取决与和谁比较,有点烦!!
template<typename T>
inline void callWithMax(const T& a, const T& b){
f(a>b?a:b);
}
用inline可以同样获得宏的效率和一般函数的可预料行为和类型安全性(f函数本体不用再加括号)
请记住:
对于单纯常量,最好以const对象或enums替换#define。
对于形似函数的宏,最好以inline函数替换#define。
条款03: 尽可能使用const (Use const whenever possible)
const char* p=greeting; (char const *也可以) //const data
char* const p=greeting; //const pointer
const char* const p=greeting; //const pointer,const data
std::vector<int> vec;
...
const std::vector<int>::iterator i=vec.begin(); //类似T* const
*i=10; //成功
++i; //错误 i为const
std::vector<int>::const_iterator cI=vec.begin(); //类似const T*
*i=10; //错误 cI为const
++i; //成功
class Rational {...};
const Rational operator* (...);
Rational a,b,c;
if(a*b = c)...
编译器直接报错,避免了‘==’意外键入‘=’
当const成员函数时:
class CTextBlock{
public:
...
std::size_t length() const;
private:
char* pText;
//std::size_t textLength;
mutable std::size_t textLength;
//bool lengthIsValid;
mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const{
if(!lengthIsValid){
textLength=std::strlen(pText);
lengthIsValid=true;
}
}
mutable 使得成员变量即使在const成员函数内也可以被更改
class TextBlock{
public:
...
const char& operator[](std::size_t position) const{
...
...
return text[position];
}
char& operator[](std::size_t position){
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
...
};
这里让non-const operator[]调用const,从而减少代码的重复量,但是不能反向做法,“让const 调用non-const”,因为const本身不能被改动。
请记住:
-将某些东西声明为const可帮助编译器侦测出错误用法。const 可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
-编译器强制实施bitwise constness,但编写程序时应该使用”概念上的常量性”
-当const和non-const成员函数有实质等价的实现时,令non-const版本调用const版本可避免代码重复量
条款04:确定对象被使用前已先被初始化(Make sure that objects are initialized before they’re used)
确保每一个构造函数都将对象的每一个成员初始化。
class PhoneNumber{...};
class ABEntry{
public:
ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones);
private:
std::string& name;
std::string& address;
std::list<PhoneNumber>& phones;
int numTimesConsulted; //内置型对象(int double float double...)
};
ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones){
//这些是赋值而非初始化
theName=name;
theAddress=address;
thePhones=phones;
numTimesConsulted=0;
}
//使用成员初值列
ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones):theName(name),theAddress(address),thePhones(phones),numTimeConsulted(0) {}
使用成员初值列(直接初始化)代替函数赋值,通常效率较高。使用赋值:首先还是调用default构造函数设初值,然后有对其赋值,造成default构造函数的作为浪费,而初值列可以避免此问题。对了,初值列的成员初始化次序可以不一,但最好是和成员变量的声明顺序一致(让别人也能看懂)。下面有个难点的东西:不同编译单元内定义之non-local static对象的初始化次序;
先来拆分着看:
static对象: 寿命从被构造出来直到程序结束为止(包括global对象、定义域namespace作用域内的对象、在classes内、在函数内、以及file作用域内)
non-loacl static对象: 函数内对象称为local static对象(对函数而言为local),其他static对象成为non-loacl static对象
编译单元:产出单一目标文件的源码(就是单一源码文件+所含头文件(#include files))
这里说明下,FileSystem class和Directory class 来自不同编译单元。
class FielSystem{ //自己本机建立
public:
...
std::size_t numDisks() const;
...
};
extern FileSystem tfs; //预留给客户使用对象
class Directory{ //有程序库客户(他人)建立
public:
Directory();
...
};
Directory::Directory(){
...
std::size_t disks=tfs.numDisks(); //使用你所创建的class对象
...
}
-----------------------------------------------
Directory tempDir(); //使用Directory
使用Directory时初始化顺序就显得重要了,tfs就必须在tempDir之前被初始化,不然tempDir的构造函数对使用为初始化的tfs.但是tfs和tempDir是在不同时间不同的使用者于不同源码文件建立,这就是不同编译单元内定义之non-local static对象,所以我们现在的目标是如何保证tfs在tempDir之前被初始化?
书上讲的太复杂,简单一点总结就是non-local static对象被local static 对象替换,采用Singleton模式(可以看看我对单例模式实现的文章:单例模式)。
class FileSystem{...}; //同前
FileSystem& tfs(){
static FileSystem fs; //定义并初始化一个local static对象,
return fs; //返回一个reference指向上述对象
}
class Directory{...}; //同前
Directory::Directory(){
...
std::size_t disks=tfs().numDisks(); //tfs->tfs()
...
}
Directory& tempDir(){
static Directory td; //定义并初始化一个local static对象,
return td; //返回一个reference指向上述对象
}
这样使用函数返回的"指向static对象"的reference,而不再是static对象本身。
请记住:
-为内置型对象进行手工初始化,因为c++不保证初始化它们
-构造函数最好使用成员初值列,而不要在构造函数本体使用赋值操作。初值列的成员变量排列顺序应该和在class中声明的次序相同
-避免“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象
第一章至此完结,刚刚开始看可能理解的不是很透彻,希望见谅,同时大家发现错误可以指出来,大家一起学习谢谢!!
所有代码与书上一致