本文共 4233 字,大约阅读时间需要 14 分钟。
转载原文地址:
const修饰的全局变量存储在常量区,修饰的局部变量一般放在符号表中,当这个值可能发生改变的时候,在栈中重新开辟出一块内存空间,所以如果不加volitale关键字,那么可能去这个符号表中拿到原始数据,而不是栈中拿到的新数据。
const定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态区),而#define定义的宏常量在内存中有若干个拷贝。(所以说,const定义的全局变量也就是在编译初期,常量区存放这个变量,内存中只有一份拷贝,但是如果是define,那么每次用到这个变量的时候,内存中都会进行一次拷贝,相对来说,比较浪费空间,这也是使用const代替define的好处)。 #define宏是在预编译阶段进行替换,而const修饰的只读变量是在编译的时候确定其值。 #define宏没有类型,而const修饰的只读变量具有特定的类型。
const int *p; //p可变,p指向的对象不可变
int const*p; //p可变,p指向的对象不可变
int *const p; //p不可变,p指向的对象可变
const int *const p; //指针p和p指向的对象都不可变
总的来说: const:有数据类型,编译进行安全检查,可调试 define:宏,不考虑数据类型,没有安检,不能调试
1. const全局变量(非成员变量)
首先是没有const修饰的全局变量:
在文件a.cpp中定义了一个全局变量a
int a = 1;
在文件test.cpp中使用全局变量a
#include <iostream>
using namespace std; extern int a; int main() { //const volatile int a = 7; int *p = (int *)(&a); *p = 8; cout << "a=" << a << endl; cout << "*p=" << *p; system("pause"); return 0; }结果为:
如果将全局变量a定义为const
const int a = 1;
#include <iostream>
using namespace std; extern const int a; int main() { //const volatile int a = 7; int *p = (int *)(&a); *p = 8; cout << "a=" << a << endl; cout << "*p=" << *p; system("pause"); return 0; }这里可以看出const在修饰全局变量时第一个作用,会限定全局变量的作用范围到其定义时所在的编译单元。
const全局变量使得我们指定了一个语义约束,即被修饰的全局变量不允许被修改,而编译器会强制实施这个约束。
原则上,const修饰的全局变量是不被允许改变的。
#include <iostream>
using namespace std; const int a = 7; int main() { //const volatile int a = 7; int *p = (int *)(&a); *p = 8; cout << "a=" << a << endl; cout << "*p=" << *p; system("pause"); return 0; }运行这段代码,会发现编译器报异常。编译器不允许对const全局变量的改动。
2.const局部变量
对于const局部变量,有个有趣的地方:
#include <iostream>
using namespace std; int main() { //const volatile int a = 7; const int a = 7; int *p = (int *)(&a); *p = 8; cout << "a=" << a << endl; cout << "*p=" << *p; system("pause"); return 0; }运行结果显示const局部变量被修改了,但是在使用变量名输出时,编译器会出现一种类似宏定义的功能一样的行为,将变量名替换为初始值。可见,const局部变量并不能做到真正的不变,而是编译器对其进行了一些优化行为,这导致了const局部变量与真实值产生了不一致。那么,如果想获取修改后的const局部变量真实值,该怎么办呢?答案是使用volatile关键字。
但是这样不能并不能保证const修饰的变量不能被改变。只是保证变量的同步。
#include <iostream>
using namespace std; int main() { const volatile int a = 7; //const int a = 7; int *p = (int *)(&a); *p = 8; cout << "a=" << a << endl; cout << "*p=" << *p; system("pause"); return 0; }volatile关键字使得程序每次直接去内存中读取变量值而不是读寄存器值,这个作用在解决一些不是程序而是由于别的原因修改了变量值时非常有用。
cosnt修饰指针const修饰指针,涉及到两个很重要的概念,顶层const和底层cosnt
指针自身是一个对象,它的值为一个整数,表明指向对象的内存地址。因此指针长度所指向对象类型无关,在32位系统下为4字节,64位系统下为8字节。进而,指针本身是否是常量以及所指向的对象是否是常量就是两个独立的问题。
顶层const(top-level const): 指针本身是个常量
底层const(low-level const): 指针指向对象是一个常量
int a = 1;
int b = 2; const int* p1 = &a; int* const p2 = &a;根据从内到外,由近到远读符号的规则
p1依次解读为:p1是个指针(*),指向一个int型对象(int),该对象是个常量(const)。 因此这是一个底层cosnt
p2依次解读为:p2是个常量(const),p2是个指针(*),指向一个int对象(int)。 因此这是一个顶层const
const修饰函数参数const修饰参数是为了防止函数体内可能会修改参数原始对象。因此,有三种情况可讨论:
函数参数为值传递:值传递(pass-by-value)是传递一份参数的拷贝给函数,因此不论函数体代码如何运行,也只会修改拷贝而无法修改原始对象,这种情况不需要将参数声明为const。
函数参数为指针:指针传递(pass-by-pointer)只会进行浅拷贝,拷贝一份指针给函数,而不会拷贝一份原始对象。因此,给指针参数加上顶层const可以防止指针指向被篡改,加上底层const可以防止指向对象被篡改。 函数参数为引用:引用传递(pass-by-reference)有一个很重要的作用,由于引用就是对象的一个别名,因此不需要拷贝对象,减小了开销。这同时也导致可以通过修改引用直接修改原始对象(毕竟引用和原始对象其实是同一个东西),因此,大多数时候,推荐函数参数设置为pass-by-reference-to-const。给引用加上底层const,既可以减小拷贝开销,又可以防止修改底层所引用的对象。const修饰函数返回值
令函数返回一个常量,可以有效防止因用户错误造成的意外。
比如
if (a*b = c)
如果a,b,c都是如同int的内置类型,编译器会直接报错
因为对于内置类型的*操作返回的不是一个左值,因此不能放在=的左边。为什么会出现这种情况呢?可能用户只是想比较是否相等,却打字打漏了一个等号(ORZ)。因此,对于很多自定义类型的函数,应该尽量与内置类型兼容,在应该返回右值的函数返回那里应该加上const。
if (a*b == c)
const成员函数
将const实施与成员函数上,只要是防止成员函数修改类对象的内容。良好的类接口设计应该确保如果一个成员函数功能上不需要修改对象的内容,该成员函数应该加上const修饰。
const_reference operator[]( size_type pos ) const;
上图是STL string的成员函数,可以看出,在函数返回值,函数参数,函数是否可以修改类对象三个地方都做出了const限定。
如果const成员函数想修改成员变量值,可以用mutable修饰目标成员变量。
如果一个类对象为const 对象,语义上说明该对象的值不可改变,因此该const对象只能调用const成员函数,因为非const成员函数不能保证不修改对象值,编译器会禁止这种会造成隐患的行为。
如果是const修饰引用:
const int & b1 = a1;
常量引用,通过b1修改a1的值是不被允许的,比如:b1=2是不允许的,但是a1的值可以改变,当a1的值发生变化的时候,b1的值也会发生变化。
int & const b3 = a3;提示了“qualifiers on reference are ignored”,即对引用的限定符被忽略,所以相当于int & b3 = a3; const没有起作用。
总结:首先要知道定义引用时就要进行初始化,而且引用不能改变指向。
然后const在&之前表明引用为常引用,不能通过该引用修改值。const在&之后,const不起效果。
因为本身引用就不可以改变它的指向,所以加上这个const没有意义。
const修饰对象:
const对象顾名思义为常对象,所有对象的成员变量不可变。