抽象和类
抽象:将问题本质特征抽离出来,并根据特征描述解决方案
类型:
- 决定了数据对象所需的内存数量
- 决定如何解释内存中的每一位
- 决定可使用数据对象执行的操作或方法
类是将抽象转化成用户自定义类型的 C++ 工具
访问控制:private
(默认)、public
、protected
封装:将实现细节放在一起并将它们与抽象分开。数据隐藏是一种封装,将实现细节隐藏在私有部分中也是一种封装,将类定义和类声明放在不同文件中也是一种封装
定义在类声明中的函数将自动成为内联函数
同一个类的所有对象有自己的存储空间,但是调用同一个方法
类的构造和析构函数
构造函数
- 如果提供了一个非默认构造函数,那么构造函数会被 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 };