首先我们来了解一下现在所学的C标准,分别是C89、C99和C++99,C89标准的C语言中的const和C++中的const是有区别的,而在C99标准的C语言中是相同的。我们目前所学的C语言标准为C89标准。
- const修饰的量为一个常量即不能被修改的量。但在C语言(C89)中的const可以不初始化但后续也就无法对其赋值,所以尽管不初始化不会出错。但要使用const修饰的量就需要对其进行初始化。另外,既然const修饰的量为一常量那么const修饰的量能不能用作数组的下标?
//main.c #include<stdio.h> int main() { const int a = 10; //int array[a] = {0};//error C2057: 应输入常量表达式 //则可以得知在C语言中const修饰的量为一个常变量不是常量 int *p = &a; *p = 20; printf("%d %d\n", a, *p); return 0; //20 20 }
- 程序输出结果都为20,在这里我们就可以知道常变量和普通变量的区别在于常变量不能用于左值,其余的用法和普通常量一样。在一个工程的多个.c文件中的const修饰的量可以共享。
在C++的代码中const必须初始化,这于const在C++中的编译规则(所有使用常量名字的地方去不替换成常量的初始值)有关。
//main.cpp #include<iostream> using namespace std; int main() { const int a = 20; int arr[a] = { 0 };//编译可以通过 int *p = (int *)&a; *p = 30; cout << a << " " << *p << endl; return 0; //运行结果为20、30,发现并没有将const定义的量的值修改 }
- 由上可知在C++中const修饰的量为一个常量,可以做左值且不能修改它的值。只有当给const修饰量的值不明确的时候会退化成一个常变量。在一个C++工程中的多个.cpp文件中要用到某一个.cpp文件中const修饰的量是无法访问的,这是由于常量的符号类型为local的,只在当前文件可见,其余文件无法访问。如若想要访问这个const修饰的量,需在定义处加上extern。
以下例子全是在C++下实现
const和一级指针的结合
/* 一级指针和const结合的方式: const int *p; 修饰int即*p 所指向的内容无法改变 int const *p; 修饰int即*p 所指向的内容无法改变 int *const p; 修饰int* 即*p 不能改变指向 const int *const p; 修饰int和int* 指向和所指向的内容都不能改变 */ int main() { int a = 10; const int b = 20; a = b; //b = a; //error C3892: “b”: 不能给常量赋值 }
const修饰的作用:防止常量的值被直接或间接修改,即防止常量的指针或引用被泄露出去。这些情况编译器都会报错。
int main() { const int a = 10; //int *p = &a; //error const int * --> int *存在修改a值得风险*p = 0; const int *p1 = &a; int b = 20; const int *p2 = &b; //对于普通常量b可以用任意类型的指针指向,因为b只可以修改 int *q = p2; //error C2440: “初始化”: 无法从“const int*”转换为“int *” //这是因为p2可以存放const修饰常量和普通敞亮的地址,为了防止存在修改const修饰常量的可能所以错误。 }
现在我们用C++中的头文件typeinfo来打印一下类型:
#include<iostream> #include<typeinfo> using namespace std; int main() { int a = 10; const int *p = &a; int *const q = &a; cout << typeid(p).name() << endl; //const int* cout << typeid(q).name() << endl; //int * ----> 这是为什么? //原来const没有修饰*(指针)或&(引用)时不用考虑const,如: const int b =20; cout << typeid(a).name() << endl; //int cout << typeid(b).name() << endl; //int return 0; }
const和引用的结合:
我们来补充一点有关引用的知识。引用的底层实现和指针的实现是一样的,但在使用时会自动解引用,不需要再像指针那样先解引用后使用。引用的实质就是对引用的对变量起一个别名,引用一经定义就无法改变,引用需要开辟内存,保存的是引用的变量的地址。
int main() { /* //引用的用法: int a = 10; int &b = a; //用b引用a,b相当于a的别名,操作b就等于操作a */ const int a = 10; //int &b = a; //error C2440: “初始化”: 无法从“const int”转换为“int & //不能用普通引用引用常量 const int &b = a;//引用常量得使用常引用 const int &c = 10;//这句可以执行是因为在引用过程中系统自动生成了一个临时变量。汇编如下图: }
我们来做一个例题:如何在内存0x0018ff44地址处写一个四字节整数20,用指针和引用变量实现?
#include<iostream> using namespace std; int main() { //指针变量 int *p = (int *)0x0018ff44; *p = 20; //引用变量 int* const &q = (int *)0x0018ff44; *p = 10; //其中间生成了临时量,我们可以用如下伪代码来说明: //int *const ptemp = (int *)0x0018ff44; //int *const &q = ptemp; }
const &p可引用常量(可寻址和不可寻址常量),&(引用)不参与类型。
const和二级指针结合:
/* 引用于二级指针结合的行式: const int **p; //修饰int 098765432即**q const *int *p; //修饰int* 即*q int **const q; //修饰int** 即q */ int main() { /* int a = 20; //int *p = &a; //const int **q = &p;// error C2440: “初始化”: 无法从“int **”转换为“const int **” //**q、*p代表同一块内存 *q、p代表同一块内存。**q被const修饰无法修改,即a的值不能修改, //但*q没有被任何修饰可以改变,即可通过*q修改p保存的地址从而修改a的值,两个互相矛盾,故错误。 //正确的写法: //两处修改,改一处即可:const int *p = &a; 或 const int *const *q = &p; */ /* int b = 10; int *p1 = &b; const int *&q1 = p1; //这一句代码和上面一样是错误的,因为引用和指针可相互转换,即const int **q1 = &p1; //正确的写法: //两处修改,改一处即可:const int *p1 = &b; 或 const int *const &q1 = p1; */ int a = 10; int *p = &a; int *const *q = &p;//这句是正确的,是因为const修饰了*q保证了q保存的地址,即p的地址无法被修改, //但可以修改**q,即a的值可以被修改,也可以通过*p来修改a的值。互相满足,故正确。 */ /* return 0; } //const 和指针结合时我们关注的是离const最近的类型,对于const左边的东西我们可以不用分析, //重点在于分析const右边有没有 * 或 & ,如果有再对 * 或 & 结合的类型进行分析,针对类型做出相应判断。
文章来源: const用法详解