资讯专栏INFORMATION COLUMN

Effective C++读书笔记(2)

brianway / 1773人阅读

摘要:如进行这种操作这种情况少见少打了一个等号导致判断结构不符合碰到这种问题的时候可能排错会比较浪费时间,但是如果返回值声明成,就可以从根源上断绝这种错误的发生。

    这几天一直在忙换工作的事情,没有保证好更新的效率,既然决定了就不能拖拖拉拉的,从今天开始要保证3天一篇的速度,好了,让我们继续Effective C++的学习吧。

建议 03 : 尽可能的使用const关键字

Use const whenever possible

    这条建议应该被作为信条来使用,在阅读Amazon Alexa和google Grpc源码的过程中,我发现一流大厂对这一点的执行的相当好,及时不考虑它在语法层面上的保护作用(只读),它还能非常好的告诉阅读你代码的人,这个对象/函数是只读的,它不应当被修改。它是非常有效的语义约束,不仅是对编译器,同时它也约束了开发者作出不必要的修改,既然某个对象应该是只读的,那么我们为什么不给他加个const进一步确保它不被修改呢?编译器会对所有const修饰的对象进行编译时检查(compile-time checking),确保约束没有被违反。

    首先我们来总结一下const可以使用的场景。const几乎可以用来修饰一切你能想的到的对象,包括:

常量(global or in the namespace)

文件

函数

区块作用域(block scope)中被声明为static的对象

类内部成员变量

指针以及指针指向

关于指针和指向的const语义是非常常见的(并且在面试中可能会被问到),如:

char strs[] = "Hello World!";
char* p = strs; //普通指针
const char* p = strs; //一个指向字符常量的指针  (non-const pointer, const data)
char* const p = strs;//一个字符指针常量  (const pointer, non-const data)
char const *p = strs;//一个指向字符常量的指针 (non-const pointer, const data)
const char* const p = strs;//一个指向字符常量的指针常量 (const pointer, const data)

看起来是有一点点绕,但是只要观察const在解引用符(*)的左边还是右边就可以判断是指针常量还是指向常量了
如果const在解引用符的左边代表指针指向一个常量值,如果const在解引用符的右边则代表指针是一个常量,指向不可修改,如果解引用符的左右都有const修饰就代表这个指针是一个指向常量的指针常量。请用下面的这句来速记:

左值右向

在左边代表指向的值不可修改,在右边代表指向不可修改。

根据习惯,在进行修饰常量指针时(const data)有的程序员习惯将const写在类型左边:

void func(const char* p); 

有的习惯将const写在类型右边解引用符左边:

void func(char const *p);

这两种写法在功能上是一样的,没有本质的区别。

一些情况下,我们经常想要遍历一个容器,我们可以使用STL的迭代器来完成遍历,但是我们不希望引起容器值的改变,这个时候我们可以使用const_iterator来进行迭代过程:

std::vector vec;
..

std::vector::const_iterator iter = vec.begin();
for (iter; iter != vec.end(); ++iter) {
    std::cout << *iter << std:endl;
    ...//do something like print
}

书中还介绍到了如果要使得迭代器指向不可修改应该这样声明

std::vector vec;
... 
const std::vector::iterator iter = vec.begin();
*iter = 10;  //true 指向内容可以被修改
++iter;// 报错,指向不可修改

这种情况比较少,既然用到了迭代器,不让迭代器移动不太可能,但是写法还是需要注意一下。

一般情况下我们在进行迭代器移动的时候用++iter,而不用iter++,因为++iter是左值,已经是个对象,iter++是右值,是个引用对象的表达式,返回一个临时变量,效率要低一些。

返回值const化的情况

竟然有大佬给我评论了,有点小激动。之前的描述有问题,一般情况下都不会对函数返回值进行const化,下面可能举出需要返回值const修饰的情况:

函数返回值本就应该是常量的

为了让重载运算符符合逻辑,运算符重载加const 约束 对 a+b+c 这样的运算没有影响,因为a+b 运算的结果是const ,但对其只是只读操作,会创建一个新的 A 类返回。

通过函数创建指向常量的指针,如果通过函数来创建常字符串,除了在main 函数中约束之外,也可以在函数返回类型中约束

满足对const成员函数的调用,const类型的对象,不能调用自身的非const成员函数,但是可以调用自己的const成员函数

const 成员函数的返回类型是引用时候,需要加const 约束

一般情况下,让函数返回一个只读值,可以降低客户错误照成的意外或者未知BUG,并且还可以保证安全性和高效性,如在有理数的运算符重载实现中,将返回值修饰为只读,可以避免用户对返回值做一些奇怪的操作产生意外结果。

class Rational { ... };
const Rational operator* (const Rational& lhs, const Rational& rhs);

//如进行这种操作

Rational a, b, c;
...
(a * b) = c; //这种情况少见

if (a * b = c)...// 少打了一个等号导致判断结构不符合

碰到这种问题的时候可能排错会比较浪费时间,但是如果返回值声明成const,就可以从根源上断绝这种错误的发生。

const 成员函数

