阿龙的学习笔记---Effective C++---第一章:习惯C++

  • 条款1:视C++为一个语言联邦

    • C++发展至今已经不只是C with classes的概念了,他是一个多重范式的编程语言。可将其视为一个多重语言的联邦
    • C++的次语言被总结为以下4个:
    1. C语言。说到底C++仍是以C为基础。区块( blocks)、语句( statements)、预处理器( preprocessor)、内置数据类型( built-in data types)、数组( arrays)、指针( pointers)等统统来自C。当你以C++内的C成分工作时,高效编程守则映照出C语言的局限:没有模板( templates),没有异常( exceptions),没有重载( overloading)。
    2. object-Oriented C++,面向对象的C++。这部分也就是 C with Classes所诉求的:case(包括构造函数和析构函数),封装、继承、多态、ⅵrtual函数(动态绑定)等等。
    3. Template C++。这是C++的泛型编程(generic programming)的部分,也是大多数程序员经验最少的部分。实际上由于 template威力强大,它们带来崭新的编程范型(programming paradigm),也就是所谓的 template metaprogramming(TMP,模板元编程)。TMP相关规则很少与C++主流编程互相影响。
      4.STLSTL是个 template程序库,看名称也知道,但它是非常特殊的一个。它对容器( containers)、迭代器( iterators)、算法( algorithms)以及函数对象( function objects)的规约有极佳的紧密配合与协调。

  • 条款2:尽量以const,enum,inline代替#define

    • 首先,使用一个简单的宏定义:#define A 1.73 ,他不会被编译器看到,A符号直接被预处理器代替了,而不会进入符号表,这样使得调试困难,如果这个数值出问题,很难究其源头。
      解决之道是使用一个const定义: const double A = 1.73。这样符号表中会有这个元素。
    • 使用const定义常量会进入符号表,而#define会让每个地方都出现一次1.73,所以const可能会让你的程序更小。
    • 类的专属常量:#define不会有作用域的限制,只要#define了,之后的程序都能用。而在类中定义专属常量则可以用private:const A=1.73,要确保这个常量一个类中只需要一个,所以需要定义成静态成员:private: static const A=1.73
    • 旧式编译器也许不支持上述语法,它们不允许static成员在其声明式上获得初值。如果你的编译器不支持上述语法,你可以将初值放在定义式:
      在这里插入图片描述
    • 上述声明定义可能会有一个新的问题:假如在编译阶段就需要知道这个常量的数值(例如以他作为数组的个数),那么这种方式则不行。我们可以采用另一种经常代替#define的枚举enum作为常量。在这里插入图片描述
    • #define还有容易出问题的地方是定义宏:#define Max(a,b) a>b?a:b。虽然少了函数调用产生的开销,这种方式会有两个问题:一是需要每个都要带括号,否则很容易出错;二是假如带入a++之类的,有可能不是原意,可能会加两次等意外情况。
    • 替代#define宏的方式可以采用template inline函数。 在这里插入图片描述
    • 总结:有了 consts、 enums和inlines,我们对预处理器(特别是# define)的需求降低了,但并非完全消除。# include仍然是必需品,而# ifdef ,# ifndef也继续扮演控制编译的重要角色。目前还不到预处理器全面引退的时候,但你应该可以尽量少用。

  • 条款3:尽量使用const

    • const指定一个不该被改变的对象,告诉编译器和其他程序员,如果有这样的需求,最好都加上const。

    • 最简单用法:const int a=0; 这种用法使得a不能再被赋值。

    • 对于指针的const有两部分:在 ‘ * ’ 号之加const,则是修饰指针指向的地址处的值不能变,即指针所指物为const;在 ‘ * ’ 号之加const,则是修饰指针指向的地址不能变,即指针本身为const。例如:
      在这里插入图片描述

    • const 放在类型名之前和之后是不同是习惯,只在在*号之前都是修饰所指数据。const char *p 和 char const *p是一样的。

    • STL迭代器类似指针的用法,也会有两种形式,不过因为迭代器没有‘*’符号,所以稍有不同。直接在前面加const是代表这个迭代器指向的位置不能改变,即迭代器本身。如果需要迭代器指向的值为const,则声明一个const_iterator即可。比如:
      在这里插入图片描述

    • 函数返回值声明为const,可以防止用户因为意外粗心而造成一些错误,比如说* operator(乘法的重载),如果用户粗心写下了 if(a * b = 0) 这样的代码,则可让编译器报错防止出现运行bug。函数的输入参数也是同理,必要时别忘了加上const。

    • const可以修饰于class的成员函数,并且const和non-const两个只是常量性不同的函数可以重载。

      const修饰成员函数有两个作用:一是能够使得程序可读性增加;二是使得可以操作const对象,因为const的对象只能调用const成员函数。

      下面的例子中还有一个要注意的点,const函数的返回值不受到编译器的限制,假如返回值不是const char&,如果是普通的char&,这个函数本身可以通过编译器,但使用时可能会间接造成const变量被改变。
      在这里插入图片描述

    • 作者还提到了采用常量性转除,使得上例中 non-const operator[] 可以用 const operator[] 实现,巧妙减少了代码的重复性以及维护难度。

      在non-const函数中要调用const函数,则需要先将*this加上const属性再调用。这里用到static_cast<const classname&>。(但如果反过来,const函数中强制转换来调用non-const函数则是不安全的)

      二者之间返回值不同,所以const函数返回的 const char& 还需要除去const属性。使用 const_cast<char&> 将其变成 char&
      在这里插入图片描述


  • 条款4:确认对象在使用前已被初始化

    • 读取未初始化的值会导致不明确的行为。在某些平台上,仅仅只是读取未初始化的值,就可能让你的程序终止运行。

    • 普通的变量初始化很简单。类的初始化则由构造函数来完成,规则就是:对类中的每个对象都进行初始化。

      但重点在于:初始化≠赋值。C++规定,在进入构造函数之前才叫初始化,构造函数内是赋值。所以一个较好的写法是采用成员初始化列表(member initialization list) 的方式。
      在这里插入图片描述
      上面是赋值,下面才是初始化
      在这里插入图片描述
      这样初始化的效率一般更高,因为上面赋值的方法中,每个变量都会调用一次无参数的default构造函数,然后再进入构造函数中赋值。而下面直接在每一个的构造函数中直接初始化了。

      其次,如果有类中的const常量,则一定需要初始化而不能被赋值。

      初始化的顺序最好跟声明时的顺序一样,以免产生一些错误。

    • 最后一点(理解的不透彻,不知道是不是这个意思):
      在这里插入图片描述
      理解的大概就是比如一个classA中的一个static成员变量,就算是non-local static对象,另一个文件中的一个classB的一个对象在初始化时用到这个对象,则会出问题。不理解再看看这个例子https://www.cnblogs.com/clark-lee/p/3870140.html
      在这里插入图片描述
      这里就用函数来解决这个问题,函数返回对象的reference,在调用函数时创建一个static变量,因为调用一定会创建,所以不会有刚才的问题。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值