转:const基础
如果const关键字不涉及到指针,我们很好理解,下面是涉及到指针的情况:
int b = 500;
const int* a = &b; [1]
int const *a = &b; [2]
int* const a = &b; [3]
const int* const a = &b; [4]
如果你能区分出上述四种情况,那么,恭喜你,你已经迈出了可喜的一步。不知道,也没关系,我们可以参考《Effective c++》Item21上的做法,如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。因此,[1]和[2]的情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种情况下不允许对内容进行更改操作,如不能*a = 3 ;[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;[4]为指针本身和指向的内容均为常量。
另外const 的一些强大的功能在于它在函数声明中的应用。在一个函数声明中,const 可以修饰函数的返回值,或某个参数;对于成员函数,还可以修饰是整个函数。有如下几种情况,以下会逐渐的说明用法:
A& operator=(const A& a);
void fun0(const A* a );
void fun1( ) const; // fun1( ) 为类成员函数
const A fun2( );
二 const的初始化
先看一下const变量初始化的情况
1) 非指针const常量初始化的情况:
A b;
const A a = b;
2) 指针(引用)const常量初始化的情况:
A* d = new A();
const A* c = d;
或者:const A* c = new A();
引用:
A f;
const A& e = f; // 这样作e只能访问声明为const的函数,而不能访问一般的成员函数;
[思考1]: 以下的这种赋值方法正确吗?
const A* c=new A();
A* e = c;
[思考2]: 以下的这种赋值方法正确吗?
A* const c = new A();
A* b = c;
三 作为参数和返回值的const修饰符
其实,不论是参数还是返回值,道理都是一样的,参数传入时候和函数返回的时候,初始化const变量
1 修饰参数的const,如 void fun0(const A* a ); void fun1(const A& a);
调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const A* a,则不能对传递进来的指针的内容进行改变,保护了原指针所指向的内容;如形参为const A& a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。
[注意]:参数const通常用于参数为指针或引用的情况;
2 修饰返回值的const,如const A fun2( ); const A* fun3( );
这样声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
返回值用const修饰可以防止允许这样的操作发生:
Rational a,b;
Radional c;
(a*b) = c;
一般用const修饰返回值为对象本身(非引用和指针)的情况多用于二目操作符重载函数并产生新对象的时候。
[总结] 一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。
原因如下:
如果返回值为某个对象为const(const A test = A 实例)或某个对象的引用为const(const A& test = A实例) ,则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。
[思考3]: 这样定义赋值操作符重载函数可以吗?
const A& operator=(const A& a);
四 类成员函数中const的使用
一般放在函数体后,形如:void fun() const;
如果一个成员函数的不会修改数据成员,那么最好将其声明为const,因为const成员函数中不允许对数据成员进行修改,如果修改,编译器将报错,这大大提高了程序的健壮性。
五 使用const的一些建议
1 要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
2 要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
3 在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
4 const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
5 不要轻易的将函数的返回值类型定为const;
6除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;
本人水平有限,欢迎批评指正,可以联系 kangjd@epri.ac.cn
[思考题答案]
1 这种方法不正确,因为声明指针的目的是为了对其指向的内容进行改变,而声明的指针e指向的是一个常量,所以不正确;
2 这种方法正确,因为声明指针所指向的内容可变;
3 这种做法不正确;
在const A::operator=(const A& a)中,参数列表中的const的用法正确,而当这样连续赋值的时侯,问题就出现了:
A a,b,c:
(a=b)=c;
因为a.operator=(b)的返回值是对a的const引用,不能再将c赋值给const常量。
接着转:
转贴 ::VC知识库在线杂志
[ 本文属于第22期, 适合中级读者 ]
const传奇
原作:Rahul Singh 翻译:zhigang
[译者注]有些地方按原文解释不通,译者根据自己的理解作了适当修改。如有不妥之处,请告知coolgrass@sina.com或参考原文。
原文来自www.codeproject.com
简介
当我自己写程序需要用到const的时候,或者是读别人的代码碰到const的时候,我常常会停下来想一会儿。许多程序员从来不用const,理由是即使没用const他们也这么过来了。本文仅对const的用法稍作探讨,希望能够对提高软件的源代码质量有所帮助。
常变量
变量用const修饰,其值不得被改变。任何改变此变量的代码都会产生编译错误。Const加在数据类型前后均可。
例如
void main(void)
{
const int i = 10; //i,j都用作常变量
int const j = 20;
i = 15; //错误,常变量不能改变
j = 25; //错误,常变量不能改变
}
常指针
Const跟指针一起使用的时候有两种方法。
const可用来限制指针不可变。也就是说指针指向的内存地址不可变,但可以随意改变该地址指向的内存的内容。
void main(void)
{
char* const str = "Hello, World"; //常指针,指向字符串
*str = ''M''; //可以改变字符串内容
str = "Bye, World"; //错误,如能改变常指针指向的内存地址
}
const也可用来限制指针指向的内存不可变,但指针指向的内存地址可变。void main(void)
{
const char* str = "Hello, World"; //指针,指向字符串常量
*str = ''M''; //错误,不能改变字符串内容
str = "Bye, World"; //修改指针使其指向另一个字符串
*str = ''M''; //错误,仍不能改变字符串内容
}
看完上面的两个例子,是不是糊涂了?告诉你一个诀窍,在第一个例子中,const用来修饰指针str,str不可变(也就是指向字符的常指针);第二个例子中,const用来修饰char*,字符串char*不可变(也就是指向字符串常量的指针)。
这两种方式可以组合起来使用,使指针和内存内容都不可变。
void main(void)
{
const char* const str = "Hello, World"; //指向字符串常量的常指针
*str = ''M''; //错误,不能改变字符串内容
str = "Bye, World"; //错误,不能改变指针指向的地址
}
Const和引用
引用实际上就是变量的别名,这里有几条规则:
声明变量时必须初始化
一经初始化,引用不能在指向其它变量。
任何对引用的改变都将改变原变量。
引用和变量本身指向同一内存地址。
下面的例子演示了以上的规则:
void main(void)
{
int i = 10; //i和j是int型变量
int j = 20;
int &r = i; //r 是变量i的引用
int &s; //错误,声明引用时必须初始化
i = 15; //i 和 r 都等于15
i++; //i 和 r都等于16
r = 18; //i 和r 都等于18
printf("Address of i=%u, Address of r=%u",&i,&r); //内存地址相同
r = j; //i 和 r都等于20,但r不是j的引用
r++; //i 和 r 都等于21, j 仍等于20
}
用const修饰引用,使应用不可修改,但这并不耽误引用反映任何对变量的修改。Const加在数据类型前后均可。
例如:void main(void)
{
int i = 10;
int j = 100;
const int &r = i;
int const &s = j;
r = 20; //错,不能改变内容
s = 50; //错,不能改变内容
i = 15; // i和r 都等于15
j = 25; // j和s 都等于25
}
Const和成员函数
声明成员函数时,末尾加const修饰,表示在成员函数内不得改变该对象的任何数据。这种模式常被用来表示对象数据只读的访问模式。例如: class MyClass
{
char *str ="Hello, World";
MyClass()
{
//void constructor
}
~MyClass()
{
//destructor
}
char ValueAt(int pos) const //const method is an accessor method
{
if(pos >= 12)
return 0;
*str = ''M''; //错误,不得修改该对象
return str[pos]; //return the value at position pos
}
}
Const和重载
重载函数的时候也可以使用const,考虑下面的代码: class MyClass
{
char *str ="Hello, World";
MyClass()
{
//void constructor
}
~MyClass()
{
//destructor
}
char ValueAt(int pos) const //const method is an accessor method
{
if(pos >= 12)
return 0;
return str[pos]; //return the value at position pos
}
char& ValueAt(int pos) //通过返回引用设置内存内容
{
if(pos >= 12)
return NULL;
return str[pos];
}
}
在上面的例子中,ValueAt是被重载的。Const实际上是函数参数的一部分,在第一个成员函数中它限制这个函数不能改变对象的数据,而第二个则没有。这个例子只是用来说明const可以用来重载函数,没有什么实用意义。
实际上我们需要一个新版本的GetValue。如果GetValue被用在operator=的右边,它就会充当一个变量;如果GetValue被用作一元操作符,那么返回的引用可以被修改。这种用法常用来重载操作符。String类的operator[]是个很好的例子。(这一段译得很烂,原文如下:In reality due to the beauty of references just the second definition of GetValue is actually required. If the GetValue method is used on the the right side of an = operator then it will act as an accessor, while if it is used as an l-value (left hand side value) then the returned reference will be modified and the method will be used as setter. This is frequently done when overloading operators. The [] operator in String classes is a good example.)
class MyClass
{
char *str ="Hello, World";
MyClass()
{
//void constructor
}
~MyClass()
{
//destructor
}
char& operator[](int pos) //通过返回引用可用来更改内存内容
{
if(pos >= 12)
return NULL;
return str[pos];
}
}
void main(void)
{
MyClass m;
char ch = m[0]; //ch 等于 ''H''
m[0] = ''M''; //m的成员str变成:Mello, World
}
Const的担心
C/C++中,数据传递给函数的方式默认的是值传递,也就是说当参数传递给函数时会产生一个该参数的拷贝,这样该函数内任何对该参数的改变都不会扩展到此函数以外。每次调用该函数都会产生一个拷贝,效率不高,尤其是函数调用的次数很高的时候。
例如:
class MyClass
{
public:
int x;
char ValueAt(int pos) const //const method is an accessor method
{
if(pos >= 12)
return 0;
return str[pos]; //return the value at position pos
}
MyClass()
{
//void constructor
}
~MyClass()
{
//destructor
}
MyFunc(int y) //值传递
{
y = 20;
x = y; //x 和 y 都等于 20.
}
}
void main(void)
{
MyClass m;
int z = 10;
m.MyFunc(z);
printf("z=%d, MyClass.x=%d",z,m.x); //z 不变, x 等于20.
}
通过上面的例子可以看出,z没有发生变化,因为MyFunc()操作的是z的拷贝。为了提高效率,我们可以在传递参数的时候,不采用值传递的方式,而采用引用传递。这样传递给函数的是该参数的引用,而不再是该参数的拷贝。然而问题是如果在函数内部改变了参数,这种改变会扩展到函数的外部,有可能会导致错误。在参数前加const修饰保证该参数在函数内部不会被改变。 class MyClass
{
public:
int x;
MyClass()
{
//void constructor
}
~MyClass()
{
//destructor
}
int MyFunc(const int& y) //引用传递, 没有任何拷贝
{
y =20; //错误,不能修改常变量
x = y
}
}
void main(void)
{
MyClass m;
int z = 10;
m.MyFunc(z);
printf("z=%d, MyClass.x=%d",z,m.x); //z不变, x等于10.
}
如此,const通过这种简单安全机制使你写不出那种说不定是什么时候就会掉过头来咬你一口的代码。你应该尽可能的使用const引用,通过声明你的函数参数为常变量(任何可能的地方)或者定义那种const method,你就可以非常有效确立这样一种概念:本成员函数不会改变任何函数参数,或者不会改变任何该对象的数据。别的程序员在使用你提供的成员函数的时候,不会担心他们的数据被改得一塌糊涂。

浙公网安备 33010602011771号