课程笔记

Part1 Fundamentals

History

与可能出现的错误相比,更重要的是能做什么好的事情。Programmers needs to be trusted

  1. 演化历程

    1. C 语言是贝尔实验室的 Dennis Ritchie 在 B 语言的基础上开发出来的。C 语言作为 UNIX操作系统的开发语言而广为人知;Dennis Ritchie 和 Ken Thompson 并称为 C 语言之父
    2. 为了满足管理程序的复杂性需要,1980 年,贝尔实验室的 Bjarne Stroustrup 开始对 C语言进行改进和扩充,形成了“C with class”;
    3. 1983 年 Rick Mascitti 将其正式命名为“C++”;
    4. 1994 年制定了 ANSI C++标准的草案;
  2. C与Cpp的关系

    1. C++完全包含C语言成分(C的超集),C是建立C++的基础,同时C++还添加了OOP的完全支持
    2. C++支持C所支持的全部编程技巧
    3. 任何C程序都能被C++用基本相同的方法编写,并且具有相同的运行效率和空间
    4. C++中允许在结构和联合中定义函数,他们也具有类的基本功能
  3. C与Cpp混合变成需要注意的问题

    1. C是结构化语言,C++支持面向编程语言,所以在混合编程的时候不应该出现class等面向对象的关键字
    2. 如果要调用C语言库中的函数,需要附加关键字extern "C"
    3. C语言不支持函数重载,在 C++中 f (int, int) 与 f(int, double) 是不同的函数,都重载了 函数 f();但是在 C 语言中却被认为是相同的函数。因为在编译时,C 语言给这几个函数的 命名为 f_;而 C++命名分别为 f_int_int, f_int_double, f_,以表示区别;所以混合编程时应注意重载函数的问题;
    4. 表达式的求值顺序与副作用,不同的编辑器表达式的求值顺序也不同,可能带来副作用的问题

Data

  1. 静态类型语言与强类型语言
    1. 静态:在写程序时要声明所有变量的数据类型
    2. 强类型(上课说的):一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了,数据有更多的约束
    3. 弱类型:
  2. 基本数据类型
image.png
  1. typedef:为已有的类型定义一个同义词

表达式

  1. 影响表达式求值的因素:
    1. 优先级:不同运算符号的优先级
    2. 结合性:例如a+b-c,左结合
    3. 求值次序:括号优先,不相邻的两个运算符的运算次序由操作系统决定
    4. 类型转换约定,int x = 10, float y = 2.0 x*y得到float类型的数

语句

  1. switch语句优化,表驱动
    1. 表的行就可以是switch当中的case语句(可以用enum修饰),列就是对应的case里面需要完成的事情,比如说打印的语句,执行的函数指针,举例如下图
image.png
  1. 分类:
    1. 表达式语句
    2. IO语句
    3. 控制流语句

table driven

  1. 数组实现,Map实现
  2. 从表里查询信息来代替逻辑语句(if,case),减少比较,减少冗余代码
  3. 一目了然

image.png

函数

  1. 原则:先定义后使用,定义不允许嵌套
  2. 默认参数
  3. 执行机制:stdcall,调用者申请空间,被调用者归还,成为公共空间,调用者之后就不管了
    1. 建立被调用函数的栈空间
    2. 参数传递
      • 值传递

copy一个与传入参数一样的值,对这个值的操作不会影响到原来的值

  • 引用传递

引用就相当于传入的那个数的别名,对这个引用的所有操作,就是对原来那个值的操作

  • 指针传递

传递一个值的指针p,其中这个指针的值就是传入参数的地址,需要使用*p来操作这个指针

  1. 保存调用函数的运行状态
  2. 控制权转交被调函数
  3. 释放被调函数中局部变量占用的栈空间
  4. 回复现场:取主调函数运行状态及返回地址,释放栈空间;
  5. 函数重载
  6. inline函数
    1. 使用频率高的小段代码
    2. 编译系统将为inline函数创建一段代码,每次调用的时候,用相应的代码替换,不会产生转去转回的问题,可见它是以目标代码的增加为代价来换取时间的节省
    3. 限制:
      1. 内联函数要在函数被调用之前声明
      2. 非递归
      3. 编译系统控制
      4. inline函数只是对编译器的建议,是否对函数内联,决定权在于编译器,用inline向编译系统申请,能做编译器才会做,不能做一定会付出代价
    4. 建议:
      1. 仅对使用频率较高的小段代码使用内联,一般行数应不超过 5 行;
      2. 将内联函数的定义放到头文件中;
      3. 在内联函数中不能含有复杂的结构控制语句,如 switch、while 等;
      4. 递归函数不能用来做内联函数
    5. 问题:
      1. 增大目标代码,因为在调用时,必须在调用该函数的每个文本文件中定义
      2. 病态换页
      3. 降低指令快取装置的命中率;
  7. 静态局部变量:作用域是函数,生命周期是在整个函数调用中,不存在于栈中,static函数中

