视C++为一个语言联邦
今天的C++已经是个多重范型编程语言,同时支持过程形式、面向对象形式、函数形式、范型形式、元编程形式。为了理解C++,必须认识其主要的次语言,总共有四个:
1. C。C++以C为基础,语句、预处理、内置数据类型等来自C,但C没有模板、没有重载……
2. Object-Oriented C++。面向对象C++,class、封装、继承、多态、虚函数等
3. Template C++。C++范型编程
4. STL。标准模板库,对containers、iterators、algorithms以及function objects的规约有极佳的紧密配合与协调
尽量以const,enum,inline 替换 #define
现在我们写这样一个宏定义
#define Aspect 1.653
#define
不是语言的一部分,是预处理阶段完成的,没有到编译器,因此 #define
定义的记号名称也就没有进入符号表(symbol table)。当我们因为运用#define
定义的常量而发生编译错误时,那么会很浪费时间来追踪它。解决方法是我们以一个常量来替换上述的宏: const double Aspect = 1.653;
用常量替换宏时,有两种特殊情况:
1.定义常量指针。
由于常量定义式通常被放在头文件中,以便被不同的源码包含,因此有必要将指针声明为const,假如要在头文件中定义一个不变的 char*base 字符串,必须写const 两次:const char * const authorName = "Tanswer";
或者定义成 const std::string authorName="Tanswer";
2.class专属常量。
class stu{
static const int num = 10; //常量声明式
int score[num]; //使用该常量
};
通常C++会要求你为所使用的任何东西提供定义式,但是如果它是class的专属常量且是static且为整数类型(包括int char bool)时,可以区别对待。如果不取它的地址,你可以声明并使用它们而无需提供定义式。但是如果你要取它的地址或是编译器并不认可这种行为坚持要看到一个定义式时,你需要在实现文件中提供定义式:const int stu::num;
,这里要注意是实现文件。如果编译器不支持在static成员在声明式获得初值,那么可以将初值放在定义式。
为什么要使用enum hack?
当你在class编译期间需要一个class常量值,例如上面score
数组需要知道大小,可使用所谓的“the enum hack”补偿做法。理论基础是:一个属于枚举类型的数值可权充int被使用。于是上述类可定义如下:
class stu{
enum { num = 10}; //"the enum hack"令num称为成为10的一个记号名称
int score[num]; //OK
};
- enum hack的行为某方面比较像
#define
而不是const
。如果你不想让别人获得一个pointer或reference指向你的某个整数常量,enum可以帮你实现这个约束,enum和#define一样绝不会导致不必要的内存分配。 - 很多代码都使用了enum hack,事实上,enum hack是
template metaprogramming
(模板元编程)的基础技术。
inline
我们还会用到#define的情况是用它实现宏,看起来像函数,但是不会招致函数调用带来的额外开销。但是有很多缺点,我们可以用template inline 函数
替代,可以带来宏带来的效率以及一般函数的所有可预料行为和类型安全性。
#define CALL_WITH_MAX(a,b) f( (a) > (b) ? (a) : (b) )
//a和b的较大者调用f函数
可以用下面函数替代
template <typename T>
inline void CALL_WITH_MAX(const T& a,const T& b)
{
f(a>b?a:b);
}
这是真正的函数,遵守作用域和访问规则,比如可以写成类内的私有函数,宏则不可以。
总结:
对于单纯常量,最好以const对象或enum替换#define;对于形似函数的宏,最好改用inline函数替换#define
补充:声明和定义
声明是告知编译器该程序元素的名称以及类型,定义则是使编译器为程序元素分配内存空间。
二者最根本的区别在于是否分配内存。声明不会导致内存的分配,而定义会分配内存。在C++程序中声明可以有多次,但是定义只能有一次。因此不能将变量的定义放置于头文件中,由于头文件会被多次引用,就会导致变量在多个源文件中被重复定义,这是C++所不允许的。但是也有例外的情况,比如类的定义、const变量的定义、inline函数就可以放在头文件中。
下列几种情况声明不可充当定义,其余声明可以充当定义
1. 函数原型(无函数体的函数声明),如double fac(int );
2. 没有定义的类名声明,如class T;
3. 类声明中的静态数据成员,如上面讲的 class专属常量
4. 包含extern
关键字且没有初始化变量、对象或函数体,比如:
extern int i; //声明 extern int i = 10; //定义