摘要
这一节按照顺序说了以下这么5件事情:
- 上来首先强调一个观念,尽量用const常量来替换#define定义,原因有两个,1) const常量名会进入记号表方便调试;2)#define会导致目标码出现多份对象;第1条引申出两个特殊情况分别列出在第2和第3;
- 但是第1条所说的const常量有两个特殊情况,这里第一个特殊情况是定义常量指针,因为常量定义式通常放在头文件,因此头文件内的常量指针必须声明为const指针,也即是:const char * const authorName = "Scott Meyer"。其实我觉得这里更应该展开说明的是“由于常量定义式通常被放在头文件内”这一句,这个后面展开讨论;
- 第二个特殊情况是所谓的class专属常量,需要满足“常量”和“class专属(多个对象共享一份实体)”两个属性,所以既需要const,又需要static,这里引出很多稍显“混乱”的场景,写在class类型的定义内部的static const类型其实是一个声明式,是否可以在声明的同时赋值?是否需要在cpp文件中再加一行定义式?这里书中展开说了一大堆;
- 从第3引申出来enum,如果旧的编译器不支持static成员在声明时赋初值但在编译期间又确实需要这个class常量值,那么就用enum hack来解决这个问题;
-
用define来实现像函数一样的宏很容易出现意想不到的问题,典型的是自增自减操作,因此为了避免类似情况,宁愿采取template inline的方式来替代define宏。
常量定义式通常被放在头文件内
文中说,“由于常量定义式通常被放在头文件中(以便被不同的源码含入)”,这句话很容易给人造成困惑,特别是有较多C背景的同学,确实在C语言下将const常量放在头文件中被不同的源码包含确实是行不通的。
-------------------------------------------------------------------------myhead.h
const int a = 1;
const char * const aChar="This is a const char[]";
--------------------------------------------------------------------------func1.c
#include "myhead.h"
int func1()
{
printf("This is func1 :\n");
printf ("a = %d, and &a=%p\n", a, &a);
printf("char = %s, and $char = %p\n", aChar, &aChar);
return 0;
}
--------------------------------------------------------------------------func2.c
#include "myhead.h"
int func2()
{
printf("This is func2 :\n");
printf ("a = %d, and &a=%p\n", a, &a);
printf("char = %s, and $char = %p\n", aChar, &aChar);
return 0;
}
--------------------------------------------------------------------------main.c
#include "func.h"
int main()
{
func1();
func2();
return 0;
}
编译时报错
/tmp/ccTtku40.o:(.rodata+0x0): multiple definition of `a'
/tmp/cccDr6AR.o:(.rodata+0x0): first defined here
/tmp/ccTtku40.o:(.rodata+0x20): multiple definition of `aChar'
/tmp/cccDr6AR.o:(.rodata+0x20): first defined here
collect2: error: ld returned 1 exit status
但是如果是C++,不仅可以编译通过运行,打印出来func1和func2中的const对象地址是不一样的,说明func1.cpp和func2.cpp各自有一份副本。
This is func1 :
a = 1 and &a = 0x401084
char = This is a const char[] and &aChar = 0x401088
string = This is a const string! and &aString = 0x401084
This is func2 :
a = 1 and &a = 0x40111c
char = This is a const char[] and &aChar = 0x401120
string = This is a const string! and &aString = 0x40111c
第一反应这应该是C++的特性,事实确实如此,《C++ Primer 4》p86 say:const对象默认为文件的局部变量。
class专属常量
class类的定义通常是写在头文件里,class专属常量需要满足“常量”和“class专属(多个对象共享一份实体)”两个属性,因此在class定义式内会有static const int NumTurns = 5;这样的常量声明式。这里有两方面的问题:
- class类定义内已经声明了常量声明式,还需要在cpp文件中再写一遍定义式吗?
- class类内的常量声明式是否可以直接初始化;
一个个来回答,同时引申出第3条:
- 如果是class专属常量,且类型为整数类型(int char bool),只要不取地址,就不需要再提供定义式,否则就需要提供定义式;当然某些奇葩的老旧编译器是一律要求要提供定义式的;
- 常规来说是可以在声明时直接给整数常量的class专属常量赋值,此时如果提供了定义式也不需要赋值了。但是同样的,某些旧的编译器依然不允许在声明时给整数类型的class专属常量赋值,那没办法,只能在cpp中再写定义式并赋值了;
- 但是有些时候需要在编译期间就拿到class专属常量的值,这要求声明时就予以赋值,而老旧编译器不允许在声明时赋值,怎么办?此时只有放弃static const的方法,采取enum hack的方式实现了(总之就是不用#define)。
综上,最具移植性的方法是如下:
1.不需要在编译期间就拿到class专属常量值,其声明写在class类型定义的头文件内且不赋值,定义式写在clss实现cpp文件内且赋值,像这样:
----------------------------------------------------------myClass.h
class myClass {
private:
stastic const int a;
...
};
-----------------------------------------------------------myClass.cpp
const int myClass::a = 3;
2.但是如果需要在编译时就知道这个a的值,那最好换用enum hack的方式万事大吉,像这样:
-------------------------------------------------------------------myClass.h
class myClass {
private:
enum { a = 3};
int classMates[a];
...
}