概述
const关键字的基本思想就是将一个变量变成常量,试图从语言设计者的角度去分析引入该关键字的动机。
通常的理解,const修饰的变量是不可修改的。显然并不是,const关键字在某种程度上来说只是一个面向编译器和程序员的幌子,并不能保证被修饰的内容的不变性。
const的部分用法:
-
const修饰局部变量,即声明一个局部常量
g++编辑器
#include <iostream>
using namespace std;
int main()
{
const int a = 3;
int *b = (int *)&a;
*b = 5;
cout << a << " , " << *b << endl;
return 0;
}
运行结果:
3 , 5
片面的理解,const确实很负责的保证了被修饰内容的不变性,在一定程度上使得con_var1变成了一个常量。但是,并没有实现保证con_var1在内存中的值不被修改。
gcc编辑器
int main()
{
const int a1 = 3;
int *b1 = (int *)&a1;
*b1 = 5;
printf("a = %d, *b1 = %dn", a, *b1);
return 0;
}
运行结果:
a1 = 5, *b1 = 5
可以看到const属性的值a被改变了。
从gcc和g++两种编辑器产生的结果可以看出,g++入栈时是直接使用立即数3入栈,而并不是从a所在内存读取出值来入栈的。而gcc则不同。
因此,有如下结论:
- 对于const修饰的局部常量而言,g++编译器(不同编译器机制未必相同)会为其分配内存,由于是静态分配而且是局部常量,因此分配在栈中。
- 对代码中直接引用到该常量的情况,编译器会将其直接替换成定义时赋的值。这样一来,无论运行期该常量所在内存被如何修改,都不会影响常量的使用。对于取址操作&ar而言,&a整体被当作一个右值,没有被编译器替换成常值,这也是为什么既然编译器还要为const修饰的常量分配内存的原因。
- const只在编译期间保证常量被使用时的不变性,无法保证运行期间的行为。程序员直接修改常量会得到一个编译错误,但是使用间接指针修改内存,只要符合语法则不会得到任何错误和警告。因为编译器无法得知你是有意还是无意的修改,但是既然定义成const,那么程序员就不应当修改它,不然直接使用变量定义好了。
- c语言中原本没有const关键字,后来为了兼容,引入了const,但是编译器对其的操作与c++中不同。
-
const修饰 常量指针或指针常量
int main()
{
int a = 3;
const int *p = &a;
cout << a << ", " << *p << endl;
a = 5;
cout << a << ", " << *p << endl;
return 0;
}
运行结果:
3, 3
5, 5
上面代码定义了一个指向常量的指针,即被指向的地址中址是不能通过*ptr_cvalue来修改的。
上面结果与const实现这个功能并不冲突,应为const只保证修饰的指针不能被用来修改其指向的内存的值。但是其他手段来修改该内存的值就与const无关了。
这说明,const机制在运行期没有任何行为,只是编译期的行为。
- const修饰类成员变量
class A
{
public:
A(int i=1,int j=2):a(i),b(j) {};
void fun();
private:
const int a;
const int b;
};
void A::fun()
{
cout << "&a" << &a << "a=" << a << endl;
cout << "&b" << &b << "b=" << b << endl;
int *a1 = (int*)&(a);
*a1 = 5;
int *b1 = (int*)&(b);
*b1 = 6;
cout<<&a<<"="<<a<<endl<<a1<<"="<<*a1<<endl;
cout<<&b<<"="<<b<<endl<<b1<<"="<<*b1<<endl;
}
int main()
{
A a;
a.fun();
cout << &a << endl;
return 0;
}
结果:
&a = 0xbfd955ac a =1
&b = 0xbfd955b0 b =2
0xbfd955ac = 5
0xbfd955ac = 5
0xbfd955b0 = 6
0xbfd955b0 = 6
0xbfd955ac
类const成员被修改了。
-
函数const传址也是可以被修改的。
-
const修饰的全局常量
const int a = 2;
int main()
{
unsigned int addr;
int *p;
addr = (unsigned int)&a;
p = (int *)addr;
cout << &a << " " << &addr << endl;
//*p = 10; // 段错误
cout << a << endl;
return 0;
}
段错误的原因在于:const int constant,这个全局常量内存分配在.rodata段,对于内存分页机制来说,
.rodata对应的页是只读的,当试图去修改该段内存时,会引发段错误,这是在运行期的错误。
可以说,这是真正能够保证不会被修改的常量,是在操作系统层面实现的。
const关键字是用于约束程序员和编译器的行为的,而不是程序员用以保证程序正确性的良药,除了全局常量外,没有任何机制保证了const声明的常量的不可修改性,要想使得程序中定义的常量不被修改,就只能循规蹈矩的使用它。
const 与 #define
- 编译器处理方式不同。
define宏是在预处理阶段展开。
const常量是编译运行阶段使用。
- 类型和安全检查不同
define宏没有类型,不做任何类型检查,仅仅是展开。
const常量有具体的类型,在编译阶段会执行类型检查。
- 存储方式不同
define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
const常量会在内存中分配(可以是堆中也可以是栈中)。
- const 可以节省空间,避免不必要的内存分配。
- 提高了效率。
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
-
const 与 #define的比较
C++ 语言可以用const来定义常量,也可以用 #define来定义常量。但是前者比后者有更多的优点:
(1) const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。
( 2 ) 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
在C++ 程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。
最后
以上就是默默咖啡为你收集整理的const常量的实现机制以及与宏定义区别const 与 #define的全部内容,希望文章能够帮你解决const常量的实现机制以及与宏定义区别const 与 #define所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复