前言
想一想C++也学了大半年但博客没怎么写过C++方面的。一是当初自己看《C++ Primer》很懵,写博客也只是抄书,二是后来代码量上去了,踩了各种坑之后也收获的很多,更多的都是要牢牢记住的基础用法,没啥整理的必要。。。
对于C++的学习,《Effective C++》绝对是一本必看的书,但又不是和《C++ Primer》同时看,而是在有了一定的C++基础和代码量之后,对于基础语法,知识点都有所掌握,但是不知道如何正确使用C++时,再深入阅读,这时收获更多。
同时,这本书所讲述的并不是一些规定,而是有效运用C++的方法,所以需要仔细的理解和体会,因此,希望通过整理读书笔记深入理解其内容。
正文
Item 1: View C++ as a federation of Languages
这里侯捷老师翻译为“视C++为一个语言联邦”,我觉得确实很正规,也很准确,但是对于我们理解上可能并不容易(联邦。。。)。
C++,有很多人爱它,但好像更多人Diss它,在国内遍地Java培训机构,但却少有C++的培训班,我想有很大一部分原因就是C++的内容太多了,不仅向现代高级语言看齐,又要兼容古老的C。
我们可以把它分为四大部分:
1。 C,C++的设计就是要和C兼容,以至于很多地方做的很XX,不如Java等方便,但是好处就是和OS更加亲近了。
2。OO 也就是从C到C++最大的不同吧,有class,有继承,多 态,封装。
3。泛型,主要就是模板(template),对于写库来说是个重头戏。
4。STL,一门编程语言想要流行开来必须要有一套高效,便利的轮子(库),当然对学习者来说也是优秀代码的宝库,STL符合这两点,(iostream可能。。。)。
在我们实际使用C++时往往只会用其中的一部分,比如ACMer可能就是C部分加上STL,而在实际工程中可能主要是OO,库的作者则主要是template,所以C++就像一个大工具箱,选好你适合的工具,当然,还要遵从工具的使用规约。。。
Item 2: Prefer consts,enums,inlines to #defines
记得C语言课上,讲到#define时老师往往会说把多次出现的常量用#define定义(比如PI),那么为什么不用const呢(黑线)?
使用const的好处如下:
1.调试更加方便,由于宏是在预处理阶段被CPP展开,所以也许编译器并不知道,如果你在使用这个常量的过程中遇到问题,你完全是蒙蔽的,尤其是使用到别人的代码时。
一个形如9.8的常量你可能并不知道有什么特殊意义,但是一个const 常量是由编译器处理的,将被记录到symbol table里,方便我们之后的调试(能看到变量名可以定位bug发生点)。
2.减少目标代码长度,盲目地将宏替换可能导致目标代码会出现多份,而一个const常量则不会。(这里不是很懂为什么宏替换会出现多份)
同时,对于C++里的常量,#define有其无法完成之事,即限定作用域(类作用域)的常量,也就是说对于类内的专属常量,#define并不能限定其作用域。
我们当然可以通过#undef这种机制来限制宏的作用域,但是对于一个类内的常量确无法做到。
而const则可以。
当我们需要用到这个这个常量时必须要加上作用域运算符,这就限制类常量的作用域。
值得一提的是,作为一个static成员变量,我们知道其需要在类内声明,在类外定义,这样才保证同一个类的多个对象共享一个static成员变量。
然而对于static const int/char/bool 类型的常量可以直接在类内定义(书上说是较新的编译器,但本书成于2005年。。。)
class A{
public:
static const int size = 2; //正确
static const float Pi = 2.3;//错误
};
但如果你需要的是类内常量是在类内使用或者你的编译器真的不支持这种in class定义,就需要enum出场了。
class A{
public:
//static const int size = 2;
enum{
size = 2//通过enum来设置类内使用的类内常量
};
char myarray[size]
};
enum所定义的常量名和const一样,也进入symbol table,方便我们调试。
说起enum,还有一个优点,不过这也是针对旧或者“不够优秀”的编译器,便是enum中的常量不能被&或者被引用绑定,const则可能,但我在自己的g++测试时,const常量也无法被引用绑定或者&,所以感觉enum的好处,enmmmm基本被const替代。
而老师讲到#define时,可能还会说到另一种用法
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b)) // 调用函数f,将ab之中的较大值作为参数调用f
小组的面试也出过这样的问题,当然,我们都知道这样的宏会带来怎样的副作用(传入一个i++)。
这里的#define是为了,呃,实现C语言中的泛型,and代码很短不用编写成函数,但是我们来到C++了,对于泛型,我们有template,而很短的代码则编写为就地展开的inline函数。
template <typename T>
inline void callWithMax(const T &a, const T &b)
{
f(a > b ? a : b);
}
同样,当这样的函数变为类内专属时,#define一样无能为力。
但是CPP还有其不可替代的作用,包括#ifdef,包括与C兼容,包括看ACMer的代码总会一大堆宏。。。
Item 3: Use const whenever possible
const确实还是很好用的,当你需要规定一个东西为常量时,设置成const就让编译器替你做检查了。
对于重载的运算符,我们让其返回的对象为const可以避免一些无谓的错误。
const Object operator*(Obeject &a, Object &b)
// 一个手误的操作,将因为const被制止
if((a*b) = c)
C++的重载机制支持对于参数有无const的重载,和调用对象是否为const的重载。
int fun(char *a)
{
return 2;
}
int fun(const char *a) //若两种都有,则能重载,若只有const 参数版本,则按照C风格可以传非const参数
{
return 1;
}
class A{
// 即按照调用对象是否为const重载
const char &operator[](int b) const
{
return test[b];
}
char &operator[](string t)
{
int a =2;
return test[a];
}
string test;
};
需要注意的是,这里能调用const成员函数的const对象,指的是bitwise constness,也就是对于对象的每一个成员变量都无法改变其任何一个bit。
但若对象有一个指针变量呢?其指向的对象是否视为本对象的一部分呢?
所以const对象也可以有变化的成员变量,通过mutable关键字声明。
而const成员函数和non-const成员函数往往只有些许不同,所以我们可以只编写const成员函数,而对于non-const成员函数通过调用const成员函数+cast。
一个例子如下
const Object &operator[](std::size) const
{
// 老实实现
}
Object &opeartor[](std::size pos)
{
//static_case<const Object>(*this)[pos] 是将non-const对象转为const对象来调用const成员函数
//const_cast将其返回的const Object &的const 移除,来符合函数的返回值类型
return const_cast<Object &>(static_cast<const Object>(*this)[])
}
但是坚决不要通过const成员函数调用non-const成员函数(你懂的)。