[Effective C++ 003]尽可能使用const

三、尽可能使用const
经典面试题:     

    ①说出const的至少3个作用
    ②const char* p = x;
       char* const p = x;
      说出上处两个const的作用。

说到这,其实最好是说一下const的语义:

1、const语义
1).代替#define
2).使某个对象(变量)、值,指针,引用不能被修改
3).使类的静态对象在类内部可以初始化
当我们要在类的内部定义某个常量时,就可能用const来修饰,否则的话,就不能初始化

1 class print {
2 private:
3 static const int count = 10; //不能写成static int count = 10;这里的类型只能用整形
4 string info[count];
5 };
6  const int print::count;(附录1)

4).修饰函数参数和返回值,对于类的成员函数可以确定不能修改类的数据成员.
①在函数体的末尾和前面加const的区别
②令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而不至于放弃安全性和高效性。
如:

1 const cgi_ChangeVolumeOperationListData& GetData() const {
2 return *_pData;
3 }

注:如果const被指物是常量,那么将其写在类型之后,星号之前的意义是一样的,如:
void f1(const Func* p); // f1获得一个指针,指向一个常量的(Func)对象
void f2(Func const * p);
5).用const来修饰重载的类的成员函数
详见下面第3点

可以看看这个函数中所有const的意义:
const char* const foo(char const * const str) const
第一个const表示返回类型为const,也就是不能把此函数的返回值当作左值来使用。
第二个const表示指针的不可变性,但在这是可以省略,因为返类型已经是const。
第三个const表示str的常量性,也就其内容是不能改变,可以写在其前面的char的前面。
第四个const表示str的指针的常量性,也就是此指针不能指向别的地址。
第五个const表示此函数的常量性(前提是类的成员函数),不能修改所在类的数据成员

2、STL迭代器与const的关系

所谓的STL,其实就是

STL:Standard Template Library,标准模板库
STL提供了一组容器、迭代器、函数对象和算法的模板。容器是一个类似数组的单元,其存储的值类型相同;
算法是完成特定处理的处方;迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针。

2)声名STL迭代器为const
STL迭代器是指针为根据塑模出来的,其作用就像一个T*指针,因此声名迭代器为const就跟声名指针
一样的,表示这个迭代器不得指向不同的东西,但其指向的事物的值却是可以修改的。要使其指向值不可被修
改,可以定义为const_iterator,例:

1 std::vector<int> vec;
2 ...
3 const std::vector<int>::iterator iter = vec.begin(); // iter的作用就如T* const
4 *iter = 10; // 此时iter是const,是不可更改
5 
6 std::vector<int>::const_iterator iter = vec.begin();
7 ++iter; // 此时*iter是const,不可更改

3、const成员函数
1)将const实施与成员函数的目的,是为了确认该成员函数可作用与const对象身上,其理由如下:
①使得类的接口比较容易被理解
②使“操作const对象”成为可能

2)在成员函数中,如果只是常量性不同,是可以被重载的,如:

 1 class string {
 2 public:
 3 char& operator[](int position){ //
 4 return data[position];
 5 }
 6 const char& operator[](int position) const {
 7 return data[position];
 8 }
 9 private:
10 char *data;
11 };

那么我们就可以如下使用:
string s1 = "hello";
cout << s1[0]; // 调用非const
const string s2 = "world";
cout << s2[0]; // 调用const
★非const operator[]的返回类型必须是一个char的引用———char本身则不行,因为修改一个“返回值为固定
类型”的函数的返回值绝对是不合法的。即使合法,由于c++“通过值(而不是引用)来返回对象”机制的原因,
s.data[0]的一个拷贝会被修改,而不是s.data[0]本身

3)一个成员函数为const的确切含义是什么呢?
两种主要看法:
①数据意义上的const(bitwise constness)
②概念意义上的const(conceptual constness),又称logical constness
bitwise constness:
当且仅当成员函数不修改对象的任何数据成员(静态数据成员除外)时,即不修改对象中任何一个比特(bit)时,这个成员函数才是const的。
实际上,bitwise constness正是c++对const问题的定义,const成员函数不被允许修改它所在对象的任何一个数据成员。
但事情总没有绝对之分,就如同光既是直线传播的,也可以理解为光子一样。特例总会有的:
很多不遵守bitwise constness定义的成员函数也可以通过bitwise测试。特别是,一个“修改了指针所指向的数据”的成员函数,其行为显
然违反了bitwise constness定义,但如果对象中仅包含这个指针,这个函数也是bitwise const的,编译时会通过。(示例)

