const总结

一、const与#define的比较

C++语言可以用const来定义常量,也可以用#define来定义常量。但是两者有如下区别:

(1)  编译器处理方式不同
define宏是在预处理阶段展开。
const常量是编译运行阶段使用。

(2)  类型和安全检查不同
define宏没有类型,不做任何类型检查,进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误(边际效应);
const常量有具体的类型,在编译器编译阶段会执行静态类型安全检查。

(3)  存储方式不同

define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
const常量会在内存中分配(可以是堆中也可以是栈中)。

(4)  其它

有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

我们也不能得到宏常量的地址(即不能向它传递指针和引用)。

 

二.常变量

变量用const修饰,其值不得被改变。任何改变此变量的代码都会产生编译错误,const在定义的时候需要初始化。Const加在数据类型前后均可。。

1) const变量默认是局部变量,如果需要全局访问,需要显示地extern;

2) const int MAX_SIZE= 1024 与#defineMAX_SIZE1024 貌似做了相同的事情,但是是完全不同的两个同意。

#define作为宏定义是完全文本替换,而const MAX_SIZE是作为一个变量整体的。

下面这段代码可以说得更清楚:

const int MAX_SIZE = 1 <<10; cout << MAX_SIZE << endl; //120

#define MAX_SIZE 1<<10 cout<< MAX_SIZE << endl;   //110

3)const可以修饰常数组,例如:  

int const a[5]={1, 2,3, 4,5};    或 const int a[5]={1, 2, 3, 4, 5};

4)类内部的常量的限制

初始化类内部的常量

1.初始化列表:

class A

{

    public:

           A(int i=0):test(i) {}

    private:

           const int test;

};



        2 外部初始化,例如:

classA

{

 public:

         A() {}

 private:

         static constint i;  

};

const int A::i=3;


二. const对指针的修饰

沿*画竖线,const和什么在一边,什么不可修改

在赋值的的时候需要注意,我们可以将一个非const的对象地址赋给一个const指针,但是不能将一个const对象地址赋给一个非const指针,因为这样可能通过被赋值的指针改变对象的值,打破了const提供的安全性。


三. const对函数的修饰

(1)修饰函数的参数

void fun(constint &r)

表示函数不可修改参数的值.如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const修饰。

(2)函数加const后缀

    void fun()const

此时的函数为自定义结构(struct/class)的成员函数,表示该函数成员不可修改class的成员变量

(3)修饰函数的返回值

constchar * GetString(void);

表示返回的结果不可修改,且返回结果只能赋值给const修饰的变量。

如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。例如对上面的函数,如下语句将出现编译错误:

char*str = GetString();

正确的用法是

constchar *str = GetString();

如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。例如不要把函数int GetInt(void) 写成const int GetInt(void)。同理不要把函数A GetA(void) 写成const A GetA(void),其中A 为用户自定义的数据类型。

如果返回值不是内部数据类型,将函数AGetA(void) 改写为const A & GetA(void)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。

函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。

例如:
class A

{

A & operate = (const A & other);// 赋值函数

} ;

A a, b, c; // a, b, c 为A 的对象

a = b = c; // 正常的链式赋值

(a = b) = c; // 不正常的链式赋值,但合法

如果将赋值函数的返回值加const修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。

当处理用户定义类型的时候,按值返回常量就很有意义了,这时候函数的返回值不能被直接赋值也不能被修改。仅仅是非const返回值能作为一个左值使用,但是这往往失去意义,因为函数返回值在使用时通常保存为一个临时量,临时量被作为左值使用并修改后,编译器将临时量清除。结果丢失了所有的修改。
可以用const限定传递或返回一个地址(即一个指针或一个引用):

const int * const func(const int *p)

{

      static int a=*p;

      return &a;

}

参数内的const限定指针p指向的数据不能被改变,此后p的值被赋给静态变量a,然后将a的地址返回,这里a是一个静态变量,在函数运行结束后,它的生命期并没有结束,所以可以将它的地址返回。(如果是局部变量,则不可将其值或其地址作为返回值)因为函数返回一个const int* 型,所以函数func的返回值不可以赋给一个非指向const的指针 (因为在赋值的的时候需要注意,我们可以将一个非const的对象地址赋给一个const指针,但是不能将一个const对象地址赋给一个非const指针,因为这样可能通过被赋值的指针改变对象的值)

(4) const 成员函数

任何不会修改数据成员(即函数中的变量)的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。const 成员函数的const 关键字只能放在函数声明的尾部。

关于Const函数的几点规则:

a.const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.

b.const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.

c.const成员函数不可以修改对象的数据,不管对象是否具有const性质.它在编译时,以是否修改成员数据为依据,进行检查.