stack and heap

  1. stack:编译器自动分配与释放,存放函数的参数值,局部变量,返回地址等等
    1. 静态区(全局区):全局变量和静态变量的存储,初始化的在一块区域,未初始化的在另外一个区域
  2. heap:程序员分配和释放,存放new的一些内存块,一般一个new对应一个delete,否则等OS来自动回收

struct

  1. 主要作用就是封装
  2. C++中的结构体与类的区别:
    • class中默认的成员访问权限是private的,而struct中则是public的。
    • class继承默认是private继承,而从struct继承默认是public继承。

union

  1. 共享存储空间
  2. 同一个内存段存放几种不同类型的数据。注意:在每一瞬时只能存放其中一种,而不是同时存放多种。换句话说,每一瞬时只有一个成员起作用,其他成员不起作用。
  3. 共用体变量所占的内存长度等于最长的成员的长度。
  4. 能够访问的是共用体变量中最后一次被赋值的成员,在对一个新的成员赋值后原有的成员就失去作用。因此在引用共用体变量时应注意当前在共用体变量中起作用的是哪个成员。

指针

  1. 定义:存储变量的地址
    1
    2
    3
    4
    5
    6
    7
    typedef int* pointer
    pointer p,q;
    p = 0x080483A0;
    int x = 10;
    q = &x;
    void * t = q;//为所有指针定义一个公共接口,可以用任意类型的指针对void赋值
    cout<<*(int * t)<<endl;//需要把void指针指向其他类型的时候,需要强制类型转换
  2. 常量指针与指针常量
    1. 常量指针
      • const int *p 指针可以指向不同的变量的地址,但是指针指向的变量的内容不能改变(不能通过指针改变所指向的变量的内容),对于一个const变量,只能使用有const前缀的指针
    2. 指针常量 int * const p 必须在定义的时候初始化,只能是一开始指向的那个变量的地址,但是对应的变量可以变化
  3. 函数指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
float add(float a,float b)
{
printf("%f\n",a+b);
return a+b;
}
float minu(float a,float b)
{
printf("%f\n",a-b);
return a-b;
}
typedef float(*pfunType)(float, float);
pfunType p = &add;//定义函数指针变量
float (*pfunType[2])(float, float) = {&add, &minu};
...;
int fun1()
{
printf("this is fun1 call\n");
return 1;
}

void fun2(int k, char c)
{
printf("this is fun2 call:%d %c\n", k, c);
}
int (*pfun1)() = NULL;
void (*pfun2)(int, char) = NULL;
pfun1 = fun1; //第一种赋值方法
pfun2 = &fun2;//第二种赋值方法(推荐,因为和其他数据指针赋值方法一致)

