Chapter 10 对象和类
本章内容包括:
- 过程性编程和面向对象编程
- 类概念
- 如何定义和实现类
- 公有类访问和私有类访问
- 类的数据成员
- 类方法(类函数成员)
- 创建和使用类对象
- 类的构造函数和析构函数
- const成员函数
- this指针
- 创建对象数组
- 类作用域
- 抽象数据类型
面向对象编程(OOP)是一种特殊的设计程序的概念性方法,其最重要的特征图下:
- 抽象;
- 封装和数据隐藏;
- 多态;
- 继承;
- 代码的可重用性
10.1 过程性编程和面向对象编程
面向过程编程的方法:首先考虑遵循的步骤,然后考虑如何表示这些数据;
面向对象编程的方法:首先从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作。完成接口的描述后,需要确定如何实现接口和数据存储,最后使用新的设计方案创建出程序。
10.2 抽象和类
处理复杂性问题的方法之一是简化和抽象;将问题的本质特征抽象出来。
10.2.1 类型是什么
指定基本类型完成了三项工作:
- 决定数据对象需要的内存数量;
- 决定如何解释内存中的位(long和float在内存中占用的位数相同,但将他们转换为数值的方法不同)
- 决定可使用数据对象执行的操作或方法
10.2.2 C++中的类
一般来说,类规范由两个部分组成:
- 类声明:以数据成员的方式描述数据部分,以成员函数(被称为方法)的方式描述公有接口。
- 类方法定义:描述如何实现类成员函数
类成员函数可以就地定义,也可以用原型表示。
将数据和方法组合成一个单元是类最吸引人的特性。
- 访问控制
公有成员函数是程序和对象的私有成员之间的桥梁,提供了对象与程序直接的接口。防止程序直接访问数据被称为数据隐藏。
类设计尽可能将公有接口和实现细节分开,该过程被称为封装,数据隐藏也是一种封装。
数据隐藏可以让开发者无需了解数据是如何被表示的。 - 控制对成员的访问:公有还是私有
数据项通常放在私有部分,组成类接口的成员函数放在公有部分;不必在类声明中使用private:因为这是类对象的默认访问控制。
类和结构非常相似,他们之间的唯一区别是:结构的默认访问类型是public,而类为private。
10.2.3 实现类成员函数
类成员函数由两个特殊的特征:
- 定义成员函数时,使用作用域解析运算符
::
来标识函数所属的类; - 类方法可以访问类的private组件
- 成员函数说明
私有成员函数,编写类的人可以使用它,编写代码使用类的人不能使用。 - 内联方法
定义位于类声明中的函数都将自动称为内联函数;也可以在类外定义成员函数,加inline限定符使其称为内联函数。 - 方法使用哪个对象
调用成员函数时使用被用来调用它的对象的数据成员。
10.2.4 使用类
C++的目标是使用类与使用基本的内置类型(如int和char)尽可能相同。创建类对象可以声明类变量,也可以使用new为类对象分配存储空间。对象可以作为函数的参数和返回值,也可以将一个对象赋给另一个。
客户/服务器模型
客户指的是使用类的程序,类声明包括类方法构成了服务器,它是程序可以使用的资源。
服务器的责任是确保服务器根据该接口可靠并准确的执行,服务器人员只能修改类设计的细节,而不能修改接口。客户程序员的唯一责任就是了解接口。
10.2.5 修改实现
修改显示的格式,需要修改完恢复,以免对其他输出造成影响。
10.3 类的构造函数和析构函数
应该为类提供构造函数和析构函数的标准函数。
10.3.1 声明和定义构造函数
Stock hot = {"Sukie's Autos, Inc.", 200, 50.25}; //NO!compile error
Stock类一个可能的构造函数是名为Stock的成员函数。构造函数没有声明类型。
程序声明对象时将会自动调用构造函数。
成员名和参数名不能相同,常见的做法时给数据成员使用m_前缀或者_后缀
10.3.2 使用构造函数
- 显示调用:Stock food = Stock("World Cabbage", 250, 1.25)
- 隐式调用:Stock garment = ("Furry Mason", 50, 2.5)
构造函数只能被用来创建对象,而不能通过对象调用。
10.3.3 默认构造函数
默认构造函数在未提供显示初始值时,用来创建对象的构造函数。
没有提供任何构造函数,C++将自动提供默认构造函数,它是默认构造函数的隐式版本(当没有定义任何构造函数时才会提供)。
定义默认构造函数的方式有两种:
- 给已有的构造函数提供默认值
- 没有参数的构造函数Stock();定义的默认构造函数通常给所有成员提供隐式初始值。
10.3.4 析构函数
构造函数创建对象后,程序负责跟踪该对象,直到过期为止。对象过期时程序将调用析构函数。如果构造函数使用new分配内存,则析构函数将使用delete释放这些内存。
通常不应在代码中显示的调用析构函数。如果没有提供析构函数,C++编译器将隐式的声明一个构造函数
10.3.5 改进Stock类
如果可以通过初始化,也可以通过赋值来设置对象的值,则应采用初始化方式。这种方式相率更高。
const成员函数
对于一个const的类对象,只能执行不会修改的成员函数,因此应该对成员函数加const限定符,例如void show()const;
声明也需要加。
10.4 this指针
有的时候方法可能涉及到两个对象,在这种情况下需要使用C++的this指针。
每个成员函数(包括构造函数和析构函数)都有一个this指针。this指针指向调用对象。如果方法需要引用整个调用对象,则可以使用表达式*this。
10.5 对象数组
创建同意类的多个对象,创建对象数组更合适。这要求类方法必须有默认构造函数。
10.6 类作用域
类中定义的名称的作用域为整个类。
10.6.1 作用域为类的常量
直接在类中声明常量是行不通的,因为声明类只是描述了对象的形式,并没有创建对象,因此创建对象前,没有用于存储值的空间。有两种方法可以实现该目标:
- 声明一个枚举enum {Months = 12};
- 使用static; static const int Months = 12;
10.6.2 作用域枚举(C++11)
传统的枚举存在一些问题,例如两个枚举定义中的枚举量会发生冲突,为避免这种冲突,C++11提供了一种新枚举,这种枚举的声明如下:
enum class egg {Small, Medium, Large, Jumbo};
enum class t_shirt {Small, Medium, Large, Xlarge};
作用域内枚举不能隐式的转换为整形。
10.7 抽象数据类型
抽象数据类型(ADT)的例子:
栈,其特征为:
- 可创建空栈
- 可将数据添加到栈顶(压入)
- 可从栈顶删除数据(弹出)
- 可查看栈是否填满
- 可查看栈是否为空
10.8 总结
每个对象都存储自己的数据,而共享类方法。
10.9 复习题
- 什么是类?
类是抽象出来的特征的集合。类是用户定义的类型的定义。类声明制定了数据将如何存储,同时指定了用来访问和操纵这些数据的方法(类成员函数)。 - 类如何实现抽象、封装、和数据隐藏?
类通过将特征提取出来实现抽象,封装通过定义接口方法,将方法和定义分开实现封装,将数据私有化实现数据隐藏。
类表示人们可以类方法的公有接口对类对象执行的操作,这是抽象。
类的数据成员可以是私有的(默认值),这意味着只能通过成员函数的方法来访问这些数据,这就是数据隐藏。
实现的具体细节(如数据表示和方法的代码都是隐藏的,这是封装) - 对象和类之间的关系是什么?
对象是类的具体化,类是对象的抽象。
类定义了一种类型,包括如何使用它。
对象是一个变量或其他数据对象(如由new生成的),并根据类定义被创建和使用。
类和对象之间的关系同标准类型与其变量之间的关系相同。 - 除了函数之外,类函数成员与类数据成员之间的区别是什么?
类函数成员通常是公有的,它是类数据成员与程序交互的接口;类数据成员通常是私有的,通过私有实现数据隐藏。
如果创建给定类的多个对象,则每个对象都有自己的数据空间;但所有的对象都是用同一组成员函数。 - 定义一个类来表示银行账户。数据成员包括储户姓名、账号(使用字符串)和存款。成员函数执行如下操作:
- 创建一个对象并将其初始化;
- 显示储户姓名、账号和存款;
- 存入参数指定的存款;
- 取出参数指定的款项。
请提供类声明,而不用给方法实现(编程练习1将要求编写实现)
// account.h -- class defination for Account
#ifndef ACCOUNT_H_
#define ACCOUNT_H_
#include<string>
static const int NumSize = 10;
class Account
{
private:
std::string name_;
char account_number_[NumSize];
double deposit_;
public:
Account();
Account(const std::string & name, char account_number[], double deposit, int n= NumSize);
~Account();
void show_account() const;
void deposit_in(double deposit);
void deposit_out(double deposit);
};
#endif
6. 类构造函数何时被调用?类析构函数呢?
类构造函数在对象被创建时调用(或显示调用构造函数),类析构函数在对象被销毁时过期。
7. 给出复习题5中银行账户类的构造函数的代码。
Account::Account(const std::string & name, char *account_number, double deposit, int n= NumSize)
{
name_ = name;
//for(int i = 0; i < n; i++)
//{
// account_number_[i] = account_number[i];
//}
strcpy(account_number)
deposit_ = deposit;
}
8. 什么是默认构造函数,拥有默认构造函数有什么好处?
默认构造函数是指创建没有初始化的类对象时会调用的函数,拥有默认构造函数可以创建对象而不需要初始化。
9. 修改Stock类的定义(stock20.h中的版本),使之包含返回各个数据成员值的成员函数。注意:返回公司名的成员函数不应为修改数组提供便利,也就是说不能简单的返回string引用。
添加的成员函数如下:
const std::string & getcompany() const{return company;}
long getshares() const{return shares;}
double getshares_val() const {return share_val;}
double gettotal_val() const {return total_val;}
10. this和*this时什么?
this是可以当前对象指向各个指向各个成员函数的指针,*this是整个当前对象的地址。
this指针是类方法可以使用的指针,它指向用于调用方法的对象。this是对象的地址,*this是对象本身。