一、简单常量
C++中的常量具有如下直观的形式:
const long SOME_CONST = 10;
清注意:
1、简单的整型常量可以用于指明数组的大小、枚举值以及模板的整型参数等。
int a[SOME_CONST]; //指定数组的大小
enum{x = SOME_CONST }; //指定枚举值
frame<int, SOME_CONST , SOME_CONST > frm; //指定模板的整形参数
2、所有的基本类型都可以用来定义常量,但是浮点常量只能用在浮点字面值使用的地方,例如这种浮点常量不能用于定义数组的大小。
float FLOAT_CONST = 10.0; //定义一个浮点类型的常量
float a[FLOAT_CONST]; //false
int b[FLOAT_CONST]; //false
3、编译器的影响。编译器会在编译期为这些常量赋值,除非是为了获得这些常量的地址,否则在连接模块中不占用内存。
4、对常量对象的const限定强制转换会引起难以预料的问题。例如考虑下列代码在不同编译器环境下的输出。
const int w = 10;
int main()
{
int &w = const_cast<int&>(W);
int const *p = &W;
w *= 2;
int const *q=&W;
int a[W];
printf("%d,%d,%d,%d\n",W, NUM_ELEMENTS(a), w, *P, *q);
return 0;
}
GCC和Visual C++ 6.0的反应是程序崩溃,而在Borland环境下运行结果是10、10、20、20、20。
二、类类型常量
首先介绍下什么叫类类型常量。类类型常量是指以class、struct以及union关键字之一所声明的类型。
类类型常量的定义如下:
class student
{
public:
student(int id): stdudentId(id){}
public:
int stdudentId;
};
const student stu = 3;
清注意:
1、类类型常量实例不能被用作字面值来使用。
例如考虑下面的情况:
int a[stu. stdudentId]; //false
enum{ x = stu.stdudentId }; //false
frame<int, stu.stdudentId, stu.stdudentId >frm; //false
2、常量对象并非是在编译期间进行创建的,而是在运行期间(这种对象的构造函数与普通对象一样,可以在编译期以及连接期的优化步骤中消除掉)。由于他们所具有的的静态作用域存在于任何特定函数的作用域之外,所以他们必须在程序执行期间被创建(和析构)。
由此这种类类型常量的使用存在三个潜在的缺点:
首先,特定类型的构造函数和析构函数可能具有显著地运行代价。这个代价被加诸于应用程序/模块和关闭阶段,通常这些代价都是值得关注的。
其次,全局对象之间可能会出现依赖性的情形。考虑如下情形:
const student stu = 3;
extern const student stu3;
const student stu2 = stu3;
const student stu3 = stu;
int main()
{
printf(“%d %d %d\n”,stu,stu2,stu3);
……
}
程序的运行结果是3,0,3,而不是3,3,3。原因是stu2在stu3初始化之前就进行了拷贝,由于全局对象栖身于全局内存中,所以能够能够确保得到0初始化。
最后,如果(非extern)类类型常量对象在编译单元之间被“共享”的话(即声明于一个共享头文件当中),那么每个编译单元实际上接受的是该常量实例的一份单独的拷贝。
三、成员常量
除了声明在全局和函数作用域中,常量还可以被声明在类作用域中。考虑到
class student
{
public:
static const int id = 5; //成员常量
static const int num; //常量静态成员
};
请注意:
1、成员常量跟静态成员的语法类似,除了前者具备变量声明的形式和初始化语句之外。然而这种成员常量仅仅只能够用在整型和枚举型身上,他们又与静态成员不同,因为这种成员常量可以使用在编译期的表达式中,并且无需单独定义。
2、在需要的时候定义常量静态成员,考虑到获取成员常量的地址或者将他们绑定到引用的话,你就需要给出他们的定义。
int const *p = &student::id;
int const &q = student::id;
/* static */ const int student::id; //这里进行了定义
3、成员常量不能使浮点型的。
class Maths
{
public:
static const double pi = 3.1415926; //成员常量必须是整型
};
然而又存在不一致性,浮点型成员常量作为名字空间成员是合法的。
namespace Maths
{
static const double pi = 3.1415926; //没问题
}
4、考虑到成员常量并非所有的编译器都支持,成员常量的替代方案:
首先,使用枚举型代替这种成员常量,缺点是大多数编译器都不允许枚举值大于他们的int类型。
其次是,定义一个基于静态的方法返回这样的一个常量。例如:
class Maths
{
public:
static const double pi()
{
return 3.1415926;
}
};
缺点是这样的静态方法返回的常量不是编译期常量,而是运行期间得到的常量。即使这种静态方法在编译期间被优化为一个常量值,他的值依然实在运行期间求出的,因此不能将其用于编译期表达式。例如:
class X
{
static const int i1 = std::numeric_limits<char>::digits; //没问题
static const int i2 = std::numeric_limits<char>::pi(); //错误
};