数组

  1. 指针与数组
    1
    2
    3
    4
    5
    6
    int a[12]
    int *p = a;
    int *q = &a[0]
    bool isEqual = p == q//true
    typedef int T[2];//一个T有两个int,一个a有6个T
    T a[6];对应的就是a[6][2]
  2. 二维数组的呈现(数组存放的元素是顺序存放,然后指针与指向指针的指针有两层的关系

image.png

动态变量

  1. 用指针+new对动态变量初始化
  2. 存储在堆中,不需要的时候需要delete,注意如果指针指向的是一个东西,那就delete p,如果是一个数组,就delete []p
  3. 动态内存分配
    1. 问题:如果内存空间很大,数据却很少就会使内存空间浪费。如果内存空间不足就会出现溢出的现象。
      1
      2
      3
      4
      5
      int* p = nullptr;
      p = (int*)malloc(sizeof(int)* 25);//malloc返回void*类型的指针,需要强制类型转换,这里是申请一个25int大小的内存空间
      free(p);//最后释放空间
      void realloc(void *ptr, size_t new_size);//扩容或者缩小
      void *realloc(size_t new_size);//与malloc一样

引用

  1. 为一块已有的内存空间取一个别名,通过引用类型可以定义一个变量,它与另一个变量占用 相同的内存空间
  2. 作用:引用主要用于函数的形式参数和动态变量名
  3. 返回引用:当函数返回引用类型时,没有复制返回值。相反,返回对象本身。
  4. 限制:
    1. 用conts限定引用
      1
      void swap (const int &a, const int &b);
    2. 在堆中释放引用
      1
      2
      3
      4
      int *p = new int(100);
      int &x = *p;
      ...;
      delete &x;

问题:

  1. 当类中未自主定义构造函数,compiler会提供默认构造函数,Why?,这个时候编译器会怎么做?

  2. When构造函数、析构函数被定义为private?

    构造函数声明为private,希望通过static方法获得这个类的唯一对象,不能使用new构造一个对象

    析构函数声明为private,在栈上分配空间,类在离开作用域时会调用析构函数释放空间,此时无法调用私有的析构函数,希望对象只能创建在堆上。

  3. 友元、static成员的使用,When?

    • 类的外部不能访问类的私有成员或者protected成员

    • 同一个类的不同对象共享变量

  4. Why引入成员初始化表?

    • 构造函数的补充,减轻编译器的负担,尽量采用初始化表代表赋值动作(除非数据成员太多)

    • 类类型成员放在初始化列表里,可明显提高效率。也就是说,调用构造函数的时候,先创建对象,再进入函数体。若不使用初始化成员列表会浪费一次赋值机会,进入函数体后另外赋值。

  5. Why初始化表执行次序只与类数据成员的定义次序相关?

    • 构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。

    • 所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中。这是与声明次序有关的

    • 成员按它们在初始化列表上出现的顺序被初始化发生,编译器就要为每一个对象跟踪其成员初始化的顺序,以保证它们的析构函数以正确的顺序被调用。这会带来昂贵的开销。同一种类型的所有对象在创建(构造)和摧毁(析构)过程中对成员的处理顺序都是相同的,而不管成员在初始化列表中的顺序如何。

  6. Why引入拷贝构造函数?

    • 创建对象的时候,用同一类的对象将其初始化**

    • 一个对象以值进入一个函数体,在对象传递给某一函数之前,对象的一些属性可能已经被改变了,不是刚创建的状态,如果在产生对象副本的时候再执行对象的构造函数,那么这个对象的属性又再恢复到原始状态,所以产生对象副本的时候,用拷贝构造来创建

    • 从函数返回时复制一个对象;**

  7. = 操作符重载?

    • 有时候希望赋值运算符两边的类型可以不匹配,比如说char*类型的字符串赋值给字符串对象

    • 赋值运算符“=”只能重载为成员函数

  8. What is Late Binding?

    • 运行时才确定对象的方法或者函数。动态绑定

    • 在C++,late binding通常发生在关键字vitrual用于函数声明时,C++会创建虚拟函数表。当虚函数调用时,系统会查询虚拟函数表,来确定调用函数。这也是C++实现多态的机制。

  9. How C++implement vitural ?

    • 派生类不能改变virtual现状,基类函数没有virtual的时候派生类不能声明成有,基类有virtual的时候派生类声明不声明都一样。

    • 这里用到了VTABLE虚函数表,当编译器发现一个类有虚函数时就会建立这样一个表,一个类对应一个VTABLE,和对象无关。

    • 因为这样指针指到哪个对象都能通过这个对象对应的类的VTABLE找到想要的信息

  10. When we use virtual ?

    • 虚函数是指一个类中你希望重载的成员函数 ,当你用一个  基类指针或引用   指向一个继承类对象的时候,调用一个虚函数时, 实际调用的是继承类的版本
  11. What public继承和non-public继承means?

    • public:基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。

    • protected:保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。

    • private:基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。

  12. Why= () [] ->不能作为全局函数重载?

    • 类的this指针会被绑定到运算符的左侧运算对象,成员运算符函数的显示参数比运算符对象总数少一个

    • 编译器发现当类中没有定义这3个运算符的重载成员函数时,就会自己加入默认的运算符重载成员函数。

    • C++一个类本身对这几个运算符已经有相应的解释了

  13. When成员函数返回& ?

    • 函数体内局部对象是不能被引用的,因为函数调用完局部对象会被释放。

    • 可以返回传入参数的引用

    • 用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本

  14. When and How to 重载new、delete?

    • 比如你想对内存的分配策略进行自定义管理或者你想监测堆内存的分配情况或者你想做堆内存的内存泄露监控等

    • 想对某个类的堆内存分配的对象做特殊处理

  15. how to

    1
    2
    void * operator new (size_t size,...);//...为其他参数,返回一个void指针
    void operator delete(void* p,size_t size);

Part2 Function

OO Programming

  1. 概念:程序由多个对象组成,一个对象就是指数据和操作,用函数调用完成消息传递,通常用类表示Object
  2. 分类:
  • 面向对象:封装,继承,多态
  • 基于对象
  1. 评估:产品在规定的条件下和规定的时间内完成规定功能的能力

构造函数

编译器自动写的类:

1
2
3
4
5
6
7
8
9
class Empty { };
class Empty {
Empty();//构造
Empty(const Empty&);//拷贝构造
~Empty();//析构
Empty& operator=(const Empty&); //赋值操作符
Empty *operator &();//取地址符号 Empty *pe1 = &e1;
const Empty* operator &() const; //const版本取地址符 const Empty *pe2 = &e2;
};
  1. 对象的初始化
  2. 定义:
    1. 与类同名,没有返回类型,自动调用,可重载
  3. 默认构造函数:当类中没有提供构造函数的时候,编译器按照需要提供
    1. 数据成员都是基本数据类型,编译器不会合成一个默认构造函数。这是没有必要的
    2. 编译器什么时候
      1. 类成员中有成员是类对象,并且该成员的类含有默认构造函数,那么C++编译器会给这个类也生成一个默认构造函数,用来调用其成员对象的构造函数,完成该成员的初始化构造。需要强调的是,如果这个成员的类也没有给出默认构造函数,那么C++编译器也不会帮你生成该类的默认构造函数。
      2. 类的基类有默认构造函数。生成该派生类的默认构造函数,以调用基类的默认构造函数,完成基类的初始化。如果基类没有提供这个默认构造的函数,那么C++编译器也不会为派生类生成默认的构造函数(这里包括两层意思,第一,基类没有任何形式构造函数;第二,基类存在其他形式的非默认构造函数,当然了,这种类型就是编译不过的,道理很明显)。
      3. 类中存在虚函数,生成默认构造函数,以初始化虚表(虚函数表vftable)。
      4. 存在虚基类,那么C++编译器会为你生成默认构造函数,以初始化虚基类表(vbtable)。
  4. 成员初始化表
    1. 构造函数的补充
    2. 先于构造函数体,按照类数据成员声明次序,减轻编译器的负担,尽量采用初始化表代表赋值动作(除非数据成员太多)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      class A{
      int x;
      int &z;
      public:
      A():y(1),z(x),x(0){x=100}//最终x会是100
      A(int m){x = m}
      };
      Class B{
      int x;
      A a;
      public:
      B(){ x = 0; }//调用B()和A()
      B(int x1) { x = x1; }//B(int) and A()
      B(int x1, int x2): a(x2) { x = x1;}//B(int,int) and
      }

析构函数

  1. 对象消亡的时候,系统自动调用
  2. 问题:
    1. 但默认生成的析构函数只能释放非内存资源, 无法释放通过 new 或 malloc 进行申请的空间, 所以需要显式定义析构函数对这些申请的空间进行释放, 避免造成内存泄露。
  3. private:想在堆中创建对象,必须找到一种方法禁止以调用“new”以外的其它手段建立对象。由于非堆对象(non-heap object)栈对象在定义它的地方被自动构造,在生存时间结束时自动被释放,所以只要禁止使用隐式的析构函数,就可以实现这种限制。
    1. 禁止用户对此类型的变量进行重复定义。//不确定
    2. 只能在堆上声明
    3. 禁止用户在程序中使用delete删除此类型对象。

构造拷贝函数

  1. 创建对象的时候,用同一类的对象将其初始化
  2. 自动调用
  3. 默认拷贝构造函数:复制所有的值
    1. 如果包含成员对象的拷贝,就会递归调用成员对象的拷贝构造函数
  4. 自定义拷贝构造函数:按需要拷贝
    1. 如果包含成员对象的拷贝,就会递归调用成员对象的默认拷贝构造函数
  5. 没有任何声明,只会隐式调用默认的拷贝构造函数

动态对象

  1. 使用new和delete在堆中创建
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    int* int ptr = new int;
    Car* carPtr = new Car("Benz");
    A *p = (A*) malloc(sizeof(A));
    A *p = new A[100];
    delete []p;
    /* 动态二维数组 */
    const int ROWS = 3; const int COLUMNS = 4;
    char **chArray2;
    chArray2 = new char* [ ROWS ];
    for (int row = 0; row < ROWS; row++ )
    chArray2[ row ] = new char[ COLUMNS ];
    delete [ ] chArray2[ row ];//删除这些指针指向的对象
    chArray2[ row ] = nullptr;//指针设置为nullptr
    delete[] chArray2;
    chArray2 = nullptr;

    重载,重写,覆盖

成员

  1. const成员
    1. const成员函数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      class A{
      int x,y;
      int &z;
      ...
      }
      ...;
      void A::f()
      { x=1;y=1;}//void f( A * const this);
      void A::show() const
      { cout <<x << y;}//void show(const A* const this);
      void A::g()const
      {z++;}
      ...;
      const A a(0,0,x);
      a.f();//不能调用,因为A是const类型,本身不能修改
      a.show();//可以调用
      a.g();//可以跑
  2. static成员:同一个类的不同对象共享变量
    1. 成员函数,只能存取静态成员变量,调用静态成员函数
      1. 通过对象使用 A a;a.f(); 
      2. 通过类使用 A::f() 

友元

  1. 类外部不能访问该类的private成员,解决
    1. 通过public方法访问
    2. 友元访问
  2. 分类:
    1. 友元函数
    2. 友元类
    3. 友元类成员函数
  3. 使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void func() ; 
    class B;
    class C
    { ...
    void f(); };
    class A {...
    friend void func(); //友元函数,前面不用生命
    friend class B; //友元类,前面的声明不是必须的
    friend void C::f();//友元类成员函数,前面需要声明
    };
  4. 注意
    1. 友元不具有传递性
    2. 友元函数或者友元类成员函数中的参数必须提前声明
    3. image.png

继承

  1. 继承方式
    1. public
    2. private:把原来的public换成private,但是原来的private还是不能访问,没有权限把派生类转化成基类类型
    3. protected
  2. 初始化:基类和派生类共同完成
  3. 执行次序:
    1. 构造函数:
      1. 基类
      2. 派生类对象成员类的构造函数
      3. 派生类的构造函数
    2. 析构函数:
      1. 与构造函数相反
  4. 调用:
    1. 默认执行基类的默认构造函数
    2. 如果要执行基类的自定义构造函数,就要在派生类的构造函数的成员初始化表中指出

虚函数

  1. 前期绑定,cpp默认
    1. 编译时刻
    2. 根据对象的静态类型,即只看形参的类型
    3. 效率高
  2. 动态绑定
    1. 运行时刻判断
    2. 根据对象的实际类型动态决定
    3. 灵活性高
  3. 虚函数
    1. 提供动态绑定,根据实际引用和指向的对象的类型
  4. 后期绑定的实现:虚函数表:一个可变的vtable存这个类的虚函数的地址,指向另外一片区域

image.png

  1. 注意
    1. 静态调用后还可以发生动态调用:非虚接口
    2. 动态调用后将不能继续静态调用

image.png

  1. 构造函数中只会调用正在构造的类的版本的函数
  2. 如果把上图的h改为virtual,里面的东西都调B的
  3. 绝对不要重新定义继承而来的缺省参数值,
  4. override,final
    1. override:必须在父类中是虚函数,并且函数参数都要相同,父类中是const,子类也需要是const
    2. final:父类当中说明了final,就不能override
  5. 纯虚函数:形如 vitual int f() = 0; 
    1. 只给出声明,不给出实现
  6. 抽象类:至少包含一个纯虚函数
    1. 不能用于创建对象
    2. 派生类需要提供抽象基类的所有成员函数的实现
  7. 抽象工厂
  8. 虚析构函数

因为析构函数没有声明为虚函数,所以只会调用B的析构函数
image.png

多继承

  1. 看不懂
  2. 如果直接基类有公共的基类,则该公共基类中的成员变量 在多继承的派生类中有多个副本,解决名冲突:基类名::基类成员名

image.png
image.png

Part3 Advance

多态

  1. 同一论域中一个元素可有多种解释,提高语言灵活性
  2. 不影响代码而替换行为
  3. 程序设计语言:
    1. 函数重载,一名多用
      1. 在同一个作用域中,相同的标识符可以用于定义不同的函数,但要求这些函数应拥有不同的参数(参数类型或个数)
      2. 函数重载主要用于定义多个功能相同而参数不同的函数
    2. 类属,template
      1. 编译的时候多态
    3. 程序设计:虚函数
      1. 运行时多态
  4. 多态性可以严格的分为四类:重载多态,强制多态,包含多态,和参数多态,前面两种统称 为专用多态,而后面两种也称为通用多态

操作符重载

  1. 根本上来说,也是函数重载
    1. 名字相同,参数不同导致重载
    2. 静态绑定
    3. 为了改变操作符语义,对于自定义数据类型或者built_in类型有新的操作符
    4. 提高可用性和可拓展性
  2. 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Complex
{ double real, imag;
public:
Complex() { real = 0; imag = 0; }
Complex(double r, double i) { real = r; imag = i; }
friend Complex operator + (Complex& c1, Complex& c2);
};
Complex operator + (Complex& c1, Complex& c2){//Complex的友元函数
Complex temp;
temp.real = c1.real + c2.real;
temp.imag = c1.imag + c2.imag;
return temp;
}
Complex a(1,2),b(3,4),c;
c = a + b;
...;
enum Day { SUN, MON, TUE, WED, THU, FRI, SAT};
Day& operator++(Day& d){
return d= (d==SAT)? SUN: Day(d+1);
}
ostream& operator << (ostream& o, Day& d) {
//为什么不能作为成员函数:
switch (d){
case SUN: o << "SUN" << endl;break;
...;
}
return o;}//返回同一个引用,支持连续使用

class Counter
{ int value;
public:
Counter() { value = 0; }
Counter&operator++()//++a {
value++;
return *this; }
Counter operator ++(int) //a++ {
Counter temp=*this;
value++;
return temp; }
}

