条款一:视C++为一个语言联邦
条款二:尽量以const,enum,inline替换 #define
条款三:尽可能使用const
使用建议:
- 对于单纯常量,最好以const对象活enums替换#define
- 对于形似函数的宏(macros),最好改用inline函数替换#defines
- 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
- 当const和non-const成员有着实质等价实现时,令non-const版本调用const版本可避免代码重复。
过时的#define
#define 是预处理命令,它带来的主要问题是:
1、 其中的被替换的代号从来未被编译器看见,从而可能导致跟踪编译时难以定位。
2、 在用以实现宏时容易出错。
#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被累加一次
a的激增次数竟然取决于“它被拿来和谁做比较”!
解析:看#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
程序代码中有CALL_WITH_MAX(a,b)的,就先直接用f((a)>(b)?(a):(b))表达式代替了.然后来看.程序代替过后是
f((++a)>(b)?(++a):(b));
f((++a)>(b+10)?(++a):(b+10));
-?:运算符就是先判断?前面的表达式是否为真,为真的话就运行:前面的代码,否则就运行:后面的代码。
Const关键字
常指针和指针常量
Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:
把一个声明从右向左读。
char * const cp; ( * 读成 pointer to )
cp is a const pointer to char
const char * p;
p is a pointer to const char;
使用const修饰指针时,由于const的位置不同,而含意不同。下面举两个例子,说明它们的区别。
下面定义的一个指向字符串的常量指针:
char * const prt1 = stringprt1; //注意const位置
// prt1只能指向stringprt1字符串,指针是常量,而字符串stringprt1的字符串可以修改
其中,ptr1是一个常量指针。因此,下面赋值是非法的。
ptr1 = stringprt2;
而下面的赋值是合法的:
*ptr1 = "m";
因为指针ptr1所指向的变量是可以更新的,不可更新的是常量指针ptr1所指的方向(别的字符串)。
下面定义了一个指向字符串常量的指针:
const char* ptr2 = stringprt1; //const写在前面,字符串是常量,指针不是常量
其中,ptr2是一个指向字符串常量的指针。ptr2所指向的字符串不能更新的,而ptr2是可以更新的。因此,
*ptr2 = "x";
是非法的,而:
ptr2 = stringptr2;
是合法的。
所以,在使用const修饰指针时,应该注意const的位置。定义一个指向字符串的指针常量和定义一个指向字符串常量的指针时,const修饰符的位置不同,前者const放在*和指针名之间,后者const放在类型说明符前。
常引用
使用const修饰符也可以说明引用,被说明的引用为常引用,该引用所引用的对象不能被更新。其定义格式如下:
const <类型说明符> & <引用名>
例如:
const double & v;
v is a reference to const double
在实际应用中,常指针和常引用往往用来作函数的形参,这样的参数称为常参数。
在C++面向对象的程序设计中,指针和引用使用得较多,其中使用const修饰的常指针和常引用用得更多。使用常参数则表明该函数不会更新某个参数所指向或所引用的对象,这样,在参数传递过程中就不需要执行拷贝初始化构造函数,这将会改善程序的运行效率。
下面举一例子说明常指针作函数参数的作法。
#include <iostream.h>
const int N = 6;
void print(const int *p, int n);
void main()
{
int array[N];
for (int i=0; i<N; i++)
cin>>array[i];
print(array, N);
}
void print(const int *p, int n) //指向常数组的指针
{
cout<<"{"<<*p;
for (int i=1; i<N; i++)
cout<<","<<*(p+i);
cout<<"}";
//*(p+0)=5;
}
常成员函数
使用const关键字进行说明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使用const关键字说明的成员函数不能用来操作常对象。常成员函数说明格式如下:
<类型说明符> <函数名> (<参数表>) const;
其中,const是加在函数说明后面的类型修饰符,它是函数类型的一个组成部分,因此,在函数实现部分也要带const关键字。
有带const修饰符的成员函数处理const常量,这也体现出函数重载的特点。
常数据成员
类型修饰符const不仅可以说明成员函数,也可以说明数据成员。
由于const类型对象必须被初始化,并且不能更新,因此,在类中说明了const数据成员时,只能通过成员初始化列表的方式来生成构造函数对数据成员初始化。
下面通过一个例子讲述使用成员初始化列表来生成构造函数。
#include <iostream.h>
class A
{
public:
A(int i);
void print();
const int &r;
private:
const int a;
static const int b;//const声明
};
const int A::b=10;//主程序中进行初始化
A::A(int i):a(i), r(a)
{}
void A::print()
{
cout << "a=" << a << ",b=" << b << ",r=" << r << endl;
}
void main()
{
A a1(100), a2(0);
a1.print();
a2.print();
//a1.r=6;
}
该程序的运行结果为:
100:10:100
0:10:0
在该程序中,说明了如下三个常类型数据成员:
const int & r;
const int a;
static const int b;
其中,r是常int型引用,a是常int型变量,b是静态常int型变量。
程序中对静态数据成员b进行初始化。
值得注意的是构造函数的格式如下所示:
A(int i):a(i),r(a)
{
}
其中,冒号后边是一个数据成员初始化列表,它包含两个初始化项,用逗号进行了分隔,因为数据成员a和r都是常类型的,需要采用初始化格式。
Const 函数和Mutable关键字
public:
...
std::size_t length() const;
private:
char *pText;
std::size_t textLength; // 最后一次计算出的文字块长度
bool lengthIsValid; // 当前长度是否可用
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // 错误!不能在 const 成员函数中
lengthIsValid = true; // 对 textLength 和 lengthIsValid 赋值
}
return textLength;
}
以上 length 的实现绝不是按位恒定的。这是因为 textLength 和 lengthIsValid 都可以改动。尽管看上去它应该对于 CTextBlock 对象常量可用,但是编译器不答应。编译器始终坚持遵守按位恒定。那么该怎么办呢?
解决方法很简单:利用 C++ 中 与 const 相关的灵活性,使用可变的( mutable )数据成员。 mutable 可以使非静态数据成员不受按位恒定规则的约束:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength; // 这些数据成员在任何情况下均可修改
mutable bool lengthIsValid; // 在 const 成员函数中也可以
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // 现在可以修改了
lengthIsValid = true; // 同上
}
return textLength;
}
STL中的const指针
STL 迭代器是依照指针模型创建的, 所以说一个 iterator 更加像一个指向 T* 的指针。把一个 iterator 声明为 const 的更像是声明一个 const 的指针(也就是声明一个指向 T* const 的指针): iterator 不允许指向不同类型的内容,但是其所指向的内容可以被修改。如果你希望一个迭代器指向某些不能被修改的内容(也就是指向 const T* 的指针),此时你需要一个 const_iterator :
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin();
// iter 就像一个 T* const
*iter = 10; // 正确,可以改变 iter 所指向的内容
++iter; // 出错! Iter 是一个 const
std::vector<int>::const_iterator cIter = vec.begin();
// cIter 就像一个 const T*
*cIter = 10; // 出错! *cIter 是一个 const
++cIter; // 正确,可以改变 cIter
参考文献;
1、 http://wenku.baidu.com/view/83c4be0bf78a6529647d5345.html
2、 http://zhidao.baidu.com/question/166010834.html
3、 《effective c++》
4、 http://www.cppblog.com/tiandejian/archive/2007/04/11/ECPP_03.html