Chapter 10 对象和类

本章内容包括:

  • 过程性编程和面向对象编程
  • 类概念
  • 如何定义和实现类
  • 公有类访问和私有类访问
  • 类的数据成员
  • 类方法(类函数成员)
  • 创建和使用类对象
  • 类的构造函数和析构函数
  • const成员函数
  • this指针
  • 创建对象数组
  • 类作用域
  • 抽象数据类型

面向对象编程(OOP)是一种特殊的设计程序的概念性方法,其最重要的特征图下:

  • 抽象;
  • 封装和数据隐藏;
  • 多态;
  • 继承;
  • 代码的可重用性

10.1 过程性编程和面向对象编程

面向过程编程的方法:首先考虑遵循的步骤,然后考虑如何表示这些数据;
面向对象编程的方法:首先从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作。完成接口的描述后,需要确定如何实现接口和数据存储,最后使用新的设计方案创建出程序。

10.2 抽象和类

处理复杂性问题的方法之一是简化和抽象;将问题的本质特征抽象出来。

10.2.1 类型是什么

指定基本类型完成了三项工作:

  • 决定数据对象需要的内存数量;
  • 决定如何解释内存中的位(long和float在内存中占用的位数相同,但将他们转换为数值的方法不同)
  • 决定可使用数据对象执行的操作或方法

10.2.2 C++中的类

一般来说,类规范由两个部分组成:

  • 类声明:以数据成员的方式描述数据部分,以成员函数(被称为方法)的方式描述公有接口。
  • 类方法定义:描述如何实现类成员函数

类成员函数可以就地定义,也可以用原型表示。
将数据和方法组合成一个单元是类最吸引人的特性。

  1. 访问控制
    公有成员函数是程序和对象的私有成员之间的桥梁,提供了对象与程序直接的接口。防止程序直接访问数据被称为数据隐藏。
    类设计尽可能将公有接口和实现细节分开,该过程被称为封装,数据隐藏也是一种封装。
    数据隐藏可以让开发者无需了解数据是如何被表示的。
  2. 控制对成员的访问:公有还是私有
    数据项通常放在私有部分,组成类接口的成员函数放在公有部分;不必在类声明中使用private:因为这是类对象的默认访问控制。
    类和结构非常相似,他们之间的唯一区别是:结构的默认访问类型是public,而类为private。

10.2.3 实现类成员函数

类成员函数由两个特殊的特征:

  • 定义成员函数时,使用作用域解析运算符::来标识函数所属的类;
  • 类方法可以访问类的private组件
  1. 成员函数说明
    私有成员函数,编写类的人可以使用它,编写代码使用类的人不能使用。
  2. 内联方法
    定义位于类声明中的函数都将自动称为内联函数;也可以在类外定义成员函数,加inline限定符使其称为内联函数。
  3. 方法使用哪个对象
    调用成员函数时使用被用来调用它的对象的数据成员。

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 复习题

  1. 什么是类?
    类是抽象出来的特征的集合类是用户定义的类型的定义。类声明制定了数据将如何存储,同时指定了用来访问和操纵这些数据的方法(类成员函数)。
  2. 类如何实现抽象、封装、和数据隐藏?
    类通过将特征提取出来实现抽象,封装通过定义接口方法,将方法和定义分开实现封装,将数据私有化实现数据隐藏。
    类表示人们可以类方法的公有接口对类对象执行的操作,这是抽象。
    类的数据成员可以是私有的(默认值),这意味着只能通过成员函数的方法来访问这些数据,这就是数据隐藏。
    实现的具体细节(如数据表示和方法的代码都是隐藏的,这是封装)

  3. 对象和类之间的关系是什么?
    对象是类的具体化,类是对象的抽象。
    类定义了一种类型,包括如何使用它。
    对象是一个变量或其他数据对象(如由new生成的),并根据类定义被创建和使用。
    类和对象之间的关系同标准类型与其变量之间的关系相同。

  4. 除了函数之外,类函数成员与类数据成员之间的区别是什么?
    类函数成员通常是公有的,它是类数据成员与程序交互的接口;类数据成员通常是私有的,通过私有实现数据隐藏。
    如果创建给定类的多个对象,则每个对象都有自己的数据空间;但所有的对象都是用同一组成员函数。
  5. 定义一个类来表示银行账户。数据成员包括储户姓名、账号(使用字符串)和存款。成员函数执行如下操作:
  • 创建一个对象并将其初始化;
  • 显示储户姓名、账号和存款;
  • 存入参数指定的存款;
  • 取出参数指定的款项。
    请提供类声明,而不用给方法实现(编程练习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是对象本身。

posted @ 2021-12-24 18:35  Fight!GO  阅读(42)  评论(0编辑  收藏  举报