const的使用
来源:互联网 发布:晋升锦标赛与政治网络 编辑:程序博客网 时间:2024/06/05 08:53
const的使用
《Effective C++》条款3 学习总结
- const的使用
- const与指针
- const与类
- const成员函数
- bitwise constness又称physical constness和logical constness
- 在const和non-const函数的重载重避免代码重复
- 转载请注明出处
1.const与指针
#include <iostream>int main(){ char str[]="hello world"; char *p1(str); //指向非常量的非常量指针 指向数据:非常量 指针:非常量 const char *p2(str); //指向常量的非常量指针 指向数据:常量 指针:非常量 char const *p3(str); //指向常量的非常量指针 指向数据:常量 指针:非常量 char * const p4(str); //指向非常量的常量指针 指向数据:非常量 指针:常量 const char * const p5(str); //指向常量的常量指针 指向数据:常量 指针:常量 return 0;}
上面的代码看起来有点晕,有一个规律:
1.以指针的*(星号)为分界点
2.星号 左边 的const修饰的是指针所指向的数据
3.星号 右边 的const修饰的是指针本身
4.const int 和 int const 等价
2.const与类
const的一个重要作用就是:防止用户或程序员意外修改了不应该被修改的值或其他的东西。
举个例子:
我们都知道右值是不能放在等号左边被赋值的
#include <iostream>int main(){ int a(10),b(100),c(1110); (a+b)=c; //将a与b相加,然后将c的值赋值给(a+b)的结果 return 0;}
错误如下:
但是如果是类,并且重载了+运算符
#include <iostream>class demo{public: demo(){} demo(int data_):data(data_){} int data=0; //重载+运算符 demo operator+(const demo & other){ return demo(this->data+other.data); }};int main(){ demo a(10),b(100),c(1110); (a+b)=c; //将a与b相加,然后将c的值赋值给(a+b)的结果 return 0;}
完美的编译通过了,但是(a+b)确实是右值,这不是矛盾了吗?
所以,我们可以将operator+写成
const demo operator+(const demo & other){ return demo(this->data+other.data); }
结果,编译提示错误:
当然你会说,没有人会那么变态地(a+b)后用c为之赋值。
那么下面一个例子也有足够的理由让你在从在运算符时重视const属性的添加与否。
if(a+b=c){ //do something}else{ //do something else}
你看到端倪了吗?
if(a+b=c) ,我们都有粗心的时候,加上const就不会为这种低级错误抓耳挠腮了。当然如果你有足够的自信不会把==写成=,那么加不加const也没有什么区别。
3.const成员函数
const用于成员函数的方式可以是下面的这种方式:
class demo{public: demo(){} const demo& do_something()const{ //do something }};
这种方式的存在理由至少有下面两点:
(来自《Effective C++》)
1.它们使class比较容易被理解。这是因为,得知哪个函数可以改动对象内容而哪个函数不行,很是重要。
2.它们使操作const对象成为可能。(改善C++程序效率的一个根本方法是以pass by reference-to-const方式传递对象,此技术可行的前提是,我们有const成员函数可用来处理取得的const对象)。
第一点很容易理解,第二点需要稍微解释(有经验的读者可以跳过,这里仅供初学者观看)
1.使用reference-to-const可以防止不必要的对象拷贝
(传值和传递引用的根本性的不同指出就是:由于函数有副本机制,传值会激发函数的副本机制,传入的是对象的拷贝,操作的是对象的拷贝,而不是我们传入的那个参数本身)
2.同时可以防止对象被意外修改(因为我们使用了const引用)(此时我们很可能只是取出对象中的某些值加以操作,而不会改变对象的任何一个成员)。
3.两个成员函数,如果只是常量性不同,可以被重载(这是C++一大特性)
class demo{public: demo():m_dat(0){} demo(int dat):m_dat(dat){} void print(){ std::cout<<"void print() called"<<std::endl; std::cout<<m_dat<<std::endl; } void print()const{ std::cout<<"void print()const called"<<std::endl; std::cout<<m_dat<<std::endl; }private: int m_dat;};
函数中两个print没有本质的区别,只是 void print()const 发誓始终不会对类进行任何改动。
当然我们不会重载如此无聊的函数,只是作为一个例子,更好的例子是iterator:(下面的例子是< array >头文件中的begin函数,用于返回一个迭代器)
// Iterators. iterator begin() noexcept { return iterator(data()); } const_iterator begin() const noexcept { return const_iterator(data()); }
3.非const函数无法操作const对象
#include <iostream>class demo{public: demo():m_dat(0){} demo(int dat):m_dat(dat){} void print(){ std::cout<<"void print() called"<<std::endl; std::cout<<m_dat<<std::endl; }private: int m_dat;};int main(){ const demo mx(200); mx.print(); return 0;}
编译出错:
正如编译器所说,我们无法调用非const成员函数 print
为什么呢?
答:因为我们操作的是const对象,也就是该对象禁止我们对其进行任何改动。然而,作为非const成员函数并没有发誓:我不会对对象进行任何的改动。所以,当我们调用print成员函数时,print很有可能对对象进行改动,这是不符合C++const属性的,所以编译器禁止非const函数的调用。
所以我们对代码进行如下改动:
#include <iostream>class demo{public: demo():m_dat(0){} demo(int dat):m_dat(dat){} void print(){ std::cout<<"void print() called"<<std::endl; std::cout<<m_dat<<std::endl; } void print()const{ std::cout<<"void print()const called"<<std::endl; std::cout<<m_dat<<std::endl; }private: int m_dat;};int main(){ demo m(100); m.print(); const demo mx(200); mx.print(); return 0;}
如上图,完美运行。可以看出,const对象调用的是void print()const重载。
4.bitwise constness(又称physical constness)和logical constness
(来自《Effective C++》)
bitwise constness成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是const。也就是说他不更改对象内的任何一个bit。这种论点的好处是很容易侦测违反点:编译器只需寻找成员变量的赋值动作即可。bitwise constness正是C++对常量性(constness)的定义,因此const成员函数不可以更改对象内任何non-static成员变量。
但是有些不完全的const函数却可以逃过bitwise constness的检测:
#include <iostream>#include <cstring>class demo{public: //默认的构造函数 demo():m_str(nullptr){} //用字符串初始化的构造函数 demo(char *str):m_str(new char[std::strlen(str)+1]){ strcpy(m_str,str); } //重载[] char & operator[](std::size_t position)const{ return m_str[position]; }private: //存储的字符 char * m_str; //友元函数,重载输出流<< friend std::ostream & operator<<(std::ostream & out,const demo&obj);};std::ostream & operator<<(std::ostream & out,const demo&obj){ return out<<obj.m_str;}int main(){ const demo mydemo("hello world");//构造一个demo对象mydemo std::cout<<mydemo<<std::endl;//打印mydemo中的字符串 mydemo[0]='z'; //取出mydemo的中存储的第一个字符,并赋值为‘z’ std::cout<<mydemo<<std::endl;//再次打印mydemo中的字符串 return 0;}
如图,hello world 竟然变成了 zello world
从上面的例子中,我们发现char & operator[](std::size_t position)const虽然声明为const成员函数,但是他的返回值确实一个 非常引用。所以我们可以修改之。
但是mydemo的确是const对象,我们怎么就修改了它的成员呢?
答:我们此时并没有修改mydemo的成员,但是我们修改了mydemo成员m_str所指向的数据。(但是这并不是我们使用const对象的初衷,我们的初衷是:哪怕是指向的数据也是无法被修改的)
mydemo的确是const对象,我们可以很直观的知道demo的成员m_str此时具有const属性,我们无法修改它。但是此时的const指的是,m_str这个指针(他是demo的一个成员指针变量)无法被修改const函数修改,但并不意味着它所指向的数据无法修改。
这里的问题关键是:我们让operator[]返回了一个非常量引用。我们只需将返回值声明为const即可:
const char & operator[](std::size_t position)const{ return m_str[position]; }
上述这种情况值得就是logical constness:(来自《Effective C++》)一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此。
还有一个问题:有时我们需要修改某些变量,但是const对象只允许调用const函数,而const函数中,我们无法对对象中的任何变量做出修改。此时我们就需要用到mutable变量。
mutable可以释放掉non-static成员的bitwise constness约束。
上例子:(下面我们重载了adjust_length的const版本和非const版本)
#include <iostream>#include <cstring>class demo{public: demo():m_str(nullptr),length(0){} demo(char *str){ length=std::strlen(str); m_str=new char[this->length+1]; strcpy(m_str,str); } void adjust_length()const{ std::size_t tmp=strlen(m_str); if(length!=tmp){ length=tmp; } } void adjust_length(){ std::size_t tmp=strlen(m_str); if(length!=tmp){ length=tmp; } }private: char * m_str; std::size_t length;};int main(){ return 0;}
编译器提示错误:
错误处在下面这一段代码:
void adjust_length()const{ std::size_t tmp=strlen(m_str); if(length!=tmp){ length=tmp; } }
我们无法校对const对象的length,而这种校对又显得十分重要(虽然不太必要,仅一个例子),我们可以这样做,如下:
#include <iostream>#include <cstring>class demo{public: demo():m_str(nullptr),length(0){} demo(char *str){ length=std::strlen(str); m_str=new char[this->length+1]; strcpy(m_str,str); } void adjust_length()const{ std::size_t tmp=strlen(m_str); if(length!=tmp){ length=tmp; } } void adjust_length(){ std::size_t tmp=strlen(m_str); if(length!=tmp){ length=tmp; } }private: char * m_str; mutable std::size_t length;};int main(){ return 0;}
精髓就在: 这里。
5.在const和non-const函数的重载重避免代码重复
假设有下面这样的一个类:
class demo{public: demo():m_str(nullptr){} demo(char *str):m_str(new char[std::strlen(str)+1]){ strcpy(m_str,str); } const char & operator[](std::size_t position)const{ //....检查position是否越界 //....检查数据的完整新 //....对该操作进行记录,写入log(日志) return m_str[position]; } char & operator[](std::size_t position){ //....检查position是否越界 //....检查数据的完整新 //....对该操作进行记录,写入log(日志) return m_str[position]; }private: char * m_str;};
我们会发现,operator[]的const和非const版本的过程极度相似,甚至二者之间的差别只是返回值const和非const的差别。
即使我们将中间的类似过程封装为demo类的private成员函数,但是我们还是不可避免的造成了一些不必要的开销,如函数的调用,函数的返回值等。
问题解决:Casting away constness
安全版本:
class demo{public: demo():m_str(nullptr){} demo(char *str):m_str(new char[std::strlen(str)+1]){ strcpy(m_str,str); } const char & operator[](std::size_t position)const{ //....检查position是否越界 //....检查数据的完整新 //....对该操作进行记录,写入log(日志) return m_str[position]; } char & operator[](std::size_t position){ return const_cast<char&>(static_cast<const demo>(*this)[position]); }private: char * m_str;};
代码的亮点就在:
char & operator[](std::size_t position){ return const_cast<char&>(static_cast<const demo>(*this)[position]); }
解释:此时我们做了两次数据类型转换。
第一次:
static_cast<const demo>(*this)[position];
这里我们为了调用const版本的operator[] ,我们只有将对象转换为const对象然后再调用const版本的operator[] ,因为我们没有直接的语法可以调用const版本的函数重载。
第二次:
const_cast<char&>(/*......*/);
此时,我们将const版本调用后返回的const char&转换为char&,去掉const属性我们要调用的是const_cast。第一次之所以调用static_cast是因为第一次转换并没有涉及const属性的去除。
这里我们用的的技巧是:当const版本和非const版本函数重载其操作非常相似时,我们可以通过 非const版本函数调用 const版本函数重载 来避免代码重复。
值得注意的是:上述行为的反向行为是危险的。这个反向行为就是:重载const版本函数时,通过调用 非const版本函数 来达到避免代码重用的目的。
危险版本:
#include <iostream>#include <cstring>class demo{public: demo():m_str(nullptr){} demo(char *str):m_str(new char[std::strlen(str)+1]){ strcpy(m_str,str); } const char & operator[](std::size_t position)const{ return static_cast<const char&>((*const_cast<demo*>(this))[position]); } char & operator[](std::size_t position){ //....检查position是否越界 //....检查数据的完整新 //....对该操作进行记录,写入log(日志) return m_str[position]; }private: char * m_str;};int main(){ const demo d("hello"); std::cout<<d[0]<<std::endl; return 0;}
解释:上述代码中:
const char & operator[](std::size_t position)const{ return static_cast<const char&>((*const_cast<demo*>(this))[position]); }
细心的同学会发现:它的步骤就是是 安全版本 的反向。虽然static_cast< const char& >(…..) 显得多余。
为什么是危险的?
答:const版本函数中调用的是非const版本函数,关键就是我们不知道非const版本的函数中,对数据到底做了什么处理,到底有没有修改数据。所以,这种做法是危险的。
转载请注明出处
- const的使用,const char * const foo(char const * const str) const;什么意思?
- Const 的使用
- const的使用
- const的使用
- Const的使用
- 关于const的使用
- const的使用
- const volatile的使用
- const 的使用
- const的使用
- 使用const的好处
- Const 的使用
- const关键字的使用
- const的使用
- const的使用
- const 的使用
- 使用Const的理由
- const的使用
- qt中文乱码问题
- ubuntu下django和apache的部署
- Windows上简单的Apache守护进程
- hdoj2073(无限的路
- The Makings of Enduring Organizational Culture(持久组织文化的形成)
- const的使用
- JS开发者常用的10个Sublime Text插件
- Gcd技术
- LightOJ 1046 Rider
- mybatis操作
- sicily 1202. The Bank of Kalii
- ecshop、织梦、discuz、wordpress比较
- HDU_1856
- 机器学习笔记_ 降维_2:PCA