第10章 对象和类

前言:每次想静下心来把这本书很仔细的学习一遍,发现总是学习了一会就干别的事情了,也可能是因为感觉书太厚了学不完,心浮气躁吧,为了能每次学完一章内容都有个记录和回忆,特此开了这一期的博客------记下自己的学习之旅!!!!

喜欢的名言:  成功不是将来才有的,而是决定去做那一刻起,持续积累而成!      生于忧患,死于安乐!!

本章内容包括:

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

 OOP的特性:抽象、封装和数据隐藏、多态、继承、代码的可重用性。构造函数和析构函数用于创建和删除属于当前类的对象。

10.1 过程性编程(OOP)和面向对象编程

采用OOP方法时,首先从用户的角度考虑对象----描述对象所需要的数据以及描述用户与数据交互所需要的操作。完成对接口的描述后,需要确定如何实现接口和数据存储,最后,使用新的设计方案创建出程序。

10.2 抽象和类

抽象:从具体事物抽出、概括出它们共同的方面、本质属性与关系等,而将个别的、非本质的方面、属性与关系舍弃,这种思维过程。将问题的本质特征抽象出来,并根据特征来描述解决方案。在C++中,用户定义类型指的是实现抽象接口和类设计。

10.2.1 类型是什么

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

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

对于内置类型来说,有关操作的信息被内置到编译器中,但在C++中定义用户自定义的类型时,必须自己提供这些信息,付出这些劳动换来了根据实际需要指定数据类型的强大功能和灵活性。

10.2.2 C++中的类

             类是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。

             接口:程序接口将你的意图转换为存储在计算机中的具体信息。由编写类的人提供的方法组成。   

无论类成员是数据成员还是成员函数,都可以在类的公有部分或私有部分声明它。但由于隐藏数据是OOP主要的目标之一,因此数据成员通常放在私有部分,组成类接口的成员函数放在公有部分:否则,就无法从程序中调用这些函数。

类和结构的区别:结构默认访问类型是public,而类为private。

10.2.3 实现类成员函数

         成员函数定义与常规函数定义非常相似,它们有函数头和函数体,也可以有返回类型和参数,但是他们还有两个特殊的特征:

  • 定义成员函数时,使用作用域解析运算符(::)来标识函数所属的类
  • 类方法可以访问类的private组件

        内联方法:其定义位于类声明中的函数都将自动成为内联函数,类声明常将短小的成员函数作为内联函数。

        调用成员函数时,它将使用被用来调用它的对象的数据成员。

例如:kate 和 joe 都是 Stock 对象,则 kate.shares 将占据一个内存块,而joe.shares占用另一个内存块,但kate.show() 和 joe.show()都调用同一个方法,也就是说,他们将执行同一个代码块,只是将这些代码用于不同的数据。在OOP中,调用成员函数被称为发送消息,因此将同样的消息发送给两个不同的对象将调用同一个方法,但该方法被用于两个不同的对象。

10.2.4 使用类

C++的目标是使得使用类和使用基本的内置类型(如int 和 char)尽可能相同。要创建类对象,可以声明类变量,也可以使用 new 为类对象分配存储空间。 

客户/服务器模型:OOP程序员常依照 客户/服务器 模型来讨论程序设计。在这个概念中,客户是使用类的程序。类的声明(包括类的方法)构成了服务器,它是程序可以使用的资源。客户只能通过以公有方式定义的接口使用服务器,这意味着客户客户(客户程序员)唯一的责任就是了解该接口。服务器(服务器设计人员)的责任是确保服务器根据该接口可靠并准确的执行。服务器设计人员只能修改类设计的实现细节,而不能修改接口。这样程序员独立地对客户和服务器进行改进,对服务器的修改不会对客户的行为造成意外的影响。

10.2.5 修改实现

void Stock::show()
{
	using std::cout;
	using std::ios_base;
	ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield);
	std::streamsize prec = cout.precision(3);

	std::cout << "Company: " << company
		<< "  Shares: " << shares << '\n'
		<< "  Share Price: $" << share_val;

	cout.precision(2);
    std::cout << "  Total Worth: $" << total_val << '\n';
	cout.setf(orig, ios_base::floatfield);
	cout.precision(prec);
}

10.3 类的构造函数和析构函数

C++的目标之一是让使用类对象就像使用标准类型一样,然而,到现在为止,本章提供的代码还不能让你像初始化 int 或 结构那样来初始化 Stock 对象。也就是说,常规的初始化语法不适用于类型Stock:

int year = 2000; // valid initialization

struct thing 

{

    char *pn;

    int m;

}

  thing amabob = {"wodget", -23};// valid initialization

  stock hot{"Sukie's Autos", Inc, 200, 50.25}; // No! compile error

注意:如果使数据成员成为公有,而不是私有,就可以按刚才介绍的方法初始化类对象,但使数据成为公有的违背了类的一个主要的初衷:数据隐藏

10.3.1 声明和定义构造函数

程序声明对象时,将自动调用构造函数。

10.3.2 使用构造函数

  1. 显示地调用构造函数:        Stock food = Stock("world Cabbage", 250, 1.25) ;
  2. 隐式的调用构造函数:        Stock food("Furry Mason", 50,  2.5) ;
  3. 使用new动态分配内存:     Stock *pstock = new Stock("Electroshock Games", 18, 19.0);

构造函数的使用不同于其他类的方法。一般来说,使用对象来调用方法:

stock1.show();

