以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 C/C++编程思想 』  (http://bbs.xml.org.cn/list.asp?boardid=61)
----  const传奇  (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=39213)


--  作者:卷积内核
--  发布时间:10/23/2006 3:32:00 PM

--  const传奇
简介

当我自己写程序需要用到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可用来限制指针不可变。也就是说指针指向的内存地址不可变,但可以随意改变该地址指向的内存的内容。

int main(void)

{

       int i = 10;

       int *const j = &i;   //常指针, 指向int型变量

       (*j)++;                //可以改变变量的内容

       j++;                    //错误,不能改变常指针指向的内存地址

}

const也可用来限制指针指向的内存不可变,但指针指向的内存地址可变。

int main(void)

{

       int i = 20;

       const int *j = &i;   //指针,指向int型常量

       //也可以写成int const *j = &i;

       j++;                      //指针指向的内存地址可变

       (*j)++;                  //错误,不能改变内存内容

}

看完上面的两个例子,是不是糊涂了?告诉你一个诀窍,在第一个例子中,const用来修饰指针j,j不可变(也就是指向int变量的常指针);第二个例子中,const用来修饰*j,*j不可变(也就是指向int常量的指针)。

这两种方式可以组合起来使用,使指针和内存内容都不可变。

int main(void)

{

       int i = 10;

       const int *const j = &i;  //指向int常量的常指针

       j++;                   //错误,不能改变指针指向的地址

       (*j)++;          //错误,不能改变常量的值

}

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


--  作者:卷积内核
--  发布时间:10/24/2006 5:02:00 PM

--  
const char * const foo(char const * const str) const;

const一般用来表示常量,在你写的程序中不希望被改变的值就要明确声明为常量类
型,这样你试图改变常量的值的
时候就会报错,常量可以这样写
const float pi=3.1415926;
也可以这样写
float const pi=3.1415926;
他们的含义是一样的。因此如果这样写:
const float const pi=3.1415926;
就会说重复定义。duplicate `const'
而对于指针const在指针的左边或右边有不同的含义,const在指针左边表示所指的东西
是常量,例如:
const char *str="foo";
或者这么写char const *str="foo";
都是表示foo是不能改变的,因此你要对const的变量做操作,例如:
strcpy(str,"ABC");
注意strcpy的原形是char *strcpy(char *dest, const char *src);
型参一使用的是char *不是常量指针,因此对常量做修改的时候就会报错,类似这样:
cannot convert `const char *' to `char *' for argument `1' to strcpy (char
*, const char *)'
当然,这只是编译时的操作,对于流氓程序员仍旧可以改变常量的值,例如
strcpy((char *)str,"ABC");
或者char *strTmp=str;strcpy(strTmp,"ABC");
但是不管怎么样,把const写上,便于发现减少程序员犯低级错误,例如strcpy把目标
和源搞反了。
现在在来说说const在*右边的情况,这种情况表示指针不允许改变,例如:
char * const str="foo";
str="XIXI"; //haha 这样被我限制了,不可以的。
这时编译会出现,assignment of read-only variable `str',看连指针本身都不能改
变了。
现在大家再看我以前提的问题:
const char * const foo(char const * const str) const;
其中第一个const表示返回的是个const,不能改变的值,因此你不能这么用
strcpy(foo("xixi"),"abc");
第二个const表示指针不能改变,理论上是不能这样用,foo("xixi")="oo";但是函数的
返回值本身就是readonly的
因此,实际这样写也是不成立的,这个const可以省略。第3个表示 str是个常量,注意
我在这里故意放在char的后面
就是为了和函数返回值放的位置区分,实际放在哪里都是一样的,第4个const表示str
指针不能指别的东西。
最后一个const是C++中出现的,只能用在方法中,表示这个方法不改变对象中的任何
值。
在这里的所有const都是为了有效的检查是否把某些不该改变的东西给改变了,但是记
住:任何方法都不能防止真正的流氓程序员,
这只是让你们尽量在编译的时候解决程序可能出现的问题。


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
125.000ms