const传奇(The Story of Const)

Posted on 2006-04-21 23:02 Harry 阅读(483) 评论(0)  编辑 收藏 引用 所属分类: C++转载

Introduction
For a long time I would have to stop and think before I put the const keyword in my code or when I read it someone else's code. A lot of programmers just don't worry about the const keyword and their justification is that they have gotten along just fine so far without it. With the aim of emphasizing improved software and code quality, this article is supposed to be a one place stop for the story of const. Beginners will hopefully find it an easy introduction to the different ways of using const and experienced programers can use the examples here as a quick reference.

Small Disclaimer/Caution
All the code presented below in the examples has been tested (if at all) very casually and so please analyse it carefully before using it. The pointer arithmetic esp. can be a cause for serious grief and care should be taken when writing production code.

const variables
When a variable is prefixed with the key word const its value cannot be changed. Any change will result in a compiler error. It makes no difference if the const keyword appears before the data type or after it.

Here is an example:

 

int  main( void )
{
    
const   int  i  =   10 ;   // both variables i and j are constants
     int   const  j  =   20 ;
    i 
=   15 ;             // Error, cannot modify const object
    j  =   25 ;             // Error, cannot modify const object
}

const pointers
The const keyword can be used with a pointer in two ways.

const can be used to specify that the pointer is constant i.e. the memory address that the pointer points to cannot be changed. The current value at that memory address however can be changed at whim.

int  main( void

    
int  i  =   10 ;
    
int   * const  j  =   & i;   // j is a constant pointer to int
    ( * j) ++ ;      // The int value pointed to by j can be modified.
                
//  i.e. *j = i = 11;
    j ++ ;         // ERROR: cannot modify const object j
}

 

const can also be used with a pointer to specify that the value at the memory address cannot be changed but the pointer can be assigned to a new address.

int  main( void )
{
    
int  i  =   20 ;
    
const   int   * =   & i;   // j is a pointer to a constant int
     * j ++ ;      // the pointer value of j can be incremented. 
              
// Note that the value at pointer location j 
              
// may be garbage or cause a GPF 
              
// but as far as the const keyword is concerned 
              
// the compiler doesnt care
    ( * j) ++ ;    // ERROR; cannot modify const object *j
}

 

A good way to look at the two examples above is that in the first case j is a constant (constant pointer to int) while in the second case the *j is constant (pointer to a constant int).

The two can be combined so that neither the pointer nor its value can be changed.

 

int  main( void )
{
    
int  i  =   10 ;
    
const   int   * const  j  =   & i;  // j is a constant pointer to constant int
    j ++ ;                 // ERROR: cannot modify const object j
    ( * j) ++ ;           // ERROR: cannot modify const object *j    
}


const and references
A reference is merely an alias for an entity. Here are some rules:

A reference must be initialised
Once initialized, a reference cannot be made to refer to another entity.
Any change to the reference causes a change to the original entity and vice-versa.
A reference and an entity both point to the same memory location.
Here is an example to illustrate the rules above:

 

int  main( void )

    
int  i  =   10 ;      // i and j are int variables 
     int  j  =   20 ;    
    
int   & =  i;     // r is a reference to i 
     int   & s;         // error, reference must be initialized 
    i  =   15 ;          // i and r are now both equal to 15. 
    i ++ ;          // i and r are now both equal to 16 
    r  =   18 ;         // i and r are now both equal to 18 
    r  =  j;         // i and r are now both equal to 20, 
               
// the value of j. However r does not refer to j.
    r ++ ;         // i and r are now both equal to 21, 
                
// but j is still 20 since r does not refer to j
}


Using const with a reference ensures that the reference cannot be modified. Any change however to the entity that the reference refers to will be reflected by a change in the reference. It also makes no difference whether the const keyword appears before or after the data type specifier.

Here is an example:

 

int  main( void )
{
    
int  i  =   10 ;
    
int  j  =   100 ;
    
const   int   & =  i;
    
int   const   & =  j;
    r 
=   20 ;           // error, cannot modify const object
    s  =   50 ;           // error, cannot modify const object
    i  =   15 ;           // both i and r are now 15
    j  =   25 ;           // both j and s are now 25
}

const with member functions
When the const keyword is appended to a member function declaration, then the object pointed to by the this pointer cannot be modified. This is frequently used when defining accessor methods that are only used to read the value of the object data. This is useful because developers using your code can look a the declaration and be certain that your accessor method won't modify the object in any way.

Here is an example:

 

class  MyClass 

public :
    
int  i;     // member variable
    MyClass()
    
{
        
// void constructor    
        i  =   10 ;
    }

    
    
~ MyClass()
    
{
        
// destructor
    }


    
int  ValueOfI()  const      // const method is an accessor method
     {
        i
++ ;         // error, cannot modify object
         return  i;     // return the value of i
    }

}
;


Overloading with const
The const keyword can also be used with function overloading. Consider the following code:

 

