c++中的const是一个很强大的助手,它允许你指定一个语义约束,表明某一个值不应该被更改,编译器会帮你强制执行这个约束。
const修饰指针
const可以修饰变量,数组,指针,函数,对象等等,几乎可以修饰任何东西,其中比较晦涩的应该是指针了,看下面这段代码
char arr[] = "hello world";
char* p = arr;
const char* p = arr; //const修饰arr
char* const p = arr; // const修饰p
const char* const p = arr;//既修饰arr又修饰p
const在*号左边修饰的是指针所指向的值,在*号右边修饰的是指针本身,当然你可以二者结合使用
请注意:以const在*号的左右来辨别其修饰哪一部分,因为有些程序员喜欢将const放在类型之后,*号之前来修饰指针所指向的值,如下两种写法本质是一样的
const char* p;
char const* p;
谈到指针,就不得不谈STL中的迭代器了,使用迭代器就像使用指针一样,但在const层面上,它和指针还是有一些细微差别的
当我们声明一个iterator是const时,其实是表明迭代器本身是const,约束它不能指向别的东西,相当于T* const,而如果想要约束其指向的值,那么你应该使用const_iterator
const std::vector<int>::iterator it;//实际上是修饰it
const修饰函数
const在面对函数声明时,能发挥出很大的威力
const可以修饰函数的返回值
const T& GetValue(int pos);//这个函数返回一个数组对应下标的值的引用
当你确定函数的返回值并不需要被更改时,就加上const,这样做能够有效避免一些令人困扰的问题
上述函数如果我们不加const,用户就可能对数组下标进行无理的更改,这种操作应该被避免,当然也可以不反回引用,这样的话用户得到的是一份拷贝,也就无法进行更改(由于临时变量的常量性,也可能直接给用户报错)。
const可以修饰函数的参数
const修饰参数,可以防止我们编写代码时候由于忘记而去更改不该更改的值,所以在必要的时候使用它
const修饰成员函数
const函数能够告诉我们,哪一个函数能够改动对象内容,哪一个不能。更重要的是,const成员函数提供了一种能够操作const 对象的方法。
在c++类和对象中,const对象是只能调用const 成员函数而不饿能调用非const成员函数的,这是因为const对象将其this指针设置成了const属性,而非const成员函数的函数原型是这样的:
class test
{
...
public:
char& operator[](test& this/*非const成员函数接收非const指针*/,\
std::size_t pos);
};
非const成员函数,接收的是非const this指针,而const对象调用非const成员函数传入的是const指针,权限放大,当然不行。
因此const对象只能调用const成员函数,而非cosnt对象既能调用const又能调用非const, 但是非const对象优先调用非const函数,如果没有非const函数才会去调用const函数。
更重要的是,仅是常量性不同的两个函数是可以构成重载的,所以可以实现const对象和非const对象调用同一函数却返回不同结果。
class test
{
std::string _text;
public:
test(std::string text)
:_text(text)
{}
const char& operator[](std::size_t pos) const
{
std::cout << "I am const" << std::endl;
return _text[pos];
}
char& operator[](std::size_t pos)
{
std::cout << "I am not const" << std::endl;
return _text[pos];
}
};
int main()
{
const test t1("yoxi");
test t2("hello");
t1.operator[](0);
t2[1];
return 0;
}
//结果
I am const
I am not const
bitwise constness 和 logical constness
bitwise constness要求:const成员函数必须保证不能更改对象内的任何一个bit,编译器就是以这种观点进行检测的,所以只要const成员函数中有对成员变量的赋值动作就会立即报错。这种观点为编译器提供了便利,但是有些时候,尽管成员函数不那么const,编译器仍然会让其通过,比如说下面这段代码
class test
{
char* _text;
public:
test(char* text)
:_text(text)
{}
char& operator[](std::size_t pos) const
{
return _text[pos];
}
};
这段代码,返回的是对象内部一个数组的引用,因此在外部用户可以随意更改对象内部的值,那我们能说这个const函数是完全const的吗?它确确实实可能导致成员变量的更改,只不过是间接的,这明显和bitwise constness不合。
这也引出了logical constness,这一观点认为,const成员函数可以修改它所处理的对象内部的一些bits。但是编译器只认bitwise constness,因此要使用mutable声明变量以表明他们即使在const成员函数中也能被更改。
#include <iostream>
#include <string>
class test
{
mutable int a = 1;
public:
void changeValue() const
{
std::cout << "修改前的 a:" << a << std::endl;
a = 2;
std::cout << "修改后的 a:" << a << std::endl;
}
};
int main()
{
test t;
t.changeValue();
return 0;
}
//运行结果
修改前的 a:1
修改后的 a:2
避免non-const和const的重复
前面说过,可以利用non-const和const的重载来完成对const和non-const对象的不同返回结果,但是如果两个函数的完成的工作有较多重复,就可以考虑使用non-const调用const来减少代码重复。比如说一个time类,对于const和non-const对象都需要返回且仅返回年,月,日等信息,那么就可以考虑写出以下代码
#include <iostream>
#include <vector>
class Time
{
int _year = 1;
int _month = 1;
int _day = 1;
public:
void getTime() const
{
std::cout << "I am const" << std::endl;
std::cout << "年: " << _year << std::endl;
std::cout << "月: " << _month << std::endl;
std::cout << "日: " << _day << std::endl;
}
void getTime()
{
//对this指针作类型转换,以便传入
static_cast<const Time&>(*this).getTime();
}
};
int main()
{
const Time t1;
Time t2;
t1.getTime();
t2.getTime();
return 0;
}
//运行结果
I am const
年: 1
月: 1
日: 1
I am const
年: 1
月: 1
日: 1
通过const_cast将const函数的this指针去const化,也能实现const调用non-const,但是最好不要那样做,因为const本身就是约束不能更改对象内部的值,而非const则是自由自在的,因此用non-const调用const是安全的,而用const调用non-const就无法保证其安全性了,也失去了const本身的意义