三、尽可能使用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 vec;2 ...3 const std::vector ::iterator iter = vec.begin(); // iter的作用就如T* const4 *iter = 10; // 此时iter是const,是不可更改5 6 std::vector ::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(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 就是上面两者的合集.既是只读的,又是只在当前模块中可见的