class string {

public:
// 构造函数,使data指向一个value所指向的数据的拷贝
string(const char *value);
...
operator char *() const { return data;}
private:
char *data;
};

const string s = "hello"; // 声明常量对象
char *nasty = s; // 调用 operator char*() const,取得指针
*nasty = 'm'; // 修改s.data[0]
cout << s; // 输出"mello"
这就导致了logical constness引入:一个const成员函数可以修改它所在对象的一些数据(bits) ,但只有在客户端不会发觉的情况下。例:

class string {
public:
// 构造函数,使data指向一个 value所指向的数据的拷贝
string(const char *value): lengthisvalid(false) { ... }
size_t length() const;
private:
char *data;
size_t datalength; // 最后计算出的string的长度
bool lengthisvalid; // 长度当前是否合法
};

size_t string::length() const {
if (!lengthisvalid) {
datalength = strlen(data); // 错误!
lengthisvalid = true; // 错误!
}
return datalength;
}

这里length方法的实现显然不能让编译器接受,要想完成更改,可以使用关键字mutable:
mutable size_t datalength;
mutable bool lengthisvalid;
这样即使在const成员函数中,这些成员变量也可以更改了。

总结:关于const,其实没有一个太固定的概念,可以理解为bitwise constness我所欲也,logical constness亦我所欲也,二者不可兼得..

4)如何更改const成员?
引言:为什么还需要更改const成员呢?
如果一个较庞大的const成员被各种反复引用,那么代码的重复部分就非常之多,伴随的编译时间、维护、代码膨胀等问题随之而来。
在上面bitwise constness与logical constness不可兼得的年代,虽然mutable提供了一种解决方案,但是也有时候是其解决不了(比如mutable
为加入C++标准的年代等)。
解决方案①:通过类型转换消除const(不推荐)
将一个const对象传递到一个取非const参数的函数中,同时你又知道参数不会在函数内部被修改的情况时。
const char *klingongreeting = "nuqneh"; // "nuqneh"即"hello"
char * length = const_cast<char*>(klingongreeting);
注:只有在被调用的函数(比如本例中的strlen)不会修改它的参数所指的数据时,才能保证它可以正常工作。

解决方案②:通过局部变量指针实现
类c的一个成员函数中,this指针有如下的声明:
c * const this; // 非const成员函数中
const c * const this; // const成员函数中
这种情况下(即编译器不支持mutable的情况下),如果想使那个有问题的string::length版本对const和
非const对象都合法,就只有把this的类型从const c * const改成c * const。不能直接这么做,但可以通过
初始化一个局部变量指针,使之指向this所指的同一个对象来间接实现。然后,就可以通过这个局部指针来访问
你想修改的成员:

size_t string::length() const {
// 定义一个不指向const对象的局部版本的this指针
string * const localthis = const_cast<string * const>(this);

if (!lengthisvalid) {
localthis->datalength = strlen(data);
localthis->lengthisvalid = true;
}
return datalength;
}

总结:
1)将某些东西声明为const可帮助编译器诊断出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、
成员函数本体。
2)编译器强制实施bitwise constness,但是你在编写程序时应该使用“概念上的常量性”(conceptual constness)。
3)当const和非const成员函数有着实质等价的实现时,令非const版本调用const版本可避免代码冲突。

附录1:
※关于const,static与static const
const就是只读的意思,只在声明中使用;
static一般有2个作用,规定作用域和存储方式.对于局部变量,static规定其为静态存储方式,每次调用的初始值为上一次调用的值,调用结束后存储空间不释放;
对于全局变量,如果以文件划分作用域的话,此变量只在当前文件可见;对于static函数也是在当前模块内函数可见.
static const 就是上面两者的合集.既是只读的,又是只在当前模块中可见的

原文地址:https://www.cnblogs.com/hustcser/p/2731085.html