const详解

来源:互联网 发布:java写入word文件 编辑:程序博客网 时间:2024/06/04 18:29

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)

对该部分内容的理解,需要涉及到形参和实参的区别,以及函数的几种传值方式的理解。详见我的另外相关内容的文章辅助理解。

0 0