const是一个关键字,用来限定所修饰的内容不可以再被更新。
1 const与指针:
第一种:int const *p 是指指针指向常量,指针所指向的内容不可以变,指针本身的值可以变,也就是指针的指向可以变。可以看到 int const (*p),*p就是p指向的内容。
含义理解:仓库管理员可以去任何一个仓库,但是不可以动仓库里面的东西。
int const *p 与 const int *p 相同,只读不能写与修改
举例理解:
int b=100;
const int *p=&b;
*p=50;错误
const int *p;正确,可以不初始化
p++;正确,可以访问其他的当然可以通过修改b达到修改内容的目的。偷梁换柱,虽然p这个管理员不可以动仓库里的东西,但是我们可以把仓库里的东西给换掉
int b=100;
const int *p=&b;
b=40;
cout<<*p<<enl//40
第二种:Int * const p 是指指针本身是一个常量指针,指针本身的值不可以变,也就是指针的指向不可变,但是指针指向的内容可以变。不能访问别的地方,访问自己的地方可读可写。
含义理解:仓库管理员只能去特定的仓库,但是可以动里面的东西。
举例分析:
int b=100,c=50;
int* const p;错误,未初始化
int* const p=&b;正确,初始化了
*p=34;正确,可以修改内容
p++;错误,不可以访问别的地方
规则,根据const跟*的位置,const在*的左边定内容,const在*的右边定指向:
左定值,右定向。
第三种:结合,const int * const p 就是*左右两边都有const,那么内容和指向都是常量不可以改变。不能访问别的地方,自己的地方也是只能读不能写。
含义理解:仓库管理员只能去特定的仓库,并且还不能动仓库里面的东西。
const int * const p 和int const *const p一样的。
在涉及到指针时一定要注意初始化也就是说明指针的指向,防止出现野指针。对于第二种和第三种情况,也就是常指针的情况,定义的同时必须进行初始化。
左定值,指针指向常量的情况下,注意还试图修改指针指向的内容的错误。在常指针的情况下注意,未初始化错误以及试图改变指针指向的错误,比如p++。
2 const放在类型前还是后没有区别,只修饰其后的变量。
比如const int a和int const a是一样的。
再比如const int *a 和int const *a 是一样的。
3 const与成员函数
在类中有一些函数为只读函数,不会修改成员数据,将这样的只读函数用const来修饰,可以提高程序的可读性和可靠性。
举例:
class A
{
int x,y;
public:
int gety() const;
};
int A::gety() const
{
return y;
}
注意到在函数声明和实现时都要在后面加上const关键字。
另外注意const加在函数前面,修饰的是函数的返回值,指函数的返回值是不可以被修改的,所以要注意const的不同位置。
const修饰的成员函数,不能够修改对象的成员变量,也不能调用类中的非const成员函数。
4 const与类对象,对象指针,对象引用
const 修饰类对象,该对象是常量,不可以调用非const成员函数
举例:
class A
{
int x,y;
public:
int gety() const;
int func();
};
int A::gety() const
{
return y;
}
const A a;
a.func();错误,const修饰类对象不可以调用非const成员函数
a.gety();正确
const A* a=new A();
a->func();错误
a->gety(); 正确
5 const与成员变量
成员变量不可以被修改,只可以在初始化列表中被赋初值。
class A
{
const int a;
A(int x):a(x){};
}
6 常量折叠与复写传播
常量折叠就是在编译器进行语法分析的时候,将常量表达式计算求值,求得的值来替换表达式,放入常量表,可以算作一种编译优化,如下面:
cons t int i=2*2;编译器会把2*2这个常量表达式的值计算出来为4,并用4来代替之前那个表达式,就是常量折叠。
在编译阶段以后在每次碰到i就会用4来代替,这个就叫做复写传播。
7 符号表const_cast
cons t int i =4;就是上面提到的常量折叠时会放在一个常量表中,然后编译阶段每次用到i就用4来替换(复写传播),就是符号表的概念,i就对应着4。
一个典型的例子分析:
int main(int argc, char* argv[])
{
const int i=0;
int *j = (int *) &i;
*j=1;
cout<<&i<<endl;
cout<<j<<endl;
cout<<i<<endl;
cout<<*j<<endl;
return 0;
}
结果是
0012ff7c
0012ff7c
0
1
在此处j就是指向i处的地址,但是内存中输出的内容却不同,这里只是const起到了作用,让i还是一个不被修改的常量,其实就是符号表的对应,遇到i就用0替换,只是在编译的阶段,实际运行时i地址处的内存单元的内容已经变为1了。
const int a = 3;
int *p = const_cast<int *>(&a);
*p = 4;
cout << a;//仍然输出3
8 使用const变量来初始化数组时,c语言的编译器会报错。
const int i=10;
Int a[i];
因为c语言中和c++中的const是有区别的,c语言中的const修饰的常量是要分配内存的,而且编译器不可以把其看做一个常量在编译过程中。在c中const是外部链接的,c++中是内部链接的。所以对以下情况:
const bufsize;
在c中是正确的,因为bufsize在别的地方分配了内存,但是在c++中是不正确的,要想在c++中正确,必须要使用extern来变成外部链接。
extern const bufsize;
c中数组定义时长度必须为常量,常量是5,abc之类的。c中常量通常被编译器放在内存的只读数据区域,而用const修饰的是只读变量,是在内存中单独开辟一块区域来存放,有编译器规定其值不可以被修改,但是它仍不能等同于常量。因为c中会const常量分配内存,在编译的时不能用常量替换,所以在编译的时候编译器并不知道i是多少。
但是在c++中,是可以的。在c++中const常量是用符号表访问的,在编译时用常量来替换,在编译过程中,遇到i都会用立即数$10去替换,也就是相当于常量的。
所以在c中使用const的意义不大,一般使用define更好一些。而c++中使用const意义重大。
9 const与宏定义define的区别:
1) 编译器处理的方式不同
define是在预处理阶段展开
const是在编译运行阶段使用这个常量
2) 类型和安全检查不同
define没有类型,所以不做任何类型和安全检查,仅仅是展开
const常量具有类型,在编译阶段会执行类型和安全检查
3) 存储方式不同
const定义常量,从汇编的角度看,给出的是内存地址,在程序执行过程中仅有一份拷贝
define给出的是立即数,那里用到就展开,有多份拷贝,所以const可以节省内存分配:
#define PI 3.14 宏定义
const double pi=3.14 此时并为将pi放入ram中。。。
double I=pi 此时给pi分配内存,以后不再分配
double J=PI 编译期间进行宏替换,进行内存分配
doublei=pi 不进行内存分配,编译期间,查找符号表(存放const常量的),编译时直接进行替换。牵扯到常量折叠的概念。
doublej=PI 再次进行宏替换,再次分配内存。
4) 集成化的调试工具可以对const进行调试,而不可以对宏常量进行调试
10 const的其他作用
5) 使用const可以防止所修饰的变量会被意外修改,提高程序的健壮性
6) 编译器通常不为普通的const常量分配存储空间,而将他们保存在符号表中,使其成为编译期间的一个常量,没有了对内存的读写操作,提高了效率。只有当你非要获取其修饰的比如变量i的地址时,编译器才会为i分配一个内存地址。const int i=10,也就是编译器优化以后放在符号表中,以后遇到i就用10来替换。
7) const是一个内部链接,所以const仅在被const定义过的文件里才可见,在链接时不能被其他编译单元可见,所以定义一个const时必须要附一个初值给他,否则除非用extern做了说明。extern是强制使用外部链接,必须分配内存单元,为了不同的编译可以引用它必须存储起来。
11 const修饰函数参数
const修饰函数参数是它最广泛的一种用途,它表示函数体中不能修改参数的值(包括参数本身的值或者参数其中包含的值)
函数传值:
void function(const int Var); //在此处添加const是为了才、传递过来的参数在函数内不可以被改变,但是在函数定义时的参数是形参,形参实际是函数调用时实参的一种拷贝,实参存在于主调函数中,如果你接触了解汇编语言,就会明白,函数调用时会通过栈实现,将主调函数中实参的值通过数据传递传给被调函数的形参,形参是在被调用时开辟的栈,调用结束会被释放,所以对形参的任何操作都不会影响到实参。所以在此处加const是没有意义的,有一点多此一举。其实如果想修改实参的值唯一的途径就是传入地址,那么也就是借助传指针或者传引用的方式。
函数传指针:函数传指针就是传入了实参的地址,则可以达到通过修改形参进而修改实参的目的,如果这种情况下想要使实参不被改变,那就是要使用一个指向常量的指针,那么就是要在左添加一个const。
void function(const char* Var); //参数指针所指内容为常量不可变
void function(char* const Var); //这种情况是右定向,是指针本身是一个常量,指向不可变,但是指向的内容可以变,也就是可以通过修改形参来改变实参的值,这种添加const不可以达到保护的目的。
对于非内部数据类型的参数而言,
比如传递累的对象:可以通过传递引用的方式通过使用别名,来减少临时对象的构造,复制,析构等时间消耗。
void function(A a) 这样声明的函数注定效率比较底。因为函数体内将产生A 类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。
为了提高效率,可以将函数声明改为void function(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数void function(A &a) 存在一个缺点:
“引用传递”有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可 void function(const A &a)
对该部分内容的理解,需要涉及到形参和实参的区别,以及函数的几种传值方式的理解。详见我的另外相关内容的文章辅助理解。