const用法详解 const指针 const函数 指向const对象的指针

CONST关键字用法

--“Use const whenever you need”

一、常量:

通过const关键字将一个变量定义为常量。

const int bufsize=1024;

如果在程序中试图修改bufsize的值,则会引起一个错误。由于const类型的量一经定义就不能改变它的值,因此在定义时必须初始化。

const double PI; //这条语句将产生错误,没有初始化。

1.限定符声明变量只能被读

const int i=5;

int j=0;

……

i=j;  //非法,导致编译错误

j=i;  //合法

2.必须初始化

const int i=5; //合法

const int j;   //非法,导致编译错误

3.在另一连接文件中引用const常量

extern const int i; //合法

extern const int j=10;  //非法,常量不可以被再次赋值

4.便于进行类型检查

用const方法可以使编译器对处理内容有更多了解。

#define I=10

const long &i=10;  /*由于编译器的优化,使得在const long i=10; 时i不被分配内存,而是已10直接代入以后的引用中,以致在以后的代码中没有错误,为达到说教效果,特别地用&i明确地给出了i的内存分配。不过一旦你关闭所有优化措施,即使const long i=10;也会引起后面的编译错误。*/

char h=I;   //没有错

char h=i;   //编译警告,可能由于数的截短带来错误赋值。

5.可以避免不必要的内存分配

#define STRING "abcdefghijklmnn"

const char string[]="abcdefghijklmn";

……

printf(STRING);  //为STRING分配了第一次内存

printf(string);  //为string一次分配了内存,以后不再分配

……

printf(STRING);  //为STRING分配了第二次内存

printf(string);   //不分配内存

……

由于const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

 

 6.可以通过函数对常量进行初始化

int value();

const int i=value();

假定对ROM编写程序时,由于目标代码的不可改写,本语句将会无效,不过可以变通一下:

const int &i=value();

只要令i的地址处于ROM之外,即可实现:i通过函数初始化,而其值有不会被修改。

7.是不是const的常量值一定不可以被修改呢?

观察以下一段代码:

const int i=0;

int *p=(int*)&i;

p=100;

通过强制类型转换,将地址赋给变量,再作修改即可以改变const常量值。

8.请分清数值常量和指针常量,以下声明颇为玩味:

int ii=0;

const int i=0;     //i是常量,i的值不会被修改

const int *p1i=&i;   //指针p1i所指内容是常量,可以不初始化

int * const p2i=ⅈ //指针p2i是常量,所指内容可修改

const int * const p3i=&i;//指针p3i是常量,所指内容也是常量

p1i=ⅈ          //合法

*p2i=100;         //合法

 

被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

 

二、指向const类型对象的指针

const int *p;

p是一个指向int类型对象的指针,但p本身并不是一个常量。也就是说p可以指向任何一个int类型的对象,但由p所指向的对象不可以通过p来改变值。

const char *p = "hello"; 或者 char const * p = "hello"; 

// 非const指针,const数据,就是说p指向的那个内存空间的数据是不可变的,但p还可以指向新的内存地址。(问:如果指向另外的地址,原来指向的const对象还在?)

 

把一个const对象的地址赋给一个不是指向const对象的指针也是不行的。

const int b=10;

int *p2=&b;   //error

const int *p3=&b; //ok

结论:因为变量b有const修饰,不能被修改。但指针p2是一个普通的指针,可以修改指向对象的值,两种声明矛盾,所以不合法。而指向const对象的指针不允许修改指针指向对象的数值,所以这种方式合法。

 

三、对象常量

<类名> const <对象名> 或者 const <类名> <对象名>

定义常对象时,同样要进行初始化,并且该对象不能再被更新。

 

四、常指针const指针

a)<类型> * const <对象> 表示定义一个常量指针,对象本身不能改变,但所指向的值是可以改变的。

char * const p = "hello";

// const指针,非const数据,就是说这个指针p一旦赋值或初始化,就不能在指向其他位置了,但其指向的位置的数据值是可变的。(指向的地址不变,必须初始化)

 

b)const <类型> * <对象> 表示定义一个常量的指针或指针常量,该变量的本身可以改变,但所指向的值是不能改变的。

(指向const类型对象指针)

c)const <类型> * const <对象>

const char * const p = "hello"; 

// const指针,const数据,这个就很明显了,集上述两家之长处(也可能是短处哦,),上述两者都不可变,必须初始化。

 

五、常引用

const <类型> &<对象>

使用const修饰符也可以说明引用,被说明的引用为常引用,该引用所引用的对象不能被更新。如:

const double &v;//v是一个引用

v=12.3//非法,不能更新

 

六、CONST用于函数

使用const关键字进行说明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象。没使用const说明的成员函数不能用来操作常对象。

常成员函数说明格式如下:

<返回类型说明符> <函数名> (<参数列表>) const;

其中,const是加在函数说明后的类型修饰符,它是函数类型的一个组成部分,因此在函数实现部分也要带关键字const。

 

1.const 修饰函数的参数

如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const 修饰,否则该参数将失去输出功能。const 只能修饰输入参数:一般用在采用指针或者引用的参数传递,因为采用“值传递”进的参数是原来变量的备份,不需要限定

