C++中的const
C++ 中的 const 是一项语义约束,它允许你告诉编译器和其他程序员某个值应该保持不变。需要注意的是,常量在定义之后就不能修改,因此定义时必须初始化。
const 关键字介绍
const 在 C++ 中主要用来修饰内置类型变量, 指针,函数参数, 自定义对象,成员函数,返回值。 C++ 中的 const 默认是内部链接,只在当前文件中可见,不能用于外部文件(这和 C 相反),如果要在外部文件中使用,应该加上 extern 关键字。
const 修饰变量
const 修饰变量,以下两种定义形式在本质上是一样的。它的含义是: const 修饰的类型为 TYPE 的变量 value 是不可变的。
TYPE const ValueName = value;
const TYPE ValueName = value;
将 const 改为外部链接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义。关于 C++ 中 const 变量的内存分配,可以参考这里。
extend const int ValueName = value;
const修饰指针
对于指针,你可以将指针本身、指针所指向的对象或者这两者都(或都不)定义为 const。这里比较容易弄混的是,看到一个加了 const 的指针,无法确定指针是 const 还是所指向的对象是 const,但只要记住以下规律,就可以分辨清楚:
char greeting[] = "Hello";
char* p = greeting; // non-const pointer, non-const data
// const出现在*左边,表示被指向的对象是const
const char* p = greeting; // non-const pointer, const data
char const * p = greeting; // non-const pointer, const data
// const出现在*右边,表示指针是const
char* const p = greeting; // const pointer, non-const data
const char* const p = greeting; // const pointer, const data
此外, STL 迭代器是以指针为根据塑模而成,所以 STL 迭代器的作用就像个 T* 指针。声明迭代器为 const 的方式和声明指针为 const 一样(即声明一个 T* const 指针),表示这个迭代器不得指向不同的东西,但它所指向的值是可以改动的。如果希望迭代器所指向的值不可以被改动(即希望 STL 模拟一个 const T* 指针),可以使用 const_iterator:
vector<int> vec;
...
const vector<int>::iterator iter = vec.begin(); // iter的作用像个 T* const
*iter = 10; //没问题,改变iter所指向对象的值
++iter; //错误,iter 是const
vector<int>const_iterator cIter = vec.begin(); // cIter的作用像个const T*
*cIter = 10; //错误,*cIter是const
++cIter; //没问题,改变cIter
const 修饰函数参数
const 修饰函数参数分为以下四种情况:
(1)const 修饰值传递参数
void func(const int a);
一般这种情况不需要 const 修饰,因为函数会自动产生临时变量复制实参值,而且对形参的修改也不会对实参造成影响。
(2)const 修饰指针所指向变量
void func(const int* a){
...
*a = 0; // 错误
int b = 0;
a = &b; // 正确,但是对实参无影响,即不会修改实参所指向的对象
...
}
这种方式比较常用,当函数内部对指针所指向的值进行修改时,编译器会报告错误。 而且 const 指针可以接收非 const 和 const 指针,而非 const 指针只能接收非 const 指针。
(3)const 修饰指针
void func(int* const a){
int *b = 1;
a = b; // 错误,无法对read-only参数赋值
*a = 1; // 正确,可以修改a所指向对象的值
}
这种方式表明指针不可以指向其它对象,但实际上作用不大,因为 *a 是一个形参,即使在函数体内修改了 a 所指向的对象,也不会对实参造成任何影响。
(4)const 修饰引用传递参数
void func(const Type &a);
void func(const class &a);
这样的一个 const 引用传递和最普通的函数按值传递的效果是一模一样的,它禁止对引用的对象的一切修改,唯一不同的是按值传递会先建立一个实参的副本, 然后传递过去,而这里直接传递地址。对于何时使用值传递合适使用引用传递,可以参考这里。
const 修饰自定义对象
在 C++ 中,const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)。主要有两种修饰方式:
// 定义常对象
const class object(params);
class const object(params);
// 定义指向常对象的指针
const class *p = new class(params);
class const *p = new class(params);
注意,我们如果将对象 a 作为参数传入函数中,传递进来的参数a是实参对象的副本,要调用构造函数来构造这个副本,而且函数结束后要调用析构函数来释放这个副本,在空间和时间上都造成了浪费。所以函数参数为类对象时,推荐用引用。但按引用传递,造成了安全隐患,通过函数参数的引用可以修改实参的内部数据成员,所以用 const 来保护实参。
const 修饰成员函数
const 修饰成员函数时,放到函数体的行尾处,表明在该函数体内,不能修改对象的数据成员,且不能调用非 const 成员函数。比如:
void SetAge(int age)
void SetAgeConst(int age) const
两者的区别在于:前者可以修改类的数据成员,而后者不可以。为什么不能调用非 const 函数?因为非 const 函数可能修改数据成员, const 成员函数是不能修改数据成员的,所以在 const 成员函数内只能调用 const 函数。
const 修饰函数返回值
用 const 修饰返回的指针或引用,保护指针或引用的内容不被修改。比如:
#include <iostream>
using namespace std;
class Student {
public:
int& GetAge() {
return m_age;
}
// 值得注意的时,两个GetAge的常量性(constness)不同,也可以被重载
const int& GetAgeConst() {
return m_age;
}
void ShowAge() {
cout << "Age: " << m_age << endl;
}
private:
int m_age = 0;
};
int main()
{
Student stu;
stu.ShowAge();
stu.GetAge() = 5; // 会修改成员变量的值
stu.ShowAge();
stu.GetAgeConst() = 8; // 编译器会报错
stu.ShowAge();
return 0;
}
两者的区别在于:前者返回的是一个左值,其引用的内容可以被修改;后者返回的是一个右值,其引用的内容不可被修改。因此,当函数返回值是一个引用时,如果不允许其作为左值,就加上 const 。
一般来说,一个 const 成员函数是不可用修改成员变量的值的,但是如果一定要在其中修改,那么可以使用 mutable 关键字。
class C {
public:
void funcA() const;
void funcB();
private:
mutable int a;
int b;
};
void C::funcA() const{
a = 1; // 可以
b = 1; // 不可以
}
void C::funcB(){
a = 1; // 可以
b = 1; // 可以
}
注意到,如果一个对象是 const 对象,那么它只能调用类的 const 函数,或者 const 成员变量。因此,大部分情况下我们面临着重写两次代码的困境,此时我们可以将常量性移除。
class C{
public:
const int& operator[](int a) const{
...
return data[a];
}
int & operator[](int a){
...
return const_cast<int&>(static_cast<const C&>(*this)[a]);
}
private:
int* data;
}
在这里,我们让非 const 成员函数调用 const 成员函数,使用了两次转型操作。第一次将非 const 对象转换为 const 对象;第二次使用 const_cast 移除 const 。
小结
-
将某些东西声明为 const 可以帮助编译器检测出错误用法。 const 可以被施加在任何作用域内的对象、函数参数、返回值、成员函数体。
-
编译器强制实施 bitwise constness (即成员函数不能修改任何成员变量),但编写程序时应该使用“概念上的常量性”。
-
当 const 和 non-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可以避免代码重复。
参考
[1] Effective C++ 中文版,第三版 / (美)梅耶(Meyers, S.)著;侯捷译。北京:电子工业出版社,2006.7.
[2] C++ const对象(常对象)
[4] C++ const 关键字小结
本文来自博客园,作者:greatestchen},转载请注明原文链接:https://www.cnblogs.com/greatestchen/articles/16943972.html