《C++ Primer Plus》10. 对象和类

抽象和类

抽象:将问题本质特征抽离出来,并根据特征描述解决方案

类型:

  • 决定了数据对象所需的内存数量
  • 决定如何解释内存中的每一位
  • 决定可使用数据对象执行的操作或方法

类是将抽象转化成用户自定义类型的 C++ 工具

访问控制:private(默认)、publicprotected

封装:将实现细节放在一起并将它们与抽象分开。数据隐藏是一种封装,将实现细节隐藏在私有部分中也是一种封装,将类定义和类声明放在不同文件中也是一种封装

  • 定义在类声明中的函数将自动成为内联函数

  • 同一个类的所有对象有自己的存储空间,但是调用同一个方法

类的构造和析构函数

构造函数

  • 如果提供了一个非默认构造函数,那么构造函数会被 delete
  • 如果类的某一个字段没有默认构造函数,那么该类就没有默认构造函数,除非我们自己定义一个
class A {
    int x;
public:
    explicit A(int x) {
        this->x = x;
    }
};

class B {
    A a;
};

上述代码编译会报错,因为 B 的默认构造函数已经被 delete:

error C2280: “B::B(void)”: 尝试引用已删除的函数

对于下面的代码:

Stock stock1(p1, p2, p3);
Stock stock2 = Stock(p1, p2, p3);
stock1 = Stock(p1, p2, p3);

第一行就是正常创建对象

对于第二行,C++ 标准允许使用两种方法来执行:

  • 和第一行代码一样,创建 Stock 对象,然后初始化
  • 调用构造函数创建一个临时对象,然后将临时对象复制到 stock2,并丢弃临时对象

对于第三行,它会创建临时对象,赋值给 stock1 然后丢弃它

C++ 有一种继承构造函数的机制:

class Base {
public:
    Base();
    Base(int a);
    Base(int a, int b);
    Base(int a, int b, int c);
};

class Derived {
public:
    using Base::Base;
};

等价于:

class Derived {
public:
    Derived() : Base();
    Derived(int a) : Base(a);
    Derived(int a, int b) : Base(a, b);
    Derived(int a, int b, int c) : Base(a, b, c);
};

析构函数

析构函数调用时机:

  • 静态存储类对象:程序结束时
  • 自动存储类对象:执行完代码块时
  • 通过 new 创建的对象:当使用 delete 释放内存时

C++ 11 的列表初始化

需要与某个构造函数的参数列表匹配

C++ 11 提供了叫做 std::initialize_list 的类,可以表示任意长度的列表

单参数构造函数

如果构造函数只有一个参数(或者只有一个参数不提供默认值,其他都提供了默认值),则将对象初始化为一个与参数的类型相同的值时,该构造函数将被调用,例如:

class A {
    int x;
public:
    A(int x) {
        this->x = x;
    }
};

int main () {
    A a = 2;
    A b(2);
    A c = A(2);
    return 0;
}

上面的代码中,三种方法基本等价

使用 explicit 关键字可以关闭这种构造方法

类作用域

第 9 章介绍了全局作用域和局部作用域,C++ 引入了新的作用域:类作用域

类中定义的名称的作用域都为整个类。在类外,必须通过对象来访问公共成员,使用 :: 显式调用公共静态方法

作用域为类的常量

这种常量的作用域为类,只有在类存活时它才存活

由于声明类仅仅描述了对象的形式,并没有创建对象,所以在创建对象前并没有用于存储值的空间,所以并不能在类中直接定义常量:

class A {
    const int x = 1;
};

上面代码如果使用 -std=c++98 并且开启 -Werror 会报错:

error: non-static data member initializers only available with '-std=c++11' or '-std=gnu++11' [-Werror=c++11-extensions]
    5 |     const int x = 1;
      |                 ^
cc1plus.exe: all warnings being treated as errors

在 C++ 98 标准里,只有 static const 声明的整型成员能在类内部初始化,并且初始化值必须是常量表达式,确保了初始化操作可以在编译时期进行

在 C++ 11 中,允许非静态(non-static)数据成员在其声明处(在其所属类内部)进行初始化,就是这样:

class A {
public:
    int a;
    A() : a(7) {}
};

此时,是可以像上面的代码中定义类常量的,常量会在构造函数之前进行初始化

但是,如果使用这种常量定义数组:

class A {
    const int x = 1;
    const int a[x];
};

仍然会报错:

error: invalid use of non-static data member 'A::x'
    5 |     const int a[x];
      |                 ^

作用域内枚举

传统的枚举存在问题:

enum egg { Small, Medium, Large };
enum t_shirt { Small, Medium, Large };

这不能通过编译,因为位于相同的作用域内(全局作用域)

在 C++ 11 中,提供了一种新的枚举:

enum class egg { Small, Medium, Large };
enum class t_shirt { Small, Medium, Large };

并且 C++ 11 提高了作用域内枚举的类型安全,常规的枚举将自动转换成整型,但是作用域内枚举并不能隐式转换为整型

作用域内枚举可以选择不同的底层类型,如:

enum class : short pizza { Small, Medium, Large };