01.尽量以const,enum,inline代替#define
也可以这么说“宁可以编译器替换预处理器”。
首先这里有个符号表的概念:在计算机科学中,符号表是一种用于语言翻译器(例如编译器和解释器)中的数据结构。在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及内存地址。
那么当你使用#define定义一个记号名称时,如果此记号从未被编译器所看见,或者在编译器开始处理源码之前就被预处理器移走了,那么此记号名称就有可能没有进入符号表,就会发生编译错误,且如果此#define语句所在的头文件还不是你写的,那么你将要去追踪它而浪费时间。
解决方法1:使用const常量代替宏
const int a = 10;
作为一个语法常量,a肯定能被编译器看到并被收入近符号表中。此外预处理器会盲目地将宏名称替换,导致内存中出现多份目标值,而const绝不会出现相同情况。
-
还有就是const修饰指针的两种情况:
- 1.常量指针,const修饰的是指针,表示指针不能再次指向其他位置,但是被指物可以被修改
int* const p = a; //const pointer,non-const data
- 2.指针常量,const修饰的是指针所指向的物,表示此物不能修改,但是指针可以指向其他位置
const int* p = a;// const data,non-const pointer int const *p = a;// ...
解决方法2:使用枚举enum
一个属于枚举类型的数值可权充ints被使用
class Student
{
private:
enum {LENGTH = 10 };
int grades[LENGTH];
...
};
注意:1.enum的某些行为像#define,例如取const的地址是合法的,但取一个enum的地址是不合法的,取#define的地址通常也是不合法的。如果你不想让别人使用指针或引用指向你的某个整数常量,enum可以帮你实现这个约束。
解决方法3:使用inline
对于形似函数的宏,最好使用inline函数代替#define,特别是涉及到++/–问题以及乘除大小比较算法的时候,光是括号的补充就够头疼了。对于这些规模较小但调用次数频繁的代码,记得要使用内联函数
template <typename T>
inline int MAX_WHO(const T& a,const T& b)
{
return a>b?a:b;
}
总结:有了const,enum,inline,我们对预处理器的需求降低了,但#include,以及#ifndef/#ifdef依然还是有相当重要的地位的。
02.尽可能使用const
关键字const多才多艺,你可以用它在class外部修饰全局或者namespace作用域中的常量,或修饰文件、函数、或区块作用域中被声明为static的对象。你也可以用它修饰class内部的成员变量。
对于const的指针两种用法,在01有说明,STL迭代器系其实也是以指针为根据建立的,所以迭代器的作用就像个T* pointer:
1.声明迭代器为const就像声明指针为const一样(即声明一个T* const iterator),表示这个迭代器不得指向不同的东西。
2.如果你希望迭代器所指向的东西不可被改动,你需要的是const_iterator
std::vector<int> vec;
...
const std::vector<int>::iterator p = vec.begin(); //== T* const p
*p = 10; // 正确√
++p; //错误×
std::vector<int>::const_iterator cp = vec.begin(); // == const T* p
*p = 10;//错误×
++p;//正确√
const成员函数
许多人漠视了一个事实:两个成员函数末尾是否加const可以构成重载。
将const修饰于成员函数的目的,是为了保证该成员函数可作用于const对象上,只有const对象才可以调用const成员函数。
那么如果要在const成员函数中修改类的变量,切记是不能直接修改的。解决方法很简单:利用mutable关键字修饰需要改动的类成员变量。
在const和non-const成员函数中避免重复
例如当实现一个operator函数时,难道需要复制粘贴一份给const修饰的吗,代码重复会让人头疼。我们真正要做的是仅一次实现这个函数的功能并使用它两次,也就是我们必须要让其中一个调用另外一个。
如下:
class TextStation
{
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 TextStation&>(*this)//non-const成员函数的*this属性为non-const TextStation,需要进行类型转换,转换成const TextStation类型,来调用const成员函数operator[]
[position]); //调用后对其进行去const转换
}
private:
std::string text;
};
这里用了两次转换,第一次将non-const对象转换为const对象,从而成功调用const operator[ ],之后对其进行去常操作,这样减少了代码重复。
但是记住:“运用const成员函数实现出其non-const孪生兄弟”的语法是可以好好学习的。 但是,其反向做法,让const成员函数调用non-const是万万不可取的,const成员函数承诺不改变对象的逻辑状态,但是non-const不承诺,因此调用non-const本身就会有很大的风险。
总结:
- 1.对于单纯常量,最好以const对象或enum替换#define
- 2.对于形式函数的宏,用inline替换#define
- 3.const可被施加于任何作用域内的对象、函数参数、返回值、成员函数本体
- 4.当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复