但无法使用对象来调用构造函数,因为在构造函数构造出对象之前,对象是不存在的。因此构造函数被用来创建对象,而不能通过对象来调用。

10.3.3 默认的构造函数

10.3.4 析构函数

  • 和构造函数一样,析构函数的名称也很特殊:在类名前加上~.和构造函数一样,析构函数也可以没有返回值和声明类型.与构造函数不同的是,析构函数没有参数.
  • 什么时候应调用析构函数呢?这由编译器决定,通常不应在代码中显示地调用析构函数(也有例外情形的,参阅第12章的”再谈定位new运算符”).如果创建的是静态存储类对象,则其析构函数将在程序结束时自动被调用.
  • 如果程序员没有提供析构函数,编译器将隐式地声明一个默认析构函数,并在发现导致对象被删除的代码后,提供默认析构函数的定义.

10.3.5 改进Stock类

  1. 头文件 
    • 程序清单10.4 stock10.h
  2. 实现文件 
    • 程序清单10.5 stock10.cpp
  3. 客户文件 
    • usestock2.cpp
    • 提示:在main()的开头和末尾多了一个大括号.诸如stock1和stock2等自动变量将在程序推出其定义所属代码块时消失.如果没有这些大括号,代码块将为整个main(),因此仅当main()执行完毕后,才会调用析构函数.在窗口环境中,这意味着将在两个析构函数调用前关闭,导致您无法看到最后两条消息.但添加这些大括号,最后两个析构函数调用将在到达返回语句前执行,从而显示相应的消息.
  4. 程序说明 
    • 注意:在默认情况下,将一个对象赋给同类型的另一个对象时,C++将源对象的每个数据成员的内容复制到目标对象中相应的数据成员中.
    • 提示:如果既可以通过初始化,也可以通过赋值来设置对象的值,则应采用初始化方式.通常这种方式的效率更高.
  5. C++11列表初始化 
    • 在C++11中,可将列表初始化语法用于类么?可以,只要提供与某个构造函数的参数列表匹配的内容,并用大括号将它们括起.
    • 另外,C++11还提供了名为std::initialize_list的类,可将其用作函数参数或方法参数的类型.这个类可表示任意长度的列表,只要所有列表项的类型都相同或可转换为相同的类型.
  6. const成员函数 
    • C++的解决方法是将const关键字放在函数的括号后面,函数定义也是如此操作.

10.3.6 构造函数和析构函数小结

  • 警告:接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值:Classname objcet = value;这种特性可能导致问题,11章将介绍,可关闭这项特性.

10.4 this指针

  • 使用被称为this的特殊指针.this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法).
  • 注意:每个成员函数(包括构造函数和析构函数)都有一个this指针.this指针指向调用对象.如果方法需要引用整个调用对象,则可以使用表达式this.在函数的括号后面使用const限定符将this限定为const,这样将不能使用this来修改对象的值.然而,要返回的并不是this,因为this是对象的地址,而是对象本身,即*this(将解除引用运算符用于指针,将得到指针指向的值).现在可以将*this作为调用对象的别名来完成前面的方法定义.
  • 程序清单10.7 stock20.h
  • 程序清单10.8 stock20.cpp

10.5 对象数组

  • 程序清单10.9 usestock2.cpp

10.6 类作用域 
10.6.1 作用域为类的常量

  • C++提供了另一种在类中定义常量的方式—使用关键字static
class Bakery
{
private:
    static const int Months = 12;
    double costs[Months];
    ...
  • 在C++98中,只能使用这种技术声明值为整型或枚举的静态常量,而不能存储double常量.C++11消除了这种限制.

10.6.2 作用域内枚举(C++11)

  • 默认情况下,C++11作用域内枚举的底层类型为int.另外,还提供了一种语法,可用于做出不同的选择:
//underlying type for pizza is short
enum class : short pizza{Small, Medium, Large, XLarge};
  • :short将底层类型制定为short.底层类型必须为整型.在C++11中,也可使用这种语法来制定常规枚举的底层类型,但如果没有制定,编译器选择的底层类型将随实现而异.

10.7 抽象数据类型(abstract data type,ADT)

  • 私有部分必须表明数据存储的方式.
  • 程序清单10.10 stack.h
  • 程序清单10.11 stack.cpp
  • 程序清单10.12 stacker.cpp

10.8 总结

  • 面向对象编程强调的是程序如何表示数据.使用OOP方法解决变成问题的第一步是根据它与程序之间的接口来描述数据,从而制定如何使用数据.然后,设计一个类来实现该接口.一般来说,私有数据成员存储信息,公有成员函数(又称为方法)提供访问数据的唯一途径.类将数据和方法组合成一个单元,其私有性实现数据隐藏.
  • 通常,将类声明分成两部分组成,这两部分通常保存在不同的文件中.类声明(包括由函数原型表示的方法)应放到头文件中.定义成员函数的源代码放在方法文件中.这样便将接口描述与实现细节分开了.从理论上说,只需指导公有接口就可以使用类.当然,可以查看实现方法(除非只提供了编译形式),但程序不应该依赖于其实现细节.只要程序和类只通过定义接口的方法进行通信,程序员就可以随意地对任何部分做独立的改进,而不必担心这样做会导致意外的不良影响.
  • 如果希望成员函数对多个对象进行操作,可以将额外的对象作为参数传递给它.如果方法需要显式地引用调用它的对象,则可以使用this指针.由于this指针被设置为调用对象的地址,因此*this是该对象的别名.

10.9 复习题 
10.10 编程练习

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值