条款03:尽可能使用const

张开发
2026/4/21 13:14:23 15 分钟阅读

分享文章

条款03:尽可能使用const
指针与const:如果要定义某指针或数据为常量不允许改变:constchar*p;//数据是const,数据不允许被改变char*constp;//指针是const,指针不允许被改变constchar*constp;//数据与指针都是const,数据与指针都不可以被改变记忆法: const在星号左边修饰数据const在星号右边修饰指针以及如下两个语句的功能是相同的不需要对此产生困惑:onstchar*pw;//都表示指向常量char的指针charconst*pw;迭代器与const迭代器在功能上相当于指向某类型T的指针 T*因此如果想定义某迭代器指向一个常数使用const_iterator是不可以的这样只相当于定义一个迭代器为一个常量(T* const)例如:conststd::vectorint::iterator itv.begin();//注意此声明只表示迭代器本身是常量*it10;//编译通过迭代器是常量但数据可以被修改it;//编译失败因为const迭代器不允许被改变解决方法使用const_iterator:std::vectorint::const_iterator itv.begin();//使用了const_iterator类型*it10;//编译失败数据不允许被改变it;//编译通过迭代器本身可以被改变令函数返回一个常量值往往可以降低因客户端而造成的意外(尽量使用const可以帮助调试)constRationaloperator*(constRationallhs,constRationalrhs);这样可以避免if((a*b)c)这样的错误。const成员函数给成员函数使用const关键字是非常重要的它可以让接口更加直观直接告诉用户这个函数是不是只读(Read only)会不会改变某变量。更重要的是用const修饰的对象只能调用const修饰的成员函数因为不被const修饰的成员函数可能会修改其他成员数据打破const关键字的限制。因此需要同时声明有const和没有const的成员函数例如:constcharoperator[](size_t pos)const;charoperator[](size_t pos);对于某自定义的类Text:Textt(Hello);constTextct(Hello);std::coutt[0];//调用了不加const修饰的索引操作符std::coutct[0];//调用了const版本, 但如果只有不加const的操作符将会报错discard qualifiert[0]x;//成立但注意此索引操作符必须声明为引用才可以支持赋值操作ct[0]x;//错误常量不能被修改成员函数的常量性(Constness)const成员函数承诺不修改对象的任何成员变量C标准对成员函数常量性的规定是数据常量性(bitwise constness)即不允许常量对象的成员数据被修改。C编译器对此的检测也十分简单粗暴只检查该成员函数有没有给成员数据的赋值操作。但如下情形即使修改了某个数据也可以通过编译器的检测constTextct(Hello);//构造某常量对象char*pcct[0];//取其指针*pcK;//通过指针修改常量对象编译不会报错结果为Kello数据常量性还有另一个局限性例如:classText{public:std::size_tlength()const;private:char*pText;std::size_t length;boollengthValid;....};std::size_tText::length()const{if(!lengthValid){//做某些错误检测lengthstd::strlen(pText);lengthValidtrue;}returnlength;//这行才是代码核心}在这段代码中length()函数要做某些错误检测因此可能会修改成员数据。即使真正的功能核心只是返回字符长度编译器依然认为你可能会修改某些成员数据而报错。因此更好的方法是逻辑常量性(Logical constness)即允许某些数据被修改只要这些改动不会反映在外例如以上问题可以用mutable关键字来解决:mutablestd::size_t length;mutableboollengthValid;这样成员函数length()就可以顺利通过编译。此外注意除mutable之外静态成员(static)也可以被const成员函数修改。在定义常量与非常量成员函数时避免代码重复可能大家会有所困惑既然两个版本的成员函数都要有为什么又要避免重复?其实在这里指的是函数的实现要避免重复。试想某函数既要检查边界范围又要记录读取历史还要检查数据完整性这样的代码复制一遍既不显 得美观又增加了代码维护的难度和编译时间。因此我们可以使用非常量的函数来调用常量函数。constcharoperator[](std::size_t pos)const{....}charoperator[](std::size_t pos){returnconst_castchar(//const_cast去掉const关键字并转换为charstatic_castconstText(*this)[position];//给当前变量加上const关键字才可以调用const操作符);}为了避免无限递归(如果存在const调用非const则const调用非const和非const调用const就会生成无限循环)调用当前非常量的操作符我们需要将(*this)转换为const Text类型才能保证安全调用const的操作符最后去掉const关键字再将其返回巧妙避免了代码的大段复制。但注意如果使用相反的方法用const函数来调用non-const函数就可能会有未知结果因为这样相当于non-const函数接触到了const对象的数据就可能导致常量数据被改变。只能在非const取消重复不能在const调用非const对const成员函数另一种方式解释对于成员函数的const有两个概念bitwise constness和logical constness。bitwise constness指的是成员函数只有在不更改对象之任何成员变量才可以说是const。也就是说它不更改对象内的任何一个bit。不幸的是许多成员函数虽然不十足具备 const性质却能通过bitwise测试。例如一个更改了“指针所指物”的成员函数虽然不能算法const但如果只有指针率属于对象那么此函数不会引发编译器意义这导致反直观结果。classCTextBlock{public:charopeartor[](std::size_t position)const//bitwise const声明但其实不适当private:char*pText;};cosnt CTextBlockcctb(Hello);char*pccctb[0];*pcJ;你创建一个常量对象设以某值而且只对它调用const成员函数。但你还是改变了它的值。​这种情况导出所谓的logical constness。这一派拥护者主张一个const成员函数可以修改它所处理的对象内的某些bits但只有在客户端侦测不出的情况下才得如此。classCTextBlock{public:std::size_tlength()const;private:char*pText;std::size_t textLength;boollengthIsValid;};std::size_tCTextBlock::length()const{if(!lengthIsValid){textLengthstd::strlen(pText);lengthIsValidtrue;}returntextLength;}length的实现当然不是bitwise const因为textLength和lengthIsValid都可能被修改。这两笔数据被修改对于const CTextBlock对象虽然可接受但编译器不同意。它们坚持bitwise constness。解决方案很接单把它们设置成mutable。const和non const防止重复constcharoperator[](std::size_t position)const{...//边界检验...//日志记录访问记录...//检验数据完整性returntext[position];}charoperator[](std::size_t position){...//边界检验...//日志记录访问记录...//检验数据完整性returntext[postion];}这里用non const函数调用const函数的方法来避免代码重复这种方式并不推荐。用const调用non const的方法是不合适的因为non const方法有可能修改数据而const调用之后就可能也会修改数据不符合函数的const语义。总结:指针迭代器引用本地变量全局变量成员函数返回值都可以使用const来实现数据只读的目的const是C一个非常强大的特性。除此之外它还能帮助加快调试过程即使编译器使用数据常量性的标准我们编程的时候应该采用逻辑常量性对相关不可避免更改的成员数据加上mutable关键字来修饰当有大段复制代码出现在const和non-const的成员函数中可以使用non-const函数来调用const函数来避免复制

更多文章