C++的const关键字

来源:互联网 发布:java 方法签名 throw 编辑:程序博客网 时间:2024/05/01 13:18

C++const关键字

整理:Ackarlix

[挨踢网]http://www.aitic.net

 

想小结一下const的种种用法和相关知识

1,const修饰数据

见文:const指针和引用

2,const修饰成员函数

表示该成员函数为常量对象调用,首先来看C++的对象和绑定的成员函数是如何实现的。

class A
{
public:
    void Test(int _a)
    {
        a = _a;
    }
private:
    int a;
};

A::Test(int)A的成员函数,一个函数是一段可执行代码,它和对象的数据是两回事,它只需要一份拷贝,相当于它们是静态的不变的(static),而非static数据成员是可以有多份拷贝,它们是动态的可变的,它们之间如何关联起来?通过this指针,在成员函数内对成员变量调用时会省略this指针,上面的语句相当于‘this->a = _a’this指针是如何传进成员函数的?this指针如何传进去这不是程序员关心的,有的编译器通过函数第一个参数,比如上面的A::Test(int)实际上是A::Test(A* this,int);函数参数的压栈顺序是反的,从汇编的角度看,就是通过系统栈传递this指针,当然,有的还会通过寄存器,如何传进去的不用担心,结果是它已经传进去了。所以可以这样理解下面的C++代码:

A a;
a.Test(100);

理解为:

A a;
A::Test(&a,100);

好了,这样理解是最好的了,我们来看const成员函数,如果上面代码中a是常量,看看有什么问题:

const A a;
A::Test(&a,100);

由于Test()的第一个参数是A*&a的结果是const A*,不能将一个指向常量的指针赋给一个一般指针,编译错误。所以简单在Test定义式中尾部加一个const

    void Test(int _a) const
    {
        a = _a;
    }

就行了,它相当于第一个this参数的类型换成了const A*,即A::Test(const A* this,int);而一个A*指针是可以赋给const A*的。那这样的话,它和没有const修饰的成员函数可以重载了,对,这两种形式是可以重载。

好继续考虑,如果这样的话,const修饰的成员函数有什么不同?

this是一个指向常量的指针,所以this->将得到一些常量类型的成员,即在const修饰的成员函数内,是不能修改成员变量的。(外话,如果你硬要修改也是可以的,可以作const_cast转型,MSDN中有例子;或者由关键字mutable改变)

好了,我把由const修饰的成员函数的作用讲清楚了,两点:1,常量对象调用;2,不能修改成员变量。

设计的时候,是不是不管三七二十都上一个const和非const成员函数?对于一个接口,最小化后的接口集,从一个方面考虑,如果允许常量对象,你会把哪些接口对它开放,将开放的函数先改成const版,看能否全全工作,如果不能再重载一个非const版本。(1)如果你设计的类不允许有常量对象(思考,如何阻止不能产生常量?),所有接口都非const2)允许有常量对象,对常量性操作开放const成员函数。

3,conststatic

const如果和static一起修饰会是什么后果?先看看static吧。它指静态的,和常量的是近义词,但是在程序中的实际意义相差蛮远。一份数据,首先有一个名字,但是同样一个名字,可能有多份数据,而且在不同时刻,数据所在存储区也是可变的,比如一般函数内的局部变量:

int foo(int x)
{
    int a;
    a = x*x;
    return a;
}

这里的a就是可变的,它在运行期只是一个偏移量,从foo()调用的时候,系统栈的栈顶到变量a的偏移量。它随着调用的时刻不同,将在栈中不同的位置出现,甚至在一个递归的函数中,它可以同时在栈中出现多份数据。但是它只有一个名字a,它是动态的。相对这种动态,用static修饰的变量,将只有一份拷贝,即它的名字只指一份数据,在一个程序的一次运行中。但是这个唯一的静态数据,不一定是常量,它可以是变量。const约束一份数据不能被修改,static表明一份数据是独一无二的(static修饰成员函数时表示它们是和对象不相关的,没有this指针,修饰成员数据时表示这份数据只存在一个拷贝,不存在于不同的对象当中)。在类内部使用静态常量,可以像enum或者#define一样用,但是如果你非要取它们的地址,那就必须有它们的定义式,否则将引发连接错误,比如在一个类内部定义了静态常量,可以不在类外部做静态变量定义,而直接使用它们,就像真常量一样。这样说可能比较难理解,看例子吧:

class A
{
public:
    static const int c = 100;
};

void foo(int const& x)
{
}

...
foo(A::c); //
引用传递需要取地址,连接错误
foo(0+A::c);// ok
...

相比于真常量,比如enum#define,它们只能是右值,即不能取地址,不能修改,甚至是运行期不存在的,而静态常量有同样的效果,如果你不给出它的定义式,如果取了地址就会有连接错误,不取地址就跟真常量一样,是编译期常量,可用于元编程中。一般const修饰只是约束了一般变量,这种约束只在编译期,为了让程序员不要犯错误,要善于利用const,让编译器查出尽可能多的错误;类内静态常量可以只有带初始化的申明式,利用它可以代替真常量。