C++Primer_ch7

第7章 类

7.1 定义抽象数据类型
7.2 访问控制与封装
7.3 类的其他特性
7.4 类的作用域
7.5 构造函数再探
7.6 类的静态成员


7.1 定义抽象数据类型

  • 类的基本思想是数据抽象封装。数据抽象是一种依赖于接口实现分离的编程技术。类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。封装实现了类的接口和实现的分离

  • 定义成员函数的方式与普通函数差不多。成员函数的声明必须在类的内部,它的定义既可以在类的内部也可以在类的外部。作为接口组成的非成员函数,定义和声明都在类的外部。定义在类内部的函数是隐式的inline函数
    在这里插入图片描述
    在这里插入图片描述

  • 尽管所有成员都必须在类的内部声明,但是成员函数体可以定义在类内也可以定义在类外。

  • 成员函数通过一个名为this的额外隐式参数来访问调用对象。当调用一个成员函数的时候,用请求该函数的对象地址初始化this。

  • 任何自定义this的参数或变量的行为都是非法的。我们可以在成员函数体内部使用this,尽管没有必要。

  • 引入const成员函数.紧随参数列表之后的const关键字。不能把this绑定到一个常量对象上,不能在常量对象上调用普通成员函数。常量对象,以及常量对象的引用或指针都之内调用常量成员函数

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

  • 在类的外部定义成员函数.成员函数的定义必须与它的声明匹配。如果成员被声明成常量成员函数,那么它的定义也必须在参数列表后明确指定const属性。类外部定义的成员的名字必须包含它所属的类名。如
    在这里插入图片描述
    使用类作用域运算符,一旦编译器看到函数名,就能理解剩余的代码是位于类的作用域内。当avg_price使用revenue和units_sold时,隐式地使用了Sales_data的成员

  • 定义一个返回this对象的函数.
    在这里插入图片描述

  • 一般来说,如果非成员函数是类接口的组成部分,则这些函数的声明应该与类在同一个头文件内。默认情况下,拷贝类的对象其实是拷贝对象的数据成员

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

  • 构造函数的名字和类名相同。和其他函数不同,构造函数没有返回类型,构造函数也有一个(可能为空的)参数列表和一个(可能为空的)函数体。类可以包含多个构造函数,类似于重载函数。构造函数不能被声明成const

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

  • 编译器构造的函数又被称为合成的默认构造函数。按照如下规则初始化类的数据成员:如果存在类内初始值,用它来初始化成员;否则。默认初始化该成员。

  • 某些类不能依赖于合成的默认构造函数.
    三个原因:一,只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数。二,含有内置类型或复合类型成员的类应该在类的内部初始化这些成员,或者定义一个自己的默认构造函数。否则,用户在创建类的对象时就可能得到未定义的值。三,有的时候编译器不能为某些类合成默认的构造函数。
    在这里插入图片描述

Sales_data()=default;
//该构造函数不接受任何实参,所以它是一个默认构造函数。定义目的时因为既需要其他形式的构造函数,也需要默认构造函数。在参数列表后面写上=default来要求编译器生成构造函数。其中-=default既可以和声明一起出现在类的内部(默认构造函数是内联的),也可以作为定义出现类的外部
  • 构造函数初始值列表.构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号括起来的(或者在花括号内的)成员初始值。不同的成员的初始化通过逗号分隔开来。
    在这里插入图片描述

  • 构造函数没有返回类型。当在类外部定义构造函数时,必须指明构造函数是哪个类的成员。Sales_data::Sales_data()

  • 类还需要控制拷贝、赋值和销毁对象时发生行为。不主动定义,编译器将会合成。当类需要分配类对象之外的资源时,合成的版本常常会失效。使用vector对象或者string对象管理必要的存储空间


7.2 访问控制与封装

  • 访问说明符.定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的借口。定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装(即隐藏了)类的实现细节
  • 使用class和struct定义类唯一的区别就是默认的访问权限.如果使用struct关键字,则定义在第一个访问说明符之前的成员是public的,相反使用class关键字,则这些成员是private的
  • 友元.类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。如果类想把一个函数作为友元,只需要增加一条以friend 关键字开始的函数声明语句即可。友元声明只能出现在类定义的内部。友元的声明.友元的声明仅仅指定类访问的权限,如果希望调用某个友元函数,需要在友元声明之外再专门对函数进行一次声明。

7.3 类的其他特性

  • 定义一个类型成员.类可以自定义某些类型在类总对别名。由类定义的类型名字和其他成员一样存在访问限制。定义类型的成员必须先定义后使用

  • 令成员作为内联函数.可以在类的内部也可以在类的外部用inline关键字修饰函数的定义。inline成员函数也应该与相应的类定义在同一个头文件

  • 成员函数也可以被重载

  • 可变数据成员.修改类某个数据成员,即使是在一个const成员函数内,也可以通过在变量的声明中加入mutable关键字来做到。

  • 类数据成员初始值.提供一个类内初始值时,必须以符号=或者花括号表示

  • 基于const的重载

  • 每个类定义了唯一的类型。对于两个类来说,即使它们的成员完全一样,这两个类也是两个不同的类型

  • 类的声明,有时候被称作前向声明。在它声明之后定义之前时一个不完全类型

  • 类还可以把其他的类定义成友元,也可以把其他类(之前已经定义过的)成员函数定义成友元。友元关系不存在传递性
    在这里插入图片描述
    除了另一个类作为友元,还可以只为这个类的成员函数声明成友元。
    在这里插入图片描述

  • 如果一个类想把一组重载函数声明成它的友元,它需要对这组函数中每一个分别声明。

  • 友元声明和作用域
    在这里插入图片描述