为什么<<与>>不能作为成员函数?
如果是重载双目操作符(即为类的成员函数),就只要设置一个参数作为右侧运算量,而左侧运算量就是对象本身
而 >>  或<< 左侧运算量是 cin或cout 而不是对象本身,所以不满足后面一点。

  1. 不可重载的操作符:

    1. “.”操作符,我们需要保证访问成员的功能不能被改变,否则引起混淆,不知道访问什么成员
    2. “.*”,同上
    3. “::”,域运算符号,只是名称域的解析
    4. “?”,操作符重载是一个函数调用,所以重载过后,可能?的条件条件都会执行
    5. “sizeof”,参数是类型,而不是变量或者表达式
  2. 双目操作符

    1. 类成员函数重载

    2. this隐含

    3. 使用 <ret type> operator # (<arg>);<class name> a,b; a#b; a.operator#(b); 

    4. 全局函数

      1. 注意友元
      2. 有些时候返回的新建的对象的引用,就会出现:if ((ab) ==(ba))
      3. =,(),[],->不能作为全局函数重载
    5. 永远不要重载&& 和 ||,重载过后,两个都会执行,短路现象没了

  3. 单目操作符

  4. 特殊操作符

    1. =的重载,相当于新一轮的拷贝构造,在构造完后的拷贝构造
