《Effective C++》 个人笔记

  • 注:
    ①因为本书就是提供一些C++编程的建议,可能会有很多诸如“要”、“一定”之类的词语。其实很多不这么做并不会出错,只是这样写出的代码可能存在一些后续扩展等等问题。
    ②以为本人是菜鸡一个,第一遍读很多还是没读懂,所以笔记也有遗漏

0. 导读

  • 默认构造函数,要不就没有参数,要不就是每个参数都有缺省值。
  • 在声明类的时候用=,调用的是拷贝构造函数;而在其他时候调用=,调用的是被重载的=运算符。
  • 函数参数的按值传递调用的是拷贝构造函数,一般还是用const引用传递参数比较好。

 

1. 让自己习惯C++

  • 可以把C++看成主要由四种次语言组成的语言:
    ①C
    ②Object-Oriented C++(C with Classes):构造析构函数、封装、继承、多态、虚函数等。
    ③Template C++:泛型编程
    ④STL
  • 尽量以const、enum、inline 替换 #define。
  • define没有任何封装的特性,const可以有。
  • 任何时候用define写函数,都记得给变量加括号(避免一些奇奇怪怪的问题【由优先级造成的】,但仍然避免不了++i之类的错误,如果替换的表达式出现多次,会++多次)

 

  • 尽可能用const。
  • const可以修饰文件、函数、或区块作用域中被声明为static的对象。
  • 迭代器也可以是const,表示这个迭代器初始化后,不可以再指向不同的东西,但所指向的值可以改动。这时候就不是用iterator,而是用const_iterator。
  • 函数返回对象用const防止的是这种情况:
    Rational a,b,c;
    (a*b)=c;
    operator*是重载的操作符,如果返回的不是一个const对象,当出现上面的情况时候,会再调用operator=操作符,这时候不会报错。如果返回值是const,那么此时编译会出错。

 

  • C++规定,对象成员变量的初始化动作发生在进入构造函数之前,因此构造函数中并非被初始化,而是赋值。所以可以通过C++的初始化列表来进行,这样的话就是初始化而不是赋值。这么做的效率也会更高。
test(int a,int b):A(a),B(b){

}

 

2. 构造/析构/赋值运算

  • 每个类都会有 一个或多个构造函数、一个析构函数、一个copy assignment操作符。
  • 编译器自动生成的析构函数不是虚函数,除非该类的基类析构函数声明为虚函数。
  • 某些类可能拥有拷贝构造函数和赋值操作符从实际上说不通,如果你不定义编译器也会自动定义。这时候可以把函数声明为private函数,但不实现它。
  • 有时候我们既希望声明一个抽象的类, 但又不知道把什么函数声明为纯虚函数。可以把该类的析构函数声明为一个纯虚函数。

 

  • 绝不要在构造和析构过程中调用virtual函数。
  • 基类构造过程中,虚函数的特性不能显现出来,调用虚函数还是基类的函数。

 

  • operator=返回值为reference to * this
    Widget& operator=(const Widget& rhs){
        //……
        return *this;
    }

     

3. 资源管理

  • 以对象管理资源:
    ①获得资源后立即放进管理对象(即智能指针)
    ②智能指针运用析构函数确保资源被释放
  • 智能指针均在其析构函数内用delete,而不是delete[](因此不能用vector、string等array类)
  • 使用new时:①内存被分配出来 ②针对此内存有一个(或更多)构造函数被调用
    使用delete时:①针对此内存有一个(或更多)析构函数被调用 ②内存被释放
  • 数组所用内存通常还包括“数组大小”的记录,以便知道用几次delete。(因此成对的new和delete必须使用相同的形式)
  • 避免使用typedef数组类型,而是用STL中vector等去实现。
    (因为typedef后,new 的返回是数组指针,应该用delete[],这样前后不一致,导致程序不太清晰)

 

4. 设计与声明

  • 最好使用pass by reference to const 替换 pass by value。
  • by reference方式除了提高效率,还能避免对象切割(slicing)问题。如果一个子类by value传递给函数, 而函数的声明是父类,最终调用的是父类拷贝构造函数,最终子类的特性被切割掉,只剩下父类对象。
  • C++编译器的底层,引用往往以指针实现出来。
  • 普通函数返回时尽量不要返回reference

 

5. 实现

  • 尽量把变量定义式延后到需要用到的时候。
  • C++提供4种新的转型:
    const_cast<T>(expression)
    dynamic_cast<T>(expression)
    reinterpret_cast<T>(expression)
    static_cast<T>(expression)
  • 避免返回指向内部成分的引用、指针、迭代器。会破坏该对象的private属性,外部可用利用这些对其进行修改。可以返回一个const的,解决这个问题,但这样还是存在引用等指向一个不存在的对象的风险。
  • 当异常抛出时,带有异常安全性的函数会:①不泄漏任何资源。②不允许数据败坏(例如有互斥器,抛出异常时资源计数正常)。
  • inline只是对编译器的一个申请,不是强制命令。
  • 将函数声明为inline,无法随着程序库的升级而升级,需要重新编译。如果不是inline,只需重新连接就好。

 

6. 继承与面向对象设计

  • public继承一定要是is-a关系。
  • D以public继承于B,每一个类型为D的对象也是一个类型为B的对象。B比D更一般化,D比B更特殊化。
  • 如果student类public继承于person类,任何函数如果期望获得一个person指针或引用,也都愿意接受一个student对象。
  • 绝不重新定义继承来的非虚函数。也就是说如果要在子类中改动这个函数,那么在父类中就把它声明为虚函数。

 

  • 如果父类和子类之间的继承方式是private,那么子类无法转换为一个父类对象。
  • private继承意味着implemented-in-terms-of(根据某物实现出)。

 

7. 模板与泛型编程

  • template类声明中,以下两种声明有区别吗?没有区别。但如果T也是一个template类,要用tyepname。
    template<class T>  class Widget;
    template<typename T> class Widget;

 

8. 定制new和delete

  • 需要重载new 和 delete的三个理由:
    ①用于检测运用上的错误:检测例如多次delete;delete失败等问题。
    ②强化性能:编译器的new 和 delete是比较中庸的(速度和空间上)。如果自己定义,可能提高速度和节省空间。
    ③收集使用上的统计数据

 

9. 杂项讨论

  • 不要忽视编译器的警告。
  • 父类中声明一个virtual函数,并且该函数是const成员函数;但在子类中没有声明为const,这时候子类不是重新声明,而是完全覆盖。而编译器不会给出错误,只是给出一个警告。

 

发布了107 篇原创文章 · 获赞 145 · 访问量 13万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览