常用的常量可以分为:字面常量、符号常量、契约性常量、布尔常量、和枚举常量
1、字面常量
这种常量最为常见,例如直接出现的各种进制的数字、字符(‘’括住的单个字符)或者字符串(“”括住的一系列字符)等。实际上,只存在基本数据类型的字面 常量。
字面常量只能引用,不能修改,语言实现一般讲它保存在程序的符号表里,而不是一般的数据区中。符号表是“只读”的(这里不同于只读存储器,只是一种访问机制)。此外,初字符串以外,无法读取字面常量的地址。
例如 int *p = &5; //错误,不能读取字面常量的地址
char *pChar = "abdcef"; //正确,取字符串的地址。注意,这里不是给一个字符串变量,字符数组初始化
char c[7] = "abdcef"; //正确,一个字符数组初始化。
*(pChar+2)= 'k'; //错误,不能修改字面常量的内存单元
2、符号常量
存在两种符号常量:用#define定义的宏常量和用const定义的常量。由于#define是预编译伪指令,它定义的宏常量在进入编译阶段前就已经被替换为所代表的字面常量,因此宏常量在本质上是字面常量。
在c语言中,用const定义的常量其实是值不能修改的变量,因此会给它分配存储空间(外连接的);但是在c++中,const定义的常量要具体情况具体对待:对于基本数据类型的常量,编译器会把它放到符号表中而不分配存储空间;而ADT/UDT(一个协议的参考实现)的const对象则需要分配内存空间(大对象)。还有一些情况也要分配存储空间,例如强制声明为extern的符号常量或者取符号常量的地址等操作,都将强迫编译器为这些常量分配内存空间,以满足用户需求。
可以对一个const常量取地址:对于基本数据类型的const 常量,编译器会重新在内存中创建一个它的拷贝,通过其地址访问的是这个拷贝而非原始的符号常量;而对于构造类型的const 常量,实际上它是编译时不允许修改的变量,因此如果能绕过编译器的静态类型安全检查机制,就可以在运行时修改其内存单元:
例如:
const long lng = 10;
long *pl = (long*)&lng; //取常量的地址
*pl = 1000; //迂回修改
cout<< *pl <<endl; //1000,修改的拷贝的内容
cout<< lng<<endl; //10,原始常量没有变
class Interger{
public:
Interger():m_lng(100){}
long m_lng;
}
const Interger int_1;
Interger *pInt = (Interger*)&int_l; //去除常数属性
pInt->m_lng = 1000; //迂回修改
cout<< pInt->m_lng <<endl; //1000,修改const对象
cout<< int_1.m_lng <<endl; //1000,迂回修改成功
3、契约性常量
用const修饰函数返回值,如果给“指针传递”的函数返回值加const那么函数返回值是一种契约性常量,不能直接被修改,并且该返回值只能被赋值给加const修饰的同类型指针(除非强制转型)。例如:
const char *GetString(void);
char *str = GetString(); //错误;
const char* str = GetString(); //正确;
//=======================================================================
#include<iostream>
#include <stdlib.h>
using namespace std;
int Stringlength(const char *s)
{
cout<<s<<endl; //输出值s并非字符串地址,而是字符串,注意C++/C默认char* 表示字符串
if(s == NULL) exit(1);
int length = 0;
char *base = (char *)s; //这里是将s的类型强制转换了,如果不强制转换应该为const char *base = s;
while((*base++) != ' \0 ' ) length++;
return length;
}
void main()
{
char *ch = "Hello,c++ world!"; //取字符串常量的地址
cout<<ch<<endl; //输出的是整个字符串
cout<<*ch<<endl; //输出的是字符串的首个字符
int len = Stringlength(ch);
cout<<len<<endl;
}
在“引用传递”时,也存在契约性常量:例如
void ReadValue(const int& num)
{
cout<<num;
}
int main()
{
int n = 0;
ReadVaule(n); //契约性常量,n被看做是const
}
enum Gigantic
{
SMALL = 10,
GIGANTIC = 100000000
};
5、布尔常量
布尔常量只有两个 false和true,大小写不敏感。
总结:
const与#define都可以用来定义常量,但是前者有更多优点:
(1)const有数据类型,而宏常量没有。编译器可以对前者进行静态安全检查;
(2)有些集成化调试工具可以对const常量进行调试,而对宏常量不可以
(3)被const修饰的东西都受到c++/c语言实现的静态安全检查机制的强制保护,可以预防以外修改。