1
2
3
4
5
6
7
8
A& operator = (A& a)
//不返回const,因为有可能会返回后马上调用函数,改变调用对象的状态
{//这里没有考虑避免自我赋值的问题(可以开一个新的指针指向原来的数组)
x = a.x; y = a.y;
delete []p;
p = new char[strlen(a.p)+1]; strcpy(p,a.p);
return *this;
};
  1. []的重载,可以赋值和不可以赋值,看返回的是引用还是新的值
  2. ()的重载
  3. new

image.png

  1. delete

image.png

模版 template

  1. 源代码复用机制,一名多用
  2. 参数化模块,对不同类型的数据实施相同的操作
  3. 多态的一种形式
  4. C++
    1. 类属函数
    2. 类属类
  5. 类属函数:
    1. 同一函数对不同类型的数据完成相同的操作
    2. 方法
      1. 万能指针,可读性差,实现复杂
      2. template
        • 函数模版定义了一类重载的函数
        • 编译系统自动实例话函数模版
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          template <typename T>
          void sort(int A[], unsigned int num){
          for (int i=1; i<num; i++){
          for (int j=0; j<num-i; j++){
          if (A[j] > A[j+1])
          A[j] = A[j+1]; A[j+1] = t;
          }
          }
          }//必须重载操作符>
          ...;
          template <class T>
          T max(T a, T b){
          return a>b?a:b;
          }
          double max(int a, double b)
          { returna>b?a:b;}
  6. 类属类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <class T>
    class Stack{
    T buffer[100];
    public:
    void push(T x);
    T pop();
    };
    ...;
    Stack <int> st1;
    Stack <double> st2;//必须显式实例化