将成员函数声明为const有两个好处,第一,接口使用人员可以很好的了解到这个接口是否会改变对象内容。第二,const对象使得“操作const对象”成为可能,所谓的“操作const对象”就是尽量避免pass by value造成的拷贝开销,而是替代于const对象传递(reference-to-const),而为了实现这种方式,前提就是存在const成员函数来处理const对象。

关于成员函数的const性质有两个概念:bitwise constnesss(physical constness) 和 logical constness

bitwise const代表成员函数不直接修改对象内的任何一个bit,这种概念的好处是编译器容易检查违反点,只需要检测成员变量的赋值语句就可以了。这也正是目前C++所采用的,但是有一个问题,就是虽然函数内不会对对象进行任何修改,但是其返回值或者动作间接的导致了修改的发生,如返回了一个非const的值,外部通过修改这个值达到了修改对象的作用,如:

const CTextBlock cctb("hello"); //声明常量对象
char* pc = &cctb[0];            //调用const operator[] 取得对应下标指针 (假设操作符已经重载)
*pc = "H";                      //还是对对象进行了修改

所以还有另外一种概念 logical constness,这种概念允许对象部分内容在const成员函数中被修改,如存在一个文本编辑器它要获取当前文本长度,那么它其实并没有修改文本内容,但是文本长度可能实时在改变,此时我们声明了一个:

std::size_t tl;
std::size_t Textpanel::length() const {
   ...
   tl = std::strlen(text); //错误 const内不允许对成员变量赋值
   return tl;
}

此时我们可以使用mutable来创建一个const相关的摆动场来允许tl可修改,只需要将tl声明成:

mutable std::size_t tl;

就可以被修改了。

虽然编译器采用的是bitwise const,但是在实际编程中我们应该使用logical constness来进行开发。

当类的non-const版本的成员函数和const版本的实现等价时,为了减少重复代码,可以采用const_cast和static_cast来进行对const版本的Non-const拓展:

class TextBlock{

public:
const char& operator[] (std::size_t position) const {
   ...
   return text[position];
}

char& operator[] (std::size_t position) {
   return
   cons_cast(
     static_cast(*this)[position]
     );
}
};

总结:

将某些东西声明为const可以帮助编译器侦测出错误用法.const可以被用于修饰任何作用域内的对象,函数参数,函数返回类型,成员函数本体。

编译器强制使用bitwise constness,但是我们在进行开发的时候应该使用概念上的常量性(conceptual constness)

当const和non-const成员函数有着相同的实现时,可以在non-const版本中调用const版本并转换减少代码重复。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/10616.html

相关文章

  • Effective C++读书笔记(1)

    摘要:给出了个改善程序的具体建议,并且提出了一些在程序设计中经常出现的坑。的泛型编程部分,就是的函数模板类模板,它们使得泛型编程成为可能。另外,需要注意的是在类中对参数的初始化,一般来说,常量值使用声明式在头文件中完成声明。   写在前面:之前一直有在写博客,但是总是没坚持下来,感觉博客作为记录自己学习过程的工具还是十分有意义的,故而决定在这儿开始我博客之路的第一步,以C++的学习为始,从得...

    xiangchaobin 评论0 收藏0
  • Effective JavaScript读书笔记(一)

    摘要:如果为假值,不传或者传入,函数都会返回但是,传入这个值是完全有可能的,所以这种判断形势是不正确的或者使用来判断也可以原始类型优于封装类型对象拥有六个原始值基本类型布尔值,数字,字符串,,和对象。 作为一个前端新人,多读书读好书,夯实基础是十分重要的,正如盖楼房一样,底层稳固了,才能越垒越高。从开始学习到现在,基础的读了红宝书《JavaScript高级程序设计》,犀牛书《JavaScri...

    zhoutao 评论0 收藏0
  • Effective JavaScript读书笔记(二)

    摘要:尽可能的使用局部变量,少用全局变量。正确的实现就是在函数体内部使用将声明成局部变量。在新特性中,引入了块级作用域这个概念,因此还可以使用,来声明局部变量。它们共享外部变量,并且闭包还可以更新的值。 变量作用域 作用域,对于JavaScript语言来说无处不在,变量作用域,函数作用域(运行时上下文和定义时上下文),作用域污染等等都跟作用域息息相关,掌握JavaScript作用于规则,可以...

    Yuqi 评论0 收藏0
  • c++代码优化~effective c++总结 10.12

    摘要:出现在左边表示被指物是常量,出现在右边表示指针自身是常量。没问题改变的是所指物错误是在函数声明时的应用令函数返回一个常量,往往可以降低因客户错误导致的意外。总结将某些东西声明为可以帮厨编译器侦测出错误用法。  一、习惯c++ 1.尽量使用const,enum,inline替换#define ...

    elva 评论0 收藏0
  • Effective Java》学习笔记 第二章 创建和销毁对象

    摘要:第二章创建和销毁对象何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。表示工厂方法所返回的对象类型。 第二章 创建和销毁对象 何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。 1 考虑用静态工厂方法代替构造器 一般在某处获取一个类的实例最...

    tinylcy 评论0 收藏0

发表评论

0条评论

brianway

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<