7.4 类的作用域

  • 当成员函数定义在类的外部时,返回类型中使用的名字都位于类的作用之外。这个时候返回类型必须指明它是哪个类的成员,哪个类定义了它

  • 名字查找,分三步:首先在名字所在块中寻找其声明的语句,只考虑在名字的使用之前的声明。如果没找到继续查找外层作用域。否则程序报错。

  • 类的定义处理,分两步:首先,编译成员的声明;直到类全部可见后才编译函数体。 编译器处理完类中全部声明后才会处理成成员函数的定义

  • 类型名要特殊处理.一般来说,内层作用域可以重新定义外层作用域中的名字。但是在类中,如果成员使用了外层作用域中的某个名字,则类不能在之后重新定义该名字。
    在这里插入图片描述

  • 成员定义中的普通块作用域的名字查找.首先,在成员函数内查找该名字的声明。如果成员函数内没有找到,在类内继续查找,这时类的所有成员都可以被考虑。如果类内也没有找到,在成员函数定义之前的作用域内继续查找。

  • 类作用域之后,在外围的作用域中查找。尽管外层的对象被屏蔽掉了,但仍然可以用作用域运算符访问 在文件中名字的出现处对其进行解析.当成员定义在类的外部时,不仅要考虑类定义之前的全局作用域中的声明,还要考虑成员函数定义之前的全局作用域的声明。


7.5 构造函数再探

  • 如果没有在构造函数的初始值列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化.

  • 构造函数的初始值有时必不可少.如果成员时const或引用的话,必须将其初始化。当成员属于某种类型且该类没有定义默认构造函数时,也必须将这个成员初始化。初始化const或引用类型的数据成员的唯一机会就是通过构造函数初始化
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 成员初始化的顺序.构造函数初始值列表只说明用于初始化成员的值,而不限定初始化的具体执行顺序。成员的初始化顺序与它们在类定义中的出现顺序一致。如果一个成员是用另一个成员来初始化的,那这两个成员的初始化顺序很关键。

class X{
	int i;
	int j;
public:
	//未定义的:i在j之前被初始化
	X(int val): j(val),i(j){}
};
//实际上,i先被初始化,因此这个初始化效果是试图使用未定义的值j初始化i
//改为, X(int val):i(val),j(val) {}
  • 如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数

  • 委托构造函数.一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程。
    在这里插入图片描述
    当一个构造函数委托给另一个构造函数时,受委托的构造函数的初始值列表和函数体被依次执行。假如函数体包含有代码时,先执行代码,然后控制权才会交还给委托者的函数体。

  • 默认构造函数的作用
    在这里插入图片描述
    在实际中,如果定义了其他构造函数,那么最好也提供一个默认构造函数。

  • 隐式的类类型转换.如果构造函数只接受了一个实参,则它实际上定义了转换为此类类型的隐式转换机制,又称转换构造函数。能通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型隐式转换的规则。编译器只会自动地执行一步类型转换

  • 抑制构造函数定义的隐式转换.可以通过将构造函数声明为explicit 加以抑制。
    在这里插入图片描述
    关键字explicit 只对一个实参的构造函数有效,多个实参的构造函数不能执行隐式转换。只能在类内声明构造函数时使用explicit关键字,在类外部定义是不应重复。explicit构造函数只能用于直接初始化,使用(=)

  • 为转换显式地使用构造函数.
    在这里插入图片描述

  • 标准库中含有显式构造函数的类.
    在这里插入图片描述

  • 聚合类.使得用户可以直接访问成员,并具有特殊的初始化语法形式。当一个类满足如下条件时,我们说它是聚合的:当所有的成员都是public的 没有定义任何构造函数 没有类内初始只 没有基类,也没有virtual函数 。我们可以一个话括号括起来的成员初始值列表,并且用它初始化聚合类的数据成员。初始值的顺序必须与声明顺序一致

  • 字面值常量类.某些类也是字面值类型,字面值类型的类可能含有constexpr函数成员。这样的成员必须符合constexpr函数所有要求,它们是隐式const的。
    在这里插入图片描述

  • constexpr构造函数.尽管构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr函数。一个字面值常量类必须至少提供一个constexpr构造函数。constexpr构造函数可以声明为=default的形式。否则constexpr构造函数就必须既符合构造函数的要求,又符合constexpr函数的要求。综合可知,constexpr构造函数体一般来说应该是空的


7.6 类的静态成员

  • 有时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。
  • 声明静态成员static.类的静态成员成员存在于任何对象之外,对象不包含任何与静态数据成员有关的数据。静态成员函数不能声明成const的,也不能在static函数体内使用this指针
  • 使用类的静态成员.使用作用域运算符直接访问静态成员。使用类的对象、引用或者指针来访问静态成员。成员函数不用通过作用域运算符就能直接使用静态成员
  • 定义静态成员.既可以在类的内部也可以在类的外部定义静态成员函数。当在类的外部定义静态成员时,不能重复static关键字,该关键字只能出现在类内部的声明语句。一个静态数据成员只能定义一次,类似于全局变量,静态数据成员定义在任何函数之外,一旦定义,就一直存在于程序的整个生命周期中。
    在这里插入图片描述
  • 静态成员的类内初始化.通常情况下,类的静态成员不应该在类的内部初始化,然而,可以为静态成员提供const整数类型的类内初始化,不过要求静态成员必须是字面值常量类型的constexpr。
发布了46 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/sinat_34686158/article/details/104422945