e.当我们运用const成员函数时,遇到需要改变数据成员,可以用mutable进行特别的指定, 加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改:

class X

{

      mutable int i;

public:

      X();

      void nochange() const;

};

void X::nochange const(){i++;}

** mutable

  mutable可以用来指出,即使结构或者类变量为const,其某个成员也可以被修改。例如

struct data

  {

  char name[30];

  mutable int accesses;

  ....

  };

  const data veep = {"david";,0,}

  strcpy(veep.name,"Jimmy");// not allowed

  veep.accesses++; // allowed

  veep的const限定符禁止程序修改veep的成员,但access成员的mutable说明符表示access不受这种限制

 

Const 深度解析

在通常的情况下const同预处理器#define一样只是将所赋值保存入编译器的符号表中(符号表仅仅在编译时存在,在编译过程中编译器将程序中的名字与之在符号表中定义的数值作简单的替换),在使用的时候进行值替换,并不为const创建存储空间。

我们将const的定义放进头文件里,这样通过包含头文件,可以把const定义单独放在一个地方并把它分配给一个编译单元,const默认为内部连接(内部连接意味着只对正在编译的文件创建存储空间,别的文件可以使用相同的标示符和全局变量,编译器不会发现冲突,外部连接意味着为所有被编译过的文件创建一片单独的存储空间,一般全局变量和函数名的外部连接通过extern声明,可以通过其他的文件访问)也就是说const仅能被它所定义过的文件访问,在定义一个const时,必须赋一个值给它,除非用extern做出说明:

extern const int a;

这表示const的定义在其他的什么地方,这里仅仅是一个声明,但是这样的做法使const使用了外部连接,也就是说上面的extern强制进行了对const的存储空间分配,这样我们就无法再用const作为常量折叠(在可能的情况下,符号常量的值会代替改名字的出现,这个替代过程叫做常量折叠)使用了,即使我们在其他地方定义了const的值,如:

extern const int a(=3);

因为const的值被放入了存储单元,在编译的过程中,编译器不会去读存储单元的内容。如果我们这样做:

int b[a];

编译器就会给我们一个错误信息。例如:
const int a = 3;

int b[a]; //不会出错


const int a = 3;

extern const int a;

int b[a]; //编译出错


事实上,想不为const分配存储空间是不可能的,因为对于复杂的结构,例如集合,编译器不会复杂到将集合保存到它的符号表中,所以必须分配内存空间,这就意味着“这是一块不能改变的存储空间”,当然也就不能在编译期间使用它的值,因为编译器不知道存储的内容:

const inti[]={1,2,3,4};

//floatf[i[2]];
//将得到错误信息,编译器提示不能在数组定义里找到一个常数表达式。 

因为编译器靠移动栈指针来存储和读取数据。(编译时必须为数组分配内存,但这时数组无法读取const常量的值,所以编译器不知道为数组f分配多大的空间。)也因此,由于无法避免为const分配内存,所以const的定义必须默认为内部连接,否则由于众多的const在多个文件中分配内存,就会引起错误。下面我们看一段简单有效的代码来说明const的常量折叠:

#include <iostream.h>

const int a=3;

const int b=a+1;

float *f=(float*)&b;

char c[b+3];

void main()

{

    const char gc=cin.get();

    const char c2=gc+3;

}

我们可以看到,a是一个编译器期间的const,b是从a中计算出来的,由于a是一个const,b的计算值来自一个常数表达式,而它自身也是一个编译期间的const,接着下面指针f取得了b的地址,所以迫使编译器给b分配了存储空间,不过即使分配了存储空间,由于编译器已经知道了b的值,所以仍然不妨碍在决定数组c的大小时使用b。

在主函数main()里,标识符gc的值在编译期间是不知道的,这也意味着需要存储空间,但是初始化要在定义点进行,而且一旦初始化,其值就不能改变,我们发现c2是由gc计算出来的,它的作用域与其他类型const的作用域是一样的,这是对#define用法的一种改进。

在c++引进常量的时候,标准c也引入了const,但是在c中const的意思和在c++中有很大不同,在c中const的意思是“一个不能改变的普通变量”,const常量总是被分配存储空间而且它的名字是全局符即const使用外部连接。于是在c中:

const int size=100;

char c[size];

得出一个错误。但是在c中可以这样写:

const int size;

因为c中的const被默认为外部连接,所以这样做是合理的。在c语言中使用限定符const不是很有用,如果希望在常数表达式里(必须在编译期间被求值)使用一个已命名的值,必须使用预处理器#define。
class A

{

   private:

   const int c3 = 7;    // err  const成员只能在定义对象的时候初始化

   static int c4 = 7;         // err,静态数员成员只能在外部初始化

   static const float c5 =7;  // err

   ......

};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值