异常

  1. 错误
    1. 语法错误
    2. 逻辑错误
  2. 异常
    1. 运行环境
    2. 异常处理
  3. 异常处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    try{ 
    f();
    } catch ( int ) { ... }
    catch ( double ) { ... }
    catch (char * ) { ... }
    //如果没有被catch,那么逐层往上面仍,直到系统自带的abort
    ...;

    void f(MyExceptionBase e){
    throw e;
    }
    //应该用引用,避免被切片
    int main() {
    MyExceptionDerived e;
    try {
    f(e);
    } catch (MyExceptionDerived &e) {
    cout << "MyExceptionDerived" << endl;
    } catch (MyExceptionBase &e) {
    cout << "MyExceptionBase" << endl;//最后会打印这个,因为派生类被切片了
    }
    }
  4. auto_ptr

在构造的时候获取资源,在析构的时候释放资源
如果在构造函数中产生了异常,那么析构函数将不会调用,这样就会造成资源泄漏的隐患。比如,如果该类有2个成员变量,指向两个资源,在构造函数中申请资源A成功,但申请资源B失败,则构造函数失败,那么析构函数不会被调用,那么资源A则泄漏。
为了解决这个问题,我们可以利用auto_ptr取代普通指针作为成员变量,这样首先调用成功的成员变量的构造函数肯定会调用其析构函数,那么就可以避免资源泄漏问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Template <class T> 
class auto_ptr{
public:
auto_ptr(T *p=0):ptr(p){}
~auto_ptr(){ delete ptr; }
T* operator->() const { return ptr;}
T& operator *() const { return *ptr; }
private:
T* ptr;
};
void processAdoptions(istream& dataSource) {
while (dataSource){
auto_ptr<ALA> pa(readALA(dataSource));
pa->processAdoption();
}
}
//pa出栈的时候肯定会被析构,不会有内存泄露

