第七章 类

7.1 定义抽象数据类型

类成员

  • 必须在类的内部声明,不能在其他地方增加成员。
  • 成员可以是数据,函数,类型别名。
  • 类的const成员函数不会修改类的数据成员:void func() const;
    • const成员函数的声明和定义处都要加const
    • const成员函数不能调用本类的非const成员函数
  • 内联函数
    • 定义在类内部的函数是隐式inline函数
    • inline成员函数应该与类定义同一个头文件中
  • 可变数据成员 (mutable data member):表示数据成员永远可变
    • mutable int cnt;,这样即使在const成员函数中也可以修改cnt的值
  • [[ch07-类#返回this的成员函数|返回this的成员函数]]
  • 构造函数
    • 构造函数初始值列表:Sales_item(): units_sold(0), revenue(0.0) { } ^742596
      • 但是类内初始值必须使用等号或者花括号进行初始化
    • 当一个类没有定义任何构造函数时,编译器才会生成一个默认构造函数(也称合成的默认构造函数),使用=default要求编译器使用合成的默认的构造函数。 ^15282e

非成员函数

  • 和类相关的非成员函数,定义和声明都应该在类的外部。

7.2 访问控制与封装

  • 访问说明符(access specifiers):publicprivateprotected
  • classstruct都可以被用于定义一个类,唯一的却别在于默认访问权限:
    • class:默认成员是 priavte的。
    • struct:默认成员是 public的。

友元

  • 在类A中设置友元B(函数或类),即允许B访问A中的非共有成员
  • 语法相关:
    • 通常将友元声明成组地放在类定义的开始或者结尾,但友元不是类的成员,不受public/private的约束
    • 如果类想把一组重载函数声明为友元,需要对这组函数中的每一个分别声明。
    • 友元关系不存在传递性。
    • 把其他类的成员函数声明为友元时,必须明确指定该函数所属的类名。
    • 友元函数可以直接定义在类的内部(隐式内联),但是必须在类外部提供相应声明,并且要在调用之前进行声明
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      
      struct X
      {
          friend void fir() { /* do something */ }
          public:
              void pub();
          private:
              void pri();
      };
      
      void X::pub() { fir(); } // 错误:友元函数fri在类外必须进行声明且需要在调用之前进行声明
      void fir(); // 类X的友元函数fri,在类外进行声明
      void X::h() { f(); }     
      

7.3 类的其他特性

this

  • 每个成员函数都有一个额外的、隐含的形参thisthis总是指向调用该成员函数的对象
    • 在普通成员函数中,this是一个T *const类型的指针
    • 在const成员函数中,this是一个const T *const类型的指针(因此数据成员无法修改)
    • 静态函数中不能使用this指针
  • const对象或是const对象的指针/引用,只能调用const成员函数;否则均可
  • return *this;可以让成员函数连续调用

类类型

  • 可以声明一个类而暂时不定义它,称为前向声明,用于引入类的名字;在前向声明之后、定义之前是一个不完全类型
  • 可以定义指向不完全类型的指针或引用,也可以声明(但是不能定义)以不完全类型作为参数或返回值类型的函数

[[ch07-类#友元|友元]]

7.4 类的作用域

类型别名如果在类外已经定义过,不能在类内再次定义。

7.5 构造函数再探

  • 使用初始值列表进行初始化才是真正的初始化,在构造函数体中进行“初始化”只是赋值
    • 如果是const成员或是引用类型,则必须在构造函数初始值列表中将其初始化(圆括号初始化)例子

委托构造函数 (delegating constructor)

委托构造函数通过其他构造函数来执行自己的初始化过程 - Sale_data(): Sale_data("", 0, 0) {}

隐式的类型转换

  • 转换构造函数:如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制,因此可以叫做转换构造函数。
  • 编译器只会自动进行一步的隐式类型转换
  • 将转换构造函数声明为explicit从而抑制隐式类型转换
    • explicit关键字只对接受一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,因此也无须将其指定为explicit
    • 只在类内声明构造函数时使用explicit,在类外部定义时不应重复
    • explicit构造函数只能用于直接初始化(圆括号初始化),不能用于拷贝赋值初始化(例子

聚合类

字面值常量类

  • constexpr函数的参数和返回值必须是字面值。
  • 字面值类型:算数类型,引用,指针,聚合类
  • constexpr 构造函数

7.6 类的静态成员

  • 语法相关:
    • 静态成员可以是public或是private的
    • 静态成员可以是常量、指针、引用、类
    • 静态成员函数不包含this指针,不能声明为const成员函数
    • 定义和初始化
      • 在类内声明,类外定义并初始化。
      • 在类外定义时,不能重复 static 关键字,static 只出现在类内的声明中。
      • 只有constexpr类型的静态数据成员可以在类内定义。
  • 使用:
    • 使用作用域运算符::直接访问静态成员:r = Account::rate();
    • 也可以使用类的对象访问:r = ac.rate();
  • 特殊场景:例子
    • 静态数据成员的类型可以是不完全类型,比如可以是它所属的类类型,而普通变量不能(只能声明为所属类类型的指针或引用)
    • 可以使用静态成员变量作为函数的默认实参