C++ Primer 第五版 第七章 类

类的基本思想是数据抽象 (data abstraction)和封装 (encapsulation)。数据抽象是一种依赖于接口 (interface)和实现(implementation)分离的编程(以及设计)技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。

封装实现了类的接口也和实现的分离。封装后的类隐藏了它的实现细节,类的用户只能使用接口而无法访问实现部分。

作为一个设计良好的类,既要有直观且易于使用的接口,也必须具备高效的实现过程。

一、定义抽象数据类型

成员函数的声明必须在类的内部,它的定义可以在类的内部也可以在类的外部。定义在类内部的函数是隐式的inline函数。

const成员函数

默认情况下,this的类型是指向类类型非常量版本的常量指针。

紧跟在参数列表后面的const表示this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数(const member function)。

类本身就是一个作用域。类的成员函数的定义嵌套在类的作用域之内。

编译器分两步处理类:首先编译成员的声明,然后才轮到成员函数体(如果有的话)。因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序。

在类的外部定义成员函数

类外部定义的成员的名字必须包含它所属的类名:

IO类型属于不能被拷贝的类型,因此我们只能通过引用来传递它们。

1. 构造函数

类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。

构造函数的名字和类名相同。构造函数没有返回类型。

不同于其他成员函数,构造函数不能被声明成const的。

默认构造函数

类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数。默认构造函数无须任何实参。

如果我们的类没有显式地定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数。编译器创建的构造函数又被称为合成的默认构造函数(synthesized default constructor)。

一旦我们定义了一些其他的构造函数,那么除非我们再定义一个默认的构造函数,否则类将没有默认的构造函数。

构造函数初始值列表

构造函数的函数体是空的,因为构造函数的唯一目的就是为数据成员赋初值。

2. 拷贝、赋值和析构

很多需要动态内存的类能(而且应该)使用vector对象或者string对象管理必要的存储空间。使用vector或者string的类能避免分配和释放内存带来的复杂性。

二、访问控制与封装

使用访问说明符加强类的封装性:

一般来说,作为接口的一部分,构造函数和一部分成员函数应该定义在public说明符之后,而数据成员和作为实现部分的函数则应该跟在private说明符之后。

使用class或struct关键字:如果我们使用struct关键字,则定义再第一个访问说明符之前的成员是public的;相反,如果我们使用class关键字,则这些成员是private的。

1. 友元

友元声明只能出现在类定义的内部,最好在类定义开始或结束前的位置集中声明友元。

友元的声明仅仅只定了访问的权限,在友元声明之外再专门对函数进行一次声明。

为了使友元对类的用户可见,我们通常把友元的声明与类本身放置在同一个头文件中(类的外部)。

三、类的其他特性

用来定义类型的成员必须先定义再使用,这一点与普通成员有所区别,因此类型成员通常出现在类开始的地方。

1. 可变数据成员

在变量的声明中加入mutable关键字,使之成为可变数据成员。

一个可变数据成员永远不会是const,任何成员函数,包括const函数在内都能改变它的值。

2. 类内初始值

含有指针数据成员的类一般不宜使用默认的拷贝和赋值操作。

3. 返回*this的成员函数
3. 类类型

对于一个类来说,在我们创建它的对象之前该类必须被定义过,而不能仅仅被声明。

4. 友元再探

类还可以把其他的类定义成友元,也可以把其他类(之前已定义过的)成员函数定义成友元

(这样其他类就可以访问该类的私有成员)。此外,友元函数能定义在类的内部,这样的函数是隐式内联的。

四、类的作用域

一个类就是一个作用域,当我们在类的外部定义成员函数时必须同时提供类名和函数名。在类的外部,成员的名字被隐藏起来了。

1. 名字查找与类的作用域

类的定义分两步处理:

类型名要特殊处理

在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义改名字:

五、构造函数再探
1. 构造函数初始值列表

构造函数中对数据成员执行赋值操作,和列表初始化不同(初始化和赋值的差异)

构造函数初始值列表只说明用于初始化成员的值,而不限定初始化的具体执行顺序。

使用默认实参的构造函数:不提供实参也能调用这个构造函数,实际上为类提供了默认构造函数。

2. 委托构造函数

一个委托构造函数可以使用它所属类的其他构造函数执行它自己的初始化过程。

3. 隐式的类类型转换
只允许一步类型转换
抑制构造函数定义的隐式转换
explicit构造函数只能用于直接初始化
标准库中含有显式构造函数的类
4. 聚合类

聚合类(aggregate class)使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。

当一个类满足如下条件时,我们说它是聚合的:

我们可以用一个花括号括起来的成员初始值列表初始化聚合类的数据成员,初始值的顺序必须与声明的顺序一致。

5. 字面值常量类

一个字面值常量类必须至少提供一个constexpr构造函数。

constexpr构造函数可以声明成=default的形式。否则,constexpr构造函数就必须既符合构造函数的要求(意味着不能包含返回语句),又符合constexpr函数的要求(意味着它能拥有的唯一可执行语句就是返回语句)。所以constexpr构造函数体一般来说应该是空的。我们通过前置关键字constexpr就可以声明一个constexpr构造函数了:

constexpr构造函数必须初始化所有数据成员。

六、类的静态成员
声明静态成员

在成员的声明之前加上关键字static使得其与类关联在一起。静态成员不是任意单独对象的组成部分,而是由该类的全体对象所共享。

静态成员作用域位于类的范围之内,避免与其他类的成员或者全局作用域的名字冲突;可以设私有成员。

定义静态成员
静态成员的类内初始化

静态成员独立于任何对象

我们可以使用静态成员作为默认实参。

相关推荐
一点媛艺35 分钟前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风39 分钟前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生2 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功2 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨2 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程2 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
UestcXiye3 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
Chrikk3 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*3 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue3 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang