2019.32019.62019.3 \sim 2019.6 上了胡兰青老师的《面向对象程序设计》、说是面向对象,其实就是专讲 C++ 的面向对象。胡老师的专业能力很厉害,知识点讲得很严密。

为了应付刁钻和故意设坑的期末考,特开此文复习(虽然最后期末考还是考砸了)。

类&对象

  • 类访问修饰符

    • public,private,protected。(默认 private
  • 构造函数和析构函数

    • 离开函数时先销毁后定义的类。
    • 构造函数先调用父类,析构函数先调用子类。
  • 拷贝构造函数

    • 形如 A::A(const A &obj)。(必须加引用,一般加上 const
    • 以下两种初始化对于其他类型是等价的,对于类类型有区别:
      • A a(2) 是直接初始化,调用构造函数。
      • A b = a 是拷贝初始化,调用拷贝构造函数(等价于 A b(a))。
    • 应用场景
      • 函数的参数为类的对象时(将外层调用的值拷贝给参数)
      • 函数的返回值是类的对象时(将 return 来的值拷贝给一个临时对象)
      • 对象需要通过另外一个对象进行初始化(常见形式)
    • 默认构造函数使用 浅拷贝:对于动态成员(比如指针),指向同一位置。
    • 如果想防止默认拷贝发生,可以声明(不定义)一个私有的拷贝构造函数。这样,一旦调用编译器就会报错。
  • 静态成员

    • 无论创建多少个类的对象,静态成员都只有一个副本。
    • 静态成员
      • 我们不能把静态成员变量的初始化放置在类的定义中。
      • 在类的外部必须用 :: 重新定义一遍静态变量(分配地址)
    • 静态成员函数
      • 静态成员函数只能访问静态成员数据、静态成员函数和类外部的函数。
      • 静态成员函数没有 this 指针。
    • 注意:
      1. static 成员函数不能为 virtual
      2. 不能存在 staticnon-static 成员函数有相同的名字和参数
      3. static 成员函数不能被声明成 constvolatile或者const volatile
  • 友元函数

    • 类的友元函数是定义在类外部,但有权访问类的所有私有成员和保护成员。
    • 尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
    • 友元也可以是一个类,该类被称为友元类。
  • 内联函数 inline

    • 定义在类内的缺省函数都是内联的。
    • 类中如果未给定义默认不内联,但可以在类外定义时加上 inline

继承

  • 已有的类称为基类,新的类称为派生类

    • 形式:class <派生类名>: <继承方式> <基类名>
  • 访问和控制继承

访问 public protected private
本类 yes yes yes
派生类 yes yes no
类外 yes no no
  • 一个派生类继承了所有的基类方法,但下列情况除外:

    • 基类的构造函数、析构函数和拷贝构造函数。
    • 基类的重载运算符。
    • 基类的友元函数。
  • 可以用 A:: 在派生类里调用基类函数和数据。

    • 所以一个普通类成员函数里也可以用这个格式。
  • 继承类型

    • 公有继承(常见继承)。
    • 保护继承:基类的公有和保护成员都变为派生类的保护成员。
    • 私有继承:基类的公有和保护成员都变为派生类的私有继承。
  • 构造函数相关

    • 调用子类的构造函数时,需要在初始化列表里指定基类的构造函数。
    • 如果不指定,默认是基类的不带参构造。
    • 一定是先调用基类构造函数,再初始化派生类的数据。
    • 连续继承时,A->B->CC 不指定 A 的构造。
    • 如果是多继承,按定义时顺序触发。
  • 多继承

    • class <派生类名>:<继承方式1><基类名1>, <继承方式2><基类名2>,…
  • 基类和派生类的指针转化

    • static_cast<type-id>(expression) 上行转化安全,下行转化不安全。
    • dynamic_cast<type-id>(expression) 都安全。
      • 指针类转化错误,指针值为 nullptr
      • 引用类转化错误,会 throw 一个异常。

重载

  • 运算符重载

    • A operator <运算符>(const A &a)const
    • 后一个 const 表示 *this 里的数据不能改。
  • 强转重载

    • operator int()const{}
      • 注意开头不跟类型;少了const会编译错误

多态

  • 虚函数

    • 虚函数 是在基类中使用关键字 virtual 声明的函数。
    • 虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。
    • 在基类的构造函数和析构函数里,只能调用基类的虚函数(不会动态联编)。
    • 坑点1:函数定义必须完全一样才能触发此效果。
      • 比如基类和派生类函数中有一个加了 const,就无法重载了。
    • 坑点2:基类通过虚函数可以调用派生类,但无法获得派生类数据。
    • 如果基类指针指向派生类,如果基类函数 F1() 里调用了基类虚函数 F2()F2()也会执行动态绑定。特别的,构造函数和析构函数内部调用的F2()不会动态绑定。
    • 只有类的成员函数才能声明为虚函数。
    • 静态成员函数,内联函数和构造函数不能是虚函数。
    • 析构函数通常是虚函数。
    • 纯虚函数
      • 在虚函数声明的结尾加上 = 0
      • 派生类必须写出对应函数的定义。
      • 含有纯虚函数的类是抽象类,不能实例化。
  • 虚继承

    • 如遇到“环装继承”:形如 A->B, A->C, (B,C)->D
      • 可以在定义 BC 时用:
      • class <派生类名>: virtual <继承方式> <基类名>
      • 只生成 A 的一个副本。

模板

  • 模板的声明或定义只能在全局、命名空间或类范围内进行,不能在局部范围内。
  • 模板函数调用时参数传递不具有隐式的类型转化。
  • classtypename 这两个关键词等价。
  • C++ 对于重载函数的编译顺序
    1. 先找参数完全匹配的普通函数(非由模板实例化得到的函数)。
    2. 再找参数完全匹配的模板函数。
    3. 再找实参经过自动类型转换后能够匹配的普通函数。
  • 函数模板
    • template <class 形参名,class 形参名,...> <返回类型> <函数名>(参数列表)
  • 类模板
    • `template<class 形参名,class 形参名,…> class 类名 { … };

异常

  • catch 到后,先进行值传递,再进行子过程里的销毁
    1
    2
    3
    4
    5
    6
    7
    8
    9
       try{
    A a;
    e = Exception();
    throw e;
    }
    catch(Exception& m)
    {
    cout<<"catch exception"<<endl;
    }
    1. call A()
    2. exception() 构造一个异常对象e,局部的。
    3. exception(const exception&) 复制一个异常对象(全局的匿名对象),m引用它。
    4. ~exception() 销毁局部的异常对象e
    5. ~A()
    6. 进入 catch 模块
    7. ~exception() 销毁全局的异常对象
  • 标准库异常类
    • include<stdexcept>
  • 在函数末尾加上 throw(A) 表示该函数只会抛出 A 类的异常。
    • 如果为空,表明该函数不会抛出异常。
  • 在构造函数和析构函数里加 throw 是危险行为,因为不会进行内存回收。

词汇翻译

  • 封装 encapsulation
  • 继承 inheritance
  • 声明 declaration
  • 定义 definition