class  MyClass
{
public :
    
int  i;     // member variable
    MyClass()
    
{
        
// void constructor    
        i  =   10 ;
    }

    
    
~ MyClass()
    
{
        
// destructor
    }


    
int  ValueOfI()  const   // const method is an accessor method
     {
        
return  i;         // return the value of i.    
    }

    
    
int &  ValueOfI()       // method is used to set the 
                         
// value by returning a reference
     {
        
return  i;
    }

}
;

In the above example the two methods ValueOfI() are actually overloaded. The const keyword is actually part of the parameter list and specifies that the this pointer in the first declaration is constant while the second is not. This is a contrived example to emphasize the point that const can be used for function overloading.

In reality due to the beauty of references just the second definition of ValueOfI() is actually required. If the ValueOfI() 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.

 

class  MyClass 

public :
    CPoint MyPoint; 
    MyClass() 
    
{
        
// void    constructor 
        MyPoint.x  =   0
        MyPoint.y 
=   0 ;
    }

    
~ MyClass()
    

        
// destructor
    }

    
    MyPoint
&  MyPointIs()     // method is used to set the value 
                            
// by returning a reference 
    
        
return  MyPoint;

    }

}
;

int  main( void )
{
    MyClass m;
    
int  x1  =  m.MyPointIs().x;         // x1 = 0
     int  y1  =  m.MyPointIs().y;         // y1 = 0
    m.MyPointIs()  =  CPoint( 20 , 20 );    // m.MyPoint.x = 20, m.MyPoint.y = 20
     int  x2  =  m.MyPointIs().x;         // x2 = 20
     int  y2  =  m.MyPointIs().y;         // y2 = 20
    CPoint newPoint  =  m.MyPointIs();     // newPoint = (20,20)
}


As seen in the main function above the same MyPointIs() method is being used as an accessor where it is used to set the values of x1 & y1 and also as a setter when it is used as an l-value. This works because the myPointIs() method returns a reference that gets modified by the value on the right hand side (CPoint(20,20)).
Why should I worry about this const gubbins?
Data is passed into functions by value by default in C/C++. This means that when an argument is passed to a function a copy is made. Thus any change to the parameter within the function will not affect the parameter outside the function. The downside is that everytime the function is called a copy must be made and this can be inefficient. This is especially true if the function is called within a loop that executes (say) a 1000 times.

Here is an example:

 

class  MyClass
{
public :
    CPoint MyPoint;
    MyClass()
    
{
        
// void constructor
        MyPoint.x  =   0 ;
        MyPoint.y 
=   0 ;
    }

    
~ MyClass()
    
{
        
// destructor
    }


    SetPoint(CPoint point)    
// passing by value 
     {
        MyPoint.x 
=  point.x;
        MyPoint.y 
=  point.y;

        point.x 
=   100 ;         // Modifying the parameter has 
                              
// no effect outside the method
        point.y  =   101 ;    
    }

}
;

int  main( void )
{
    MyClass m;
    CPoint newPoint(
15 , 15 );
    m.SetPoint(newPoint);   
// m.Mypoint is (15,15) and so is newPoint.
}

As seen 
in  the above example the value of newPoint remains unchanged because the SetPoint() method operates on a copy of newPoint. To improve efficieny we can pass parameters by reference rather than by value. In  this   case  a reference to the parameter  is  passed to the function and no copy  is  needed to be made. However now the problem  is  that  if  the parameter  is  modified  in  the method  as  above then the variable outside the method will also change leading to possible bugs. Prefixing  const  to the parameter ensures that the parameter cannot be modified from within the method.

class  MyClass
{
public :
    CPoint MyPoint;
    MyClass()
    
{
        
// void constructor
        MyPoint.x  =   0 ;
        MyPoint.y 
=   0 ;
    }

    
~ MyClass()
    
{
        
// destructor
    }


    SetPoint(
const  CPoint &  point)    // passing by reference
     {
        MyPoint.x 
=  point.x;
        MyPoint.y 
=  point.y;

        point.x 
=   100 ;         // error, cannot modify const object
        point.y  =   101 ;         // error, cannot modify const object

    }

}
;

int  main( void )
{
    MyClass m;
    CPoint newPoint(
15 , 15 );
    m.SetPoint(newPoint);        
// m.Mypoint is (15,15) and so is newPoint.
}


In this case const is used a safety mechanism that prohibits you from writing code that could come back and bite you. You should try to use const references far as possible. By declaring you method arguments as const (where appropriate), or declare const methods you are ineffect making a contract that the method will never change the value of an argument or never modify the object data. Thus other programmers can be sure that the method you provide won't clobber the data that they pass to it.

 

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   & =  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   & =  i;
    
int   const   & =  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,你就可以非常有效确立这样一种概念:本成员函数不会改变任何函数参数,或者不会改变任何该对象的数据。别的程序员在使用你提供的成员函数的时候,不会担心他们的数据被改得一塌糊涂。


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


posts - 6, comments - 3, trackbacks - 0, articles - 1

Copyright © Harry