一、抽象和类
生活中充满了复杂性,处理复杂性的方法之一是简化和抽象。在计算机中,为了根据信息与用户之间的接口来表示它,抽象是至关重要的。也就是说要将本质特征抽象出来,并根据特征来描述解决方案。在C++中,用户定义类型是指实现抽象接口的类设计。
类型是什么?我们制定基本类型主要完成了三项工作:
- 决定数据对象需要的内存数量;
- 决定了如何解释内存中的位(long和float在内存中所占的位数相同,但是将它们转换为数值的方法不同);
- 决定了使用数据对象执行的操作或方法。
C++中的类。类是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组成合成一个整洁的包。首先我们要先考虑如何表示一个类;接着我们来定义类,一般来说类规范由两个部分组成,即类声明和类方法定义。类声明:以数据成员的方式描述数据部分,以成员函数(方法)的方式来描述公有接口。类方法定义:描述如何实现类成员函数。简单来说,类声明提供了类的蓝图,而方法提供了细节。
什么是接口?接口是一个共享框架,供两个系统交互时使用。对于类,我们说公共接口。这里公众是使用类的程序,交互式系统由类对象组成,而接口有编写类的人提供的方法组成。我们要使用某一个类对象,无需直接访问类成员,只需使用该类的方法即可。类设计禁止公共用户直接访问类。
为开发一个类并编写一个使用它的程序,需要完成多个步骤。这里将开发分成多个阶段,通常c++程序员将接口(类定义)放在头文件中,并将实现(类方法)放在源代码文件中。
class Student{
//成员变量
private:
char *name;
int age;
float score;
public:
//成员函数
void say(){
printf("%s的年龄是 %d,成绩是 %f\n", name, age, score);
}
};
其中private和public关键字描述了对类成员的访问控制,private的成员公共用户需要使用public的函数来调用。
类设计尽可能将公有接口与实现分开。公有接口表示设计的抽象组件,将实现细节挡在一起并将它们与抽象分开被称为封装。数据隐藏(将数据放在类的私有部分)是一种封装。
实现类成员函数。成员函数定义和常规函数定义非常相似,都有函数头和函数体。但是类成员函数还有两个特殊的特征:
- 定义成员函数时,使用作用域解析运算符(::)来指定函数所属的类;
- 类方法可以访问类的private组件。
比如我们可以定义一个类成员函数update()的函数头为void Stock::update(double price);
这样我们不仅描述了函数所属的类,也告诉我们可以在其他类中也定义相同函数名的成员函数。因此,作用域解析运算符确定了方法定义的类的身份,同时方法可以访问类的私有成员。
实现类成员函数,我们可以在类声明中提供完整的函数定义(函数实现较小的情况),此时函数将自动成为内联函数。我们也可以在声明之外定义成员函数并使之成为内联函数,只需在类实现之中定义函数时使用inline限定符即可。
二、类的构造函数和析构函数
C++的目标之一是让使用类对象就像使用标准类型一样,常规的初始化语法不适用于类,因为数据部分是私有的,不能直接访问,因此需要设计合适的成员函数才能将对象初始化,一般最好是在创建对象时进行初始化。为此,C++提供了一个特殊的成员函数—类构造函数,专门用于构造新对象、将值赋给它们的数据成员。
声明和定义构造函数。声明构造函数和常规函数类似,不过没有声明类型,函数名与类名相同。例如:
Stock(const string &co, long n=0, double pr=0.0);
注意在声明构造函数时,不要将类成员名用作构造函数的参数。
使用构造函数。C++提供了两种使用构造函数来初始化对象的方式,第一种是显式地调用构造函数:Stock food=Stock(“world”,250,1.25);
另一种是隐式地调用构造函数:Stock garment(“world”,250,1.25);
每次创建类对象(甚至使用new动态分配内存)时,C++都使用类构造函数。下面是将构造函数与new一起使用的方法:Stock *pstock=new Stock(“games”,18,19.0);
对象是无法用来调用构造函数的,因为在构造函数创建对象之前对象是不存在,因此构造函数被用来创建对象。
在设计类时,通常应提供对所有类成员做隐式初始化的默认构造函数。
析构函数。用构造函数创建对象后,程序负责跟踪该对象,直到过期为止。对象过期时,程序将自动调用一个特殊的函数—析构函数,该函数完成清理工作。例如构造函数使用new来分配内存,则析构函数将使用delete来释放内存,当构造函数没有使用new时,析构函数什么都不用做。析构函数的声明很特殊,在类名前加上~,可以没有返回值和声明类型,与构造函数不同的是析构函数没有参数。
const成员函数。为了保证类函数成员在调用时不会被修改,需要一种新的语法,C++的解决方法是将const关键字放在函数括号的后面。
三、this指针
有时候方法调用会涉及到多个对象,在这种情况下我们需要使用this指针。每个成员函数(包括构造函数和析构函数)都有一个this指针,其指向调用对象。
四、对象数组
用户通常要创建同一个类的多个对象,我们可以创建多个独立的对象变量,但是创建对象数组将更好。创建对象数组和创建标准类型的数组相同。初始化对象数组的方案是首先使用默认构造函数创建数组,然后非默认构造函数将创建临时对象,然后将临时对象的内容复制到相应的元素中去。因此要创建对象数组,则这个类必须有默认构造函数。
五、类作用域
在类中定义的名称的作用域为整个类,且它们在类中是已知的,在类外是不可知的,因此可以在不同的类中使用相同的类成员名。在类声明或成员函数定义中,可以使用未修饰的成员名称,构造函数名称在被调用时才能被识别,因为它与类名相同。在其他情况下使用类成员名时必须根据上下文使用直接成员运算符、间接成员运算符或作用域解析运算符。
作用域为类的常量。有时候使用作用域为类的常量很有用,我们有两种方法实现这个目标,第一个是使用枚举;另一种是在类中定义常量—使用关键字static。
六、抽象数据类型
程序员常常通过定义类来表示更通用的概念。例如就实现计算机专家们所说的抽象数据类型而言,使用类是一种非常好的选择。使用栈就是这样一种应用,栈由可对它执行的操作来描述:
- 可创建空栈
- 可将数据项压入
- 可从栈顶删除数据
- 可查看栈是否填满
- 可查看栈是否为空