目录
[1. const的本质:将编译器当作你的朋友去维护一个常量](#1. const的本质:将编译器当作你的朋友去维护一个常量)
[2. 指针的const](#2. 指针的const)
[3. 迭代器的const](#3. 迭代器的const)
[4. 用const减少调用错误](#4. 用const减少调用错误)
[5. const成员函数](#5. const成员函数)
[6. bitewise观点](#6. bitewise观点)
[7. 修正:mutable](#7. 修正:mutable)
[8. Const和非const写法归一](#8. Const和非const写法归一)
1. const的本质:将编译器当作你的朋友去维护一个常量
2. 指针的const
cpp
const char *str1 = "hello";
str1 = "ttt"; // 可以改变指针指向
在上面的代码中,const仅仅为保证str1指向的字符不被改变,但是我们可以将str1指向的内容改变。
cpp
char str[] = "hello";
char* const str2 = str;
str2[0] = 'a'; // 可以修改内容
上面代码中的const只能维持str2指向不变,但是内容可以改变。
因此,我们要将两个const结合:
cpp
const char* const str3 = "hello";
尽可能保证常量不被改变。在这过程中,将const作为让编译器维护常量的口令,将编译器当作你的朋友。
但是这一方案也并非天衣无缝(远古版本c++),在下面会提到
3. 迭代器的const
由于迭代器是基于指针的,也就同理,意味着有两种类型的const:const iterator和const_iterator。
-
const iterator相当于char* const(T* const),指向不可变 -
const_iterator相当于const char*(const T*),内容不可变
4. 用const减少调用错误
cpp
class A
{
public:
A(const int a = 1)
:_a(a)
{ }
A operator*(const A& aa)
{
A ret(_a * aa._a);
return ret;
}
A operator=(const A& aa)
{
_a = aa._a;
return *this;
}
bool operator==(const A& aa) const
{
return _a == aa._a;
}
operator bool()
{
return _a != 0;
}
private:
int _a;
};
A a(1);
A b(2);
A c(3);
在这样一个类中,如果误将if (b == c)写成if (b = c),那么你是不会察觉的。但如果加上const,那么就会报错。
5. const成员函数
在类的函数后加上const有两个作用:
-
让函数做什么更加容易理解
-
可以传const对象
6. bitewise观点
首先,假设有一座房子,坏没坏有种标准。bitewise观点就是只要房子外壳还是一样的,但是里面怎么坏都无所谓。这就是编译器对于const的理解:只要这块内存不动,至于内存怎么变无所谓。
这样就会发生神奇的事(远古版本c++):
cpp
const char* const str = "hello";
char* s = &str[0];
s[0] = 'a';
cout << str << endl;
(注意:由于书本是比较老的,现在即便是c++98编译也会报错,但是由于是书中的例子,就讲究看一下吧)
同时,对于缓存,我们可能需要微小改动里面的值,即使改变了也依旧认为是const。因此也有bitewise观点认为错误,但我们需要的情况。
7. 修正:mutable
在第六点,我们发现bitewise观点很多时候并不能完全胜任
因此就需要修正
有可能我们在const函数中也需要修改值,加上mutable就可以了。
cpp
class A
{
public:
A(int a, int b)
:_a(a)
,_b(b)
{ }
int getb()
{
_b = 100;
return _b;
}
private:
int _a;
mutable int _b;
};
这就是logical constness观点。
8. Const和非const写法归一
比如[]的重载,const函数和非const函数绝大多数代码都是一样的,因此能否复用呢?可以用非const复用const。
cpp
namespace bit
{
class string
{
public:
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
char& operator[](size_t pos)
{
assert(pos < _size);
assert(pos);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
assert(pos);
return _str[pos];
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
}
private:
char* _str = nullptr;
size_t _capacity = 0;
size_t _size = 0;
static const size_t npos = -1;
};
}
上面是简易的string类。
将char& operator[](size_t pos)改为以下代码:
cpp
char& operator[](size_t pos)
{
return
const_cast<char&>(
static_cast<const bit::string&>(*this)
[pos]
);
}
详细解析
-
static_cast<const bit::string&>(*this)其中static_cast为c++的安全转换,将
*this转为const bit::string的引用。 -
[pos]调用[]重载,返回const char调用const版本的operator[]。
-
const_cast<char&>const_cast将常性去掉,转为char引用。
可以看到,代码繁琐冗长,因此要权衡代码可读性与简洁性。
可以const调用非const吗?
理论可以,但是调用非const就意味着需要冒着被改变的风险。