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