IO处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CPoint2D
{
double x, y;
public:
...
virtual void display(ostream& out){
out<<x<<‘,’<<y<<endl;
}
};
ostream& operator << (ostream& out, CPoint2D &a) {
a.display(out); return out;
}
class CPoint3D: public CPoint2D
{
double z;
public:
...;
void display(ostream& out){
CPoint2D::display();
out << ‘,’<< z << endl;
}
};

重定向

cin重定向到文件读取

1
2
3
ifstream in(“in.txt”);
streambuf *cinbuf = cin.rdbuf(); //save old buf
cin.rdbuf(in.rdbuf()); //redirect cin to in.txt!

cout重定向到文件中输出

虚构造

利用父类的虚函数,动态构造对象

1
2
3
4
5
NewsLetter::NewsLetter(constNewsLetter&rhs){ 
for ( list<NLComponent *>::iterator it=rhs.component.begin();
it != rhs.component.end(); ++it )
component.push_back((*it)->clone());
}

More Effective C++

详解1
详解2

  • 条款1:仔细区别 pointers 和 references;

    Distinguish between pointers and references.

指针是对象的地址,引用是对象的别名必须有初值,不能被改变,指针可以指向 null,引用不允许指向null。引用类型比指针类型要安全。

  • 条款2:最好使用 C++ 转型操作符

    Prefer C++style casts.

