定义抽象数据类型
对于抽象数据类型来说,我们通过它的接口来使用它的对象而不知道这个类有哪些数据成员。与之相反,若一个类允许他的用户直接访问他的数据成员,并且要求由用户来编写操作,则这个类不是一个抽象数据类型。
设计类
类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。
定义类
定义成员函数:
定义和声明成员函数的方式与普通函数差不多。成员函数的声明必须在类的内部,他的定义则既可以在类的内部也可以在类的外部。类外部定义的成员的名字必须包含它所属的类名。
定义在类内部的函数是隐式的inline函数。
引入this:
当我们调用一个成员函数时,用请求该函数的对象的地址初始化this。
在成员函数内部我们可以直接使用调用该函数的对象的成员,而无须通过访问运算符做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看作this的隐式引用。
因为this的目的是总是指向“这个”对象,所以this是个常量指针,不允许改变this中保存的地址。
引入const成员函数:
紧随参数列表之后的const关键字的作用是修改隐式this指针的类型。
默认情况下,this的类型是指向类类型非常量版本的常量指针。因为this需要遵循初始化规则,所以(在默认情况下)不能把this绑定到一个常量对象上。这一情况使得我们不能在一个常量对象上调用普通的成员函数。
如果一个函数不会改变this所指的对象,则把它设置为指向常量的指针有助于提高函数的灵活性。
紧跟在参数列表后面的const表示this是一个指向常量的指针。像这样使用const的成员函数被称为常量成员函数。常量成员不能改变调用它的对象的内容。常量对象,以及常量对象的引用或指针都只能调用常量成员函数。
类作用域和成员函数:
类本身就是一个作用域。
编译器分2步处理类:首先编译成员的声明,然后才轮到成员函数体。因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序。
定义一个返回this对象的函数:
在设计类似复合赋值运算符+=时,函数最后“return *this;"。
一般来说,当我们定义的函数类似于某个内置运算符时,应该令该函数的行为尽量模仿这个运算符。
定义类相关的非成员函数
一般来说,如果非成员函数是类接口的组成部分,则这些函数的声明应该与类在同一个头文件内。
构造函数
每个类都分别定义了他的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。
构造函数的名字和类名相同。和其他函数不一样的是,构造函数没有返回类型;除此之外类似于其他函数,构造函数也有一个(可能为空的)参数列表和一个(可能为空的)函数体。类可以包含多个构造函数,和其他重载函数差不多,不同的构造函数之间必须在参数数量或参数类型上有所区别。
不同于其他成员函数,构造函数不能被声明为const的。当我们创建一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数在const对象的构造过程中可以向其写值。
合成的默认构造函数
类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数。默认构造函数无须任何实参。(明确一点,如果一个构造函数不接受任何实参,则它是一个默认构造函数)
如果我们的类没有显示的定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数。编译器创建的构造函数又被称为合成的默认构造函数。对于大多数类来说,这个合成的默认构造函数将按照如下规则初始化类的数据成员:
·如果存在类内的初始值,用它来初始化成员。
·否则,默认初始化该成员。
(默认构造函数包括自定义的和合成的默认构造函数)
某些类不能依赖于合成默认构造函数
合成的默认构造函数只适用于非常简单的类。对于一个普通的类来说,必须定义它自己的默认构造函数。原因有三:
·只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数。也就是说如果我们定义了一些非默认构造函数,除非再定义一个默认构造函数,否则该类没有默认构造函数。
·对于某些类来说,合成的默认构造函数可能执行错误的操作。如果类包含有内置类型或者复合类型的成员,则只有当这些成员全都被赋予了类内初始值时,这个类才适合于使用合成的默认构造函数。否则,用户在创建类的对象时就可能得到未定义的值。
·有时候编译器不能为某些类合成默认的构造函数。例如,如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。
=default的含义
在参数列表后写上=default来要求编译器生成构造函数。
其中,=default既可以和声明一起出现在类内部,也可以作为定义出现在类的外部。和其他函数一样,如果=default在类的内部,则默认构造函数是内联的;如果它在类的外部,则该成员默认情况下不是内联的。
我们定义这个构造函数的目的仅仅是因为我们既需要其他形式的构造函数,也需要默认的构造函数。我们希望这个函数的作用完全等同于之前使用的合成默认构造函数。
构造函数初始值列表
如果编译器不支持类内初始值,那么默认构造函数应该使用构造函数初始值列表来初始化类的每个成员。
Sales_data(const std::string &s): bookNo(s){ }
冒号及冒号和花括号之间的代码是构造函数初始化列表,它负责为新创建的对象的一个或几个数据成员赋初值。构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号括起来的(或者在花括号内的)成员初始值。不同成员的初始值通过逗号分隔开来。
通常情况下,构造函数使用类内初始值不失为一种好的选择。构造函数不应该轻易覆盖掉类内的初始值,除非新赋的值与原值不同。(初始值列表忽略某个数据成员而且该数据成员也没有类内初始值,那么就会执行默认初始化,若是内置类型,就会在创建对象时得到未定义的值)
函数体为空的情况:因这些构造函数的唯一目的就是为数据成员赋初值,一旦没有其他任务需要执行,函数体也就为空了。
当某个数据成员被构造函数初始值列表忽略时,它将以与合成默认构造函数相同的方法隐式初始化。也就是说,没有出现在构造函数初始值列表中的成员将通过相应的类内初始值(如果存在的话)初始化,或者执行默认初始化。
拷贝、赋值和析构
尽管编译器能替我们合成拷贝、赋值和销毁操作,但是必须要清楚的一点是,对于某些类来说合成的版本无法正常工作。在学习关于如何自定义操作之前,类中所有分配的资源都应该直接以类的数据成员的形式存储。