前段时间写的C++程序,绝对是垃圾代码,运行效率着实差强人意。近来在看Effective C++(但是看网上有人说有些条款已经过时),就此做了些读书笔记,想说记录下来,加深下自己的印象,希望有一天能写出凑合的代码。
尽量不用宏定义——#define
书上建议尽量不要使用#define,而使用const、enum、inline来对#define进行替换。
#define MY_VAL 99
//使用宏定义的话,有些编译器可能没有看见MY_VAL名称(先送进预处理器中)
//可替换为
const int my_val = 99;
//使用常量的话相比较还可以减小目标码的量(用宏定义可能会多次复制该值)
//宏定义是不重视域的,没法被封装
//使用enum hack技术来替代宏定义
class MyClass
{
private:
enum {MyValue = 10};
int scores[MyValue];
};
//用模板元技术的话,就是相对于const更像#define
//取MyValue的地址非法(使用const的话可以)
//不会导致非必要的内存分配
//看上去像函数的宏,使用内联进行替换
#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
//上述宏定义,会出现奇怪的事情
//使用模板与内联替换
template <typename T>
inline void callWithMax(const T& a,const T& b){
f(a > b ? a : b);
}
多使用const
const非常神奇,感觉也好麻烦。弄清原委后,大胆的使用const会减轻编码时的麻烦,可以帮助编译器诊断出错误用法。
//const在*左边表明修饰指针所指的量,在右边就表明修饰指针为常量
const char* const p; //常量指针与常量的数据
//迭代器iterator有点类似于T* const
//而const_iterator就像const T*
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin(); //iter是常量
std::vector<int>::const_iterator cIter = vec.begin(); //cIter *是常量
//将const实施于成员函数
//1.确认该成员函数是可作用于const对象上(也使得接口更容易理解)
//2.使操作const对象成为可能
class TextBlock{
public:
const char& operator[](std::size_t position) const
{return text[position];}
char& operator[] (std::size_t position)
{return text[position];}
private:
std::string text;
};
void print(const TextBlock& ctb){
std::cout << ctb[0];
}
//像如下代码是无法执行的
//const TextBlock ctb("dream");
//ctb[0] = 'q';
//因为编译器强制实行的是bitwise constness(成员函数只有在不改变对象的任何成员变量时才可以说是const)
//但如果改变的对象成员变量是指针的话,就有例外
class CTextBlock{
public:
char& operator[](std::size_t position) const {
return pText[position];
}
private:
char* pText; //将数据存储为char*
};
//
const CTextBlock cctb("smoke");
char* pc = &cctb[0];
*pc = 'L'; //此时可以改变cctb的内容
//引出另一派别 logical constness,在客户端(应该是指其他的编译单元)检测不出的状态下是可以改变成员变量的
//但编译器始终坚持bitwise constness,再引入mutable来改变bitwise constness的约束
class CTextBlock{
public:
std::size_t length() const;
private:
char* pText;
mutable std::size_t textLength; //此时编译器允许改变该变量
mutable bool lengthIsValid; //此时编译器允许改变该变量
};
std::size_t CTextBlock::length() const{
if(!lengthIsValid){
textLength = std::strlen(pText); //改变
lengthIsValid = true; //改变
}
return textLength;
}
//在const与non_const成员函数(两者有等价实现时)中避免重复方面,提出利用转型来减少代码重复
//在non_const中调用const,反过来不行(这样曾经允诺不改动的被改动)
char& operator[](std::size_t position){
return
const_cast<char&>( //将返回值的const移除
static_cast<const TextBlock&>(*this)[position] //加上const
);
}
确定对象在使用前已经被初始化
内置类型要进行手动初始化,但其他的初始化任务就放在构造函数上。
class MyClass{
public:
MyClass(const std::string& name);
private:
std::string theName;
std::string hisName;
};
MyClass::MyClass(const std::string& name){
theName = name; //此时是赋值,并不是初始化
//先调用default构造函数初始化,后进行赋值
hisName = "Morty"; //初始化
}
//更有效率的方法是使用成员初值列替换赋值
MyClass::MyClass(const std::string& name)
:theName(name), //此时是初始化
hisName("Morty") //写的时候不能遗漏某个成员变量,否则会开启不确定行为
{}
//有的时候必须使用成员初值列,干脆直接使用初值列更简单
class MyClass{
public:
MyClass(const std::string& name);
private:
const std::string theName; //成员变量是const不能赋值
std::string& hisName; //成员变量是引用不能赋值
};
//有时候要注意C++的成员初值列的顺序(就代表了初始化顺序)
int myArray[num]; //必须先初始化num再初始化数组
//在不同的编译单元里面(有静态对象的单元),如果在不同编译单元的引用的话
//static对象的初始化顺序就变得不可琢磨
//第一个编译单元:
class FileSystem
{
public:
std::size_t numDisks() const;
};
extern FileSystem tfs; //供第二个编译单元使用
//第二个编译单元:
class Dir{...};
Dir::Dir(){
std::size_t disks = tfs.numDisks();
}
Dir tempDir(); //不确定是否tfs会在tempDir之前初始化
//为避免这种情况,引入单例实现方法(应该算懒汉模式吧)
//实质上是以函数来返回对象,这样子保证了函数调用期间会在首次遇到该对象的定义式前被初始化,这正是我们所要的
//我们用函数调用返回一个指向当地静态变量的引用来替代直接指向非当地静态变量
//第一个编译单元:
class FileSystem{...};
FileSystem& tfs() //这个函数来返回该对象的引用
{
static FileSystem fs;
return fs;
}
//第二个编译单元:
class Dir{...};
Dir::Dir(){
std::size_t disks = tfs().numDisks(); //由此保证tfs先初始化
}
Dir& tempDir(){ //这个函数来返回该对象的引用
static Dir td;
return td;
}
//而后简略讨论下单例模式的多线程安全(饿汉模式)
//即在单线程启动阶段事先调用所有的返回引用的函数
class FileSystem{
...
static FileSystem* fs;
};
FileSystem* FileSystem::fs = new FileSystem();
FileSystem* tfs()
{
return fs;
}
参考:
《Effective C++》第三版