如果输入参数采用“指针传递”,那么加const 修饰可以防止意外地改动该指针,起到保护作用。

例如StringCopy 函数:

void StringCopy(char *strDestination, const char *strSource);

其中strSource 是输入参数,strDestination 是输出参数。给strSource 加上const修饰后,如果函数体内的语句试图改动strSource 的内容,编译器将指出错误。

 

“引用传递”void Func(A &a)有可能改变参数a,加const修饰即可限制其发生改变,因此函数最终成为void Func(const A &a)。

 const &修饰输入参数的用法总结:

 

对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。

 

对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。

 

2.const 修饰函数的返回值

如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。例如函数

const char * GetString(void);

如下语句将出现编译错误:

char *str = GetString();

正确的用法是

const char *str = GetString();

如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。

例如不要把函数int GetInt(void) 写成const int GetInt(void)。

同理不要把函数A GetA(void) 写成const A GetA(void),其中A 为用户自定义的数据类型。

如果返回值不是内部数据类型,将函数A GetA(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则是非法的。

3.const 成员函数(仅用于类的成员函数)

任何不会修改数据成员的函数都应该声明为const 类型。如果在编写const 成员函数时,不慎修改了数据成员,或者调用了其它非const 成员函数,编译器将指出错误,这无疑会提高程序的健壮性。以下程序中,类stack的成员函数GetCount 仅用于计数,从逻辑上讲GetCount应当为const函数。编译器将指出GetCount 函数中的错误。

class Stack

{

public:

void Push(int elem);

int Pop(void);

int GetCount(void) const; // const 成员函数

private:

int m_num;

int m_data[100];

};

int Stack::GetCount(void) const

{

++ m_num; // 编译错误,企图修改数据成员m_num

Pop(); // 编译错误,企图调用非const 函数

return m_num;

}

const 成员函数的声明看起来怪怪的:const 关键字只能放在函数声明的尾部,大概是因为其它地方都已经被占用了。

 

关于Const函数的几点规则:

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

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

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

e. 然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的

七、const的用法总结起来又分为以下两种:

(1) 在定义变量时使用:

 a: const int a=100; 最简单的用法,说明变量a是一个常变量;

 b: int const b=100; 与a功能相同;

 c: const int *a=&b; 指向常数的指针,即指针本身的值是可以改变的,但指向的内容是不能改变的;

 d: int const *a=&b; 与c功能相同;

 e: int * const a = &b; 常指针,即指针本身的值是不可改变的,但指向的内容是可改变的;

 f: const int * const a = &b;指向常数的常指针,即指针本身与指向的内容都是不可改变的;

 g: const int &a=100; 常数引用,即不能改变引用的值;

 

 总结: 在使用const定义变量时,一定要进行初始化操作,在操作符(*,&)左边的修饰的是指向的内容,在右边的是本身。

 

(2) 在函数用使用:

 a: void func(const int a); 做为参数使用,说明函数体内是不能修改该参数的;对不同参数定义时不同的形式,可参见定义变量时使用方式;

 b: const int func(); 做为返回值使用,说明函数的返回值是不能被修改的,在取得返回值时应用const int a = func();对不同参数定义时不同的形式可,参见定义变量时使用方式;

 c: int func() const; 常函数,说明函数是不能修改类中成员的值的,只能用于类的成员函数中;

八、常量对象的动态创建

既然编译器可以动态初始化常量,就自然可以动态创建,例如:

const int* pi=new const int(10);

这里要注意2点:

1)const对象必须被初始化!所以(10)是不能够少的。

2)new返回的指针必须是const类型的。

那么我们可不可以动态创建一个数组呢?

答案是否定的,因为new内置类型的数组,不能被初始化。

九、const有什么主要的作用?

(1) 可以定义const常量,具有不可变性。

例如:

const int Max=100;

int Array[Max];

(2) 便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。

例如:

void f(const int i) { .........}

编译器就会知道i是一个常量,不允许修改;

(3) 可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。

同宏定义一样,可以做到不变则已,一变都变!如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!

(4) 可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。

还是上面的例子,如果在函数体内修改了i,编译器就会报错;

例如:

void f(const int i) { i=10;//error! }

(5) 为函数重载提供了一个参考。

class A

{

......

void f(int i) {......} file://一个函数

void f(int i) const {......} file://上一个函数的重载

......

};

(6) 可以节省空间,避免不必要的内存分配。

例如:

#define PI 3.14159 file://常量宏

const doulbe Pi=3.14159;file://此时并未将Pi放入ROM中

......

double i=Pi; file://此时为Pi分配内存,以后不再分配!

double I=PI; file://编译期间进行宏替换,分配内存

double j=Pi; file://没有内存分配

double J=PI; file://再进行宏替换,又一次分配内存!

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

(7) 提高了效率。

 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

 

参考链接:

http://blog.chinaunix.net/uid-21411227-id-1826729.html

http://www.cnblogs.com/zhanglanyun/archive/2012/08/12/2634619.html

http://www.cppblog.com/heisehuoyan/articles/9735.html

http://www.cnblogs.com/Fancyboy2004/archive/2008/12/23/1360810.html

 

posted @ 2012-10-18 18:29  天的那边  阅读(882)  评论(0编辑  收藏  举报