本篇内容包括:
- 过程性编程和面向对象编程
- 类概念
- 如何定义和实现类
- 公有类访问和私有类访问
- 类的数据成员
- 类方法(类函数成员)
- 创建和使用类对象
- 类的构造函数和析构函数
- const成员函数
- this指针
- 创建对象数组
- 类作用域
- 抽象数据类型
第10章 对象和类
下面是最重要的OOP特性:
- 抽象
- 封装和数据隐藏
- 多态
- 继承
- 代码的可重用性
- 为了实现这些特性并将它们组合在一起,C++ 所做的最重要的改进是提供了类。
10.1 过程性编程和面向对象编程
- 采用
OOP
方法时,首先从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作。完成对接口的描述后,需要确定如何实现接口和数据存储。最后,使用新的设计方案创建出程序。
10.2 抽象和类
10.2.1 类型是什么
- 指定基本类型完成了三项工作
- 决定数据对象需要的内存数量
- 决定如何解释内存中的位
- 决定可使用数据对象执行的操作或方法
10.2.2 C++ 中的类
- 通常,C++ 程序员将接口(类定义)放在头文件中,并将实现(类方法的代码)放在源代码文件中。
- 防止程序直接访问数据被称为数据隐藏
- 数据项通常放在私有部分
- 不必在类声明中使用关键字private,因为这是类对象的默认访问控制
10.2.3 实现类成员函数
- 类方法的完整名称中包括类名。形如
Stock::update()
的是函数的限定名(qualified name
),而update()
是函数的缩写(非限定名,unqualified name
),只能在类作用域中使用 - 定义位于类声明中的函数都将自动成为内联函数;当然,也可以在外部定义并加上
inline
限定符- 内联函数要求在每个使用它们的文件中都对其定义,所以最好将内联定义放在定义类的头文件中
10.3 类的构造函数和析构函数
- 与
struct
不同,下面的列表初始化是不被允许的,因为这相当于访问了私有变量。构造函数默认好像只有拷贝和无参两种。1
2class ABC { int a, b, c; };
ABC x{1, 2, 3};
10.3.2 使用构造函数
- 构造函数可以和
new
连用:1
Foo *bar = new Foo(2, 3, 3);
- 下面两条语句有根本性的差别第一行是初始化,可能会创建临时对象也可能不会;第二行是赋值,一定会创建临时对象
1
2Foo bar1 = Foo(2, 3, 3);
bar2 = Foo(2, 3, 3); - 只要与某个构造函数的参数列表匹配,就可以使用列表初始化
- 接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值(当然,如果有一个接受一个
int
的构造函数和一个接受一个long
的构造函数,这种语法是不可用的)1
Classname object = value;
1
2void Foo(Classname object);
Foo(value);
10.3.3 默认构造函数
- 默认构造函数不初始化成员
- 当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数
10.3.4 析构函数
- 有些编译器可能会过一段时间才删除临时对象,因此析构函数的调用有可能会延迟
10.3.5 const
成员函数
- 下面第一段代码不一定能够过编,因为
show()
方法无法确保调用对象不被修改。第二段是解决方案1
2const Foo bar = Foo(2, 3, 3);
bar.show();1
2
3void Foo::show() const {
/* details omitted */
} - 只要类方法不修改调用对象,就应将其声明为
const
10.5 对象数组
- 可以用构造函数来初始化数组元素。在这种情况下,必须为每个元素调用构造函数
1
2
3
4
5Foo bar[3] {
Foo(2, 3, 3),
Foo(2, 3, 3),
Foo(2, 3, 3)
};
10.6 类作用域
- 类作用域意味着不能从外部直接访问类的成员。所以,要调用公有成员函数,必须通过对象
10.6.1 作用域为类的常量
const
常量不能出现在类中。因为声明类只是描述的对象的形式,并没有创建对象。因此,在创建对象前,将没有用于储存值的空间。- 有三种方法可以解决这个问题
- 在类中声明一个枚举,但这种方式对类型有限制
1
class Foo { enum { bar = 12 }; };
- 使用
static
。这个常量与别的静态变量储存在一起,被所有对象共享。注意,初始化只能在类声明外(一般写在方法文件中而不是类声明文件中,防止出现多个初始化副本)(如果常量类型是整形则可以在类声明内初始化)1
2class Foo { static const double bar; };
const double Foo::bar = 12; - 在构造函数中初始化常量(详见 12.7.1)
- 在类中声明一个枚举,但这种方式对类型有限制
10.6.2 作用域内枚举
- 为避免不同的枚举定义中的枚举量重名,C++11 提供一种作用域为类的新枚举(这里面,
class
和struct
关键字效果相同)(不仅在类中可以定义,别的地方也可以)1
2
3
4enum class egg { small, medium, large, jumbo };
enum struct t_shirt { small, medium, large, xlarge };
egg choice = egg::large;
t_shirt Floyd = t_shirt::large; - 为提供作用域内枚举的类型安全,这种特殊的枚举形式不支持隐式地转换为整型
- 对于作用域枚举,默认情况下底层类型为
int
。当然,也可以手动调整,但必须是一个整型1
enum class alphabet : char { a = 'a', b, c, d };
10.7 抽象数据类型
- 抽象数据类型(
abstract data type
,ADT
)以通用的方式描述数据类型,而没有引入语言或实现细节。比如栈。