使用新式转型符比较容易辨析

  • 条款3:绝对不要以多态(polymorphically)方式处理数组

    Never treat arrays polymorphically.

    数组传递只有引用传递和首元素地址传递,如果采用多态,可能数组类型是BaseClass,派生类的sizeof大于BaseClass,那么在数组的array[i]的时候很可能出错

  • 条款4:非必要不提供 default constructor

    Avoid gratuitous default constructors.

    添加无意义的默认构造函数显得画蛇添足,也会影响效率。

操作符(Operators)

  • 条款5:对定制的“类型转换函数”保持警觉

    Be wary of user-defined conversion functions.

  • 条款6:区别 increment/decrement 操作符的前置(prefix)和后置(postfix)形式

    Distinguish between prefix and postfix forms of increment and decrement operators.

  • 条款7:千万不要重载&&,和, 操作符

    Never overload &&,|| , or ,

    不管你多么努力,都无法令其行为像它们应有的那样,所以千万不要重载它们

  • 条款8:了解各种不同意义的 new 和 delete

    Understand the different meanings of new and delete

    1
    2
    3
    4
    string *p = new string("Hello");
    void *p = operator new(sizeof(string));//只是分配空间
    delete p;
    delete []p;

    异常(Exceptions)

  • 条款9:利用 destructors 避免泄漏资源

    Use destructors to prevent resource leaks.

    坚持一个原则,将资源封装在对象内,这样即使发生异常,局部对象在自动销毁的时候也可以调用其析构函数释放资源,避免泄漏。

  • 条款10:在 constructors 内阻止资源泄漏(resource leak)

    Prevent resource leaks in constructors.

    C++ 只会析构 已构造完成 的对象。也就是说在构造函数中发生异常的话,析构函数是不会执行的。
    最好是使用 auto_ptr 对象来取代 pointer class members。

  • 条款11:禁止异常(exceptions)流出 destructors 之外

    Prevent exceptions from leaving destructors.

  • 条款12:了解“抛出一个 exception”与“传递一个参数” 或“调用一个虚函数”之间的差异

    Understand how throwing an exception differs from passing a parameter or calling a virtual function.

  • 条款13:以 by reference 方式捕捉 exceptions

    Catch exceptions by reference.

  • 条款14:明智运用 exception specifications

    Use exception specifications judiciously.

  • 条款15:了解异常处理(exception handling)的成本

    Understand the costs of exception handling.
    效率(Efficiency)

  • 条款16:谨记 80-20 法则

    Remember the 80-20 rule.

  • 条款17:考虑使用 lazy evaluation(缓式评估)

    Consider using lazy evaluation.

  • 条款18:分期摊还预期的计算成本

    Amortize the cost of expected computations.

  • 条款19:了解临时对象的来源

    Understand the origin of temporary objects.

  • 条款20:协助完成“返回值优化(RVO)”

    Facilitate the return value optimization.

  • 条款21:利用重载技术(overload)避免隐式类型转换(implict type conversions)

    Overload to avoid implicit type conversions.    

  • 条款22:考虑以操作符复合形式(op=)取代其独身形式(op)

    Consider using op= instead of stand-alone op.

  • 条款23:考虑使用其他程序库

    Consider alternative libraries.

  • 条款24:了解 virtual functions、multiple inheritance、virtual base classes、 runtime type identification 的成本

    Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI.
    技术(Techniques, Idioms, Patterns)

  • 条款25:将 constructor 和 non-member functions 虚化

    Virtualizing constructors and non-member functions.

  • 条款26:限制某个 class 所能产生的对象数量

    Limiting the number of objects of a class.

  • 条款27:要求(或禁止)对象产生于 heap 之中

    Requiring or prohibiting heap-based objects.

  • 条款28:Smart Pointers(智能指针)

  • 条款29:Reference counting(引用计数)

  • 条款30:Proxy classes(替身类、代理类)

  • 条款31:让函数根据一个以上的对象类型来决定如何虚化

    Making functions virtual with respect to more than one object.
    杂项讨论(Miscellany)

  • 条款32:在未来时态下发展程序

    Program in the future tense.

  • 条款33:将非尾端类(non-leaf classes)设计为 抽象类(abstract classes)

    Make non-leaf classes abstract.

  • 条款34:如何在同一个程序中结合 C++ 和 C

    Understand how to combine C++ and C in the same program.

  • 条款35:让自己习惯于标准 C++ 语言

    Familiarize yourself with the language standard.