const除了可以声明常量,还有很多其他用途。用好const对于提高程序的健壮性具有很重要的作用,本文列举了一些const的常见用法,参考了C++ FAQs Lite。
1.用const修饰函数参数
用const修饰函数参数可以防止参数在函数体内被意外修改。
void f1(const std::string& s); // const引用传递
void f2(const std::string* sptr); // const指针传递
void f3(std::string s); // 值传递
对于前两种,如果在函数体内修改参数的内容,将会在编译时报错。
对于最后一种,函数体内部将会产生一个对象副本,可以对该副本进行各种修改,但是离开函数体后该副本自动销毁,所做的修改不会影响到传递进来的参数。
2.如果函数的参数不是const型,则不能直接传递进来一个const型的参数,必须先生成一个非const型的局部副本,然后将该副本作为参数传递给函数。
void g1(std::string& s);
void f1(const std::string& s)
{
g1(s); // 错误
std::string localCopy = s;
g1(localCopy); // 正确
}
3.const int*, int const*, int* const, const int* const, int const* const之间的区别
最好的区别方法就是将变量声明从后往前念,正如注释后的英文所示:
int x = 10;
const int* p1 = &x; //p1 points to a integer that is const
//不能够通过p1来改变x的值,即*p1=5将会产生编译错误
int const* p2 = &x; //p2 points to a const integer
//不能够通过p2来改变x的值,即*p2=5将会产生编译错误,等价于p1
int* const p3 = &x; //p3 is a const pointer to a integer
//不能改变p3的值,即p3++将产生编译错误
const int* const p4 = &x;//p4 is a const pointer to a integer that is const
//不能改变p4的值,即p4++将产生编译错误
//不能够通过p4来改变x的值,即*p4=5将会产生编译错误
int const* const p5 = &x;//p5 is a const pointer to a const integer
//不能改变p5的值,即p5++将产生编译错误
//不能够通过p5来改变x的值,即*p5=5将会产生编译错误,等价于p4
4.const int&, int const&, int& const之间的区别
区别方法和3相同:
int x = 10;
const int& x1 = x; //x1 aliases a integer that is const
//不能通过x1来改变x的值,即x1=5将产生编译错误
int const& x2 = x; //x2 aliases a const integer
//不能通过x2来改变x的值,即x2=5将产生编译错误
int& const x3 = x; //x3 is a const alias to a integer(make no sense)
//由于引用本身就是“const”型的,一旦指定了引用对象就不能更改,
//因此这里的const没有意义,等价于int& x3 = x;
5.const成员函数
如果在成员函数的参数表之后添加一个const,则表示该函数不会执行改变*this对象的操作,即不会修改成员变量的值。
class Fred
{
public:
void inspect() const; // 该函数不会修改*this
void mutate(); // 该函数可能会修改*this
};
void userCode(Fred& changeable, const Fred& unchangeable)
{
changeable.inspect(); // 正确
changeable.mutate(); // 正确
unchangeable.inspect(); // 正确,不会改变*this对象的内容
unchangeable.mutate(); // 编译错误:unchangeable引用了一个const对象,
//不能通过该引用改变const对象的内容
}
6.如果一个const成员函数通过引用返回this对象的一个成员,则应该使用常量引用:
class Person
{
public:
std::string str;
const std::string& name_good() const
{
return str; //正确:虽然函数调用者获得了str的引用,但是由于是const类型,因此无法改变该成员
}
std::string& name_evil() const
{
return str; //编译错误:函数调用者有机会改变str
}
};
例如:
void myCode(const Person& p) //通过const关键字承诺不会修改Person对象
{
p.name_evil() = "Igor"; //但是却可以通过这种方式修改!(因为返回的不是const对象)
}
7.const重载
所谓const重载,指的是两个函数或者运算符的声明完全相同,只是其中一个具有const修饰符
class Fred
{
public:
int x;
void fun() const
{
cout << "const fun" << endl;
}
void fun()
{
cout << "non-const fun" << endl;
}
};
void myCode(Fred &f1, const Fred& f2)
{
f1.fun(); //输出non-const fun
f2.fun(); //输出const fun
}
对于const对象,调用const版本的函数;对于非const对象,调用非const版本的函数
如果只有const版本的函数,则都调用该函数;如果只有非const版本的函数,则对于const对象会产生编译错误
另外,如果要编写一个容器类,则需要重载下标运算符[]。下标运算符通常都必须进行const重载(come in pairs):
class Fred { ... };
class MyFredList {
public:
const Fred& operator[] (unsigned index) const;
Fred& operator[] (unsigned index);
...
};
为什么要提供这两种版本的函数呢?因为在const成员函数中需要调用const版本,而在非const成员函数中需要调用非const版本。
8.利用const成员函数对数据成员做一些外部不可见的修改
const成员函数一般不应该改变数据成员的值,但是有些情况下需要做一些外部不可见的修改,例如Set对象可能需要缓存它上次查找的对象的信息以提高下一次的查找效率。有两种实现方法:
(1)在需要修改的数据成员前用mutable关键字修饰;(VC6.0支持该关键字)
(2)如果编译器不支持该关键字,则需要在const成员函数中对自身进行const转型:
void Set::lookup() const
{
Set *self = const_cast<Set *>(this); //去掉this指针的const特性,这样就可以修改对象数据成员了
...
}
9.不能通过const int*指针改变变量值并不意味着不能改变该变量的值:仅仅是该指针承诺不改变变量值,而不是该变量承诺自身不会发生变化。
void f(const int* p1, int* p2)
{
int i = *p1; // 获得*p1的原始值
*p2 = 7; // 如果p1==p2,这样也会改变*p1的值
int j = *p1; // 获得*p1的新值
if (i != j) {
std::cout << "*p1发生了变化, 但是没有通过p1指针!\n";
assert(p1 == p2); // 这是i!=j的唯一可能
}
}
int main()
{
int x = 5;
f(&x, &x); // 传递同一个指针
return 0;
}
10.不能够将Foo **转换为const Foo**,可以将其转换为const Foo* const*
class Foo { /* ... */ };
void f(const Foo** p);
void g(const Foo* const* p);
int main()
{
Foo** p = /*...*/;
...
f(p); // 编译错误:不能够将Foo **转换为const Foo**
g(p); // 正确:可以将Foo**转换为const Foo* const*
...
}
为什么要做这样的限制呢?看下面的例子就知道了:
class Foo {
public:
void modify(); // 该函数可以对对象进行修改
};
int main()
{
const Foo x;
Foo* p;
const Foo** q = &p; // 假设可以将Foo**转换为const Foo**(事实上这行语句会产生一个编译错误)
*q = &x; // p指向了x
p->modify(); // 使用p修改了x的内容!而x是一个const对象!
...
}