第12讲——对象和类

【抽象和类】

引言:生活中充满复杂性,处理复杂性的方法之一是简化和抽象。如果我们要用信息与用户之间的的接口来表示计算,那么抽象将是至关重要的。也就是说,将问题的本质特征抽象出来,并根据特征来描述解决方案。在上一讲的垒球统计数据示例中,接口描述了用户如何初始化、更新和显示数据。抽象是通往用户定义类型的捷径,在C++中,用户定义类型指的是实现抽奖接口的类设计。

1.1  类型是什么

  当我们看到一个给定的基本数据类型,我们会想到:

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

  具体来说,我们可以根据数据在内存中如何存储来考虑其数据类型(例如,char占用1个字节的内存,而double占用8个字节的内存),也可以根据能对数据进行的操作来定义其数据类型(例如,int类型可以使用所有的算术运算,可对整数执行加、减、乘、除运算,而且还可以对它们使用求模运算符%)。而指针需要的内存数量很可能与int相同,甚至可能在内部被表示为整数,但不能对指针执行与整数相同的运算(例如,不能将两个指针相乘,这种运算将毫无意义)。因此,将变量声明为int或float指针时,不仅仅是分配内存,还规定了可对变量执行的操作。

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

1.2  C++中的类

  我们来尝试定义一个类:

  • 类声明:数据成员+成员函数(前者描述数据部分,后者描述公有接口)
  • 类方法定义:描述如何实现类成员函数

  我们仿佛感觉出:类声明提供了类的蓝图,而方法定义则提供了细节。

  什么是接口?

  接口是一个共享框架,供两个系统(如计算机和打印机之间或者用户和计算机程序之间)交互时使用。我们举一个例子:假设用户是我,程序是字处理器。我使用字处理器时,不能直接将脑子中想到的词传输到计算机内存中,而必须同程序提供的接口交互。我敲打键盘时,计算机将字符显示到屏幕上;我移动鼠标时,计算机移动屏幕上的光标,我无意间单击鼠标时,计算机对我输入的段落进行奇怪的处理。程序接口将我的意图转换为存储在计算机中的具体信息。

  对于类,我们常谈公共接口。在这里,公众(public)是使用类的程序,交互系统由类对象组成,而接口由编写类的人提供的方法组成。接口让程序员能够编写与类对象交互的代码,从而让程序能够使用类对象。举个例子:要计算string对象中包含多少个字符,我们无需打开对象,而只需使用string类提供的size()方法。类设计禁止公共用户直接访问类,但公众可以使用方法size()。方法size()是用户和string类对象之间的公共接口的组成部分。通常,方法getline()是istream类的公共接口的组成部分,使用cin的程序不是直接与cin对象内部交互来读取一行输入,而是使用getline()。

  如果希望更人性化,我们不要将使用类的程序视为公共用户,而将编写程序的人视为公共用户。然而,要使用某个类,必须了解其公共接口;要编写类,必须创建其公共接口。

  我们编程实现类时,通常将接口(类定义)放在头文件中,并将实现(类方法的代码)放在源代码文件中。在头文件中,对于数据成员,我们要进行定义,而对于成员函数,我们可以进行定义,也可以用原型表示(那这部分成员函数将在源代码文件中定义)。因此,对于描述函数接口而言,原型就够了。

  下面我们看一个头文件,它是Stock类的类声明:

// stock00.h -- Stock class interface
// version 00
#ifndef STOCK00_H_            //使用#ifndef访问多次包含同一个文件
#define STOCK00_H_

#include <string>  

class Stock                  // class declaration
{
private:
    std::string company;
    long shares;
    double share_val;
    double total_val;
    void set_tot() { total_val = shares * share_val; }
public:
    void acquire(const std::string & co, long n, double pr);
    void buy(long num, double price);
    void sell(long num, double price);
    void update(double price);
    void show();
};    // note semicolon at the end

#endif

stock00.h

类最吸引人的特性——将数据和方法组合成一个单元。

  类成员的访问控制也是我们需要着重考虑的。

  C++提供了private/public/protected关键字来描述类成员的属性。使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数(或友元函数)来访问对象的私有成员。因此,公有成员函数被称为是程序和对象的私有成员之间的桥梁,它提供了对象和程序之间的接口。防止程序直接访问数据被称为数据隐藏。

  由于数据隐藏是OOP主要的目标之一,因此类的数据项通常放在私有部分,组成类接口的成员函数放在公有部分(否则就无法从程序中调用这些函数)。尽管如此,我们仍可以将成员函数放在私有部分,虽然程序无法调用,但是公有方法却可以使用它。通常,程序员使用私有成员函数来处理不属于公有接口的实现细节。

  类和结构

  类描述看上去很像是包含成员函数以及public和private可见性标签的结构声明。实际上,C++对结构进行了扩展,使之具有与类相同的特性。它们之间唯一的区别是——结构默认访问类型是public,而类为private。C++程序员通常使用类来实现类描述,而把结构限制为只表示纯粹的数据对象。

1.3  实现类成员函数

  在类声明的文件中,有部分成员函数是用原型表示的。那么我们需要为这些成员函数提供代码,我们称之为成员函数定义,这个过程在实现文件中完成。

  成员函数定义有两个重要特征:

  1. 使用作用域解析运算符(::)来标识函数所属的类;
  2. 类方法可以访问类的private组件。

  对于第一个特征,我们举例如下:

void Stock::update(double price)

   它意味着我们定义的update()函数是Stock类的成员。这不仅将update()标识为成员函数,还意味着我们可以将另一类的成员函数也命名为update()。

  因此,作用域解析运算符确定了方法定义对应的类的身份。我们说,标识符update()具有类作用域。Stock类的其他成员函数不必使用作用域解析运算符就可以使用update()方法,这是因为它们属于同一个类,因此update()对它们是可见的。

  类方法的完整名称中包括类名。我们说,Stock::update()是函数的限定名,而简单的update()是全名的缩写(非限定名),他只能在类作用域中使用。

  对于第二个特征,我们要知道的是,如果使用非成员函数访问该类的私有数据成员,编译器将禁止这么做(友元函数例外)。

  掌握了上面这两点,我们便可实现类方法了,我们将这个过程放在一个独立的实现文件中(因此需要包含头文件stock00.h,让编译器能够访问类定义):

// stock00.cpp -- implementing the Stock class
// version 00
#include <iostream>
#include "stock00.h"

void Stock::acquire(const std::string & co, long n, double pr)
{
    company = co;
    if (n < 0)
    {
        std::cout << "Number of shares can‘t be negative; "
                  << company << " shares set to 0.\n";
        shares = 0;
    }
    else
        shares = n;
    share_val = pr;
    set_tot();
}

void Stock::buy(long num, double price)
{
     if (num < 0)
    {
        std::cout << "Number of shares purchased can‘t be negative. "
             << "Transaction is aborted.\n";
    }
    else
    {
        shares += num;
        share_val = price;
        set_tot();
    }
}

void Stock::sell(long num, double price)
{
    using std::cout;
    if (num < 0)
    {
        cout << "Number of shares sold can‘t be negative. "
             << "Transaction is aborted.\n";
    }
    else if (num > shares)
    {
        cout << "You can‘t sell more than you have! "
             << "Transaction is aborted.\n";
    }
    else
    {
        shares -= num;
        share_val = price;
        set_tot();
    }
}

void Stock::update(double price)
{
    share_val = price;
    set_tot();
}

void Stock::show()
{
    std::cout << "Company: " << company
              << "  Shares: " << shares << ‘\n‘
              << "  Share Price: $" << share_val
              << "  Total Worth: $" << total_val << ‘\n‘;
}

stock00.cpp

  4个成员函数都设置或重新设置了total_val成员值。这个类并非将计算代码编写4次,而是让每个函数都调用set_tot()函数。我们知道,set_tot()函数是在头文件中定义的私有成员函数,所以它只是实现代码的一种方式,而不是公有接口的组成部分。这种方法的主要价值在于:通过使用函数调用(而不是每次重新输入计算代码)可以确保执行的计算完全相同。另外,如果必须修订计算代码,则只需在一个地方进行修改即可。

  我们称在类声明中定义的函数为内联函数,因此Stock::set_tot()是一个内联函数。类声明常将短小的成员函数作为内联函数,set_tot()符合这样的要求。内联函数的特殊规则要求——在每个使用它们的文件中都对其进行定义。那么确保内联定义对多文件程序中的所有文件都是可用的、最简便的方法是:将内联定义放在定义类的头文件中。

1.4  使用类

  知道如何定义类及其方法后,我们便可以创建一个这样的程序,它创建并使用类对象:

// usestock0.cpp -- the client program
// compile with stock00.cpp
#include <iostream>
#include "stock00.h"

int main()
{
    Stock fluffy_the_cat;
    fluffy_the_cat.acquire("NanoSmart", 20, 12.50);
    fluffy_the_cat.show();
    fluffy_the_cat.buy(15, 18.125);
    fluffy_the_cat.show();
    fluffy_the_cat.sell(400, 20.00);
    fluffy_the_cat.show();
    fluffy_the_cat.buy(300000,40.125);
    fluffy_the_cat.show();
    fluffy_the_cat.sell(300000,0.125);
    fluffy_the_cat.show();
    return 0;
}

usestock0.cpp

  C++的目标是使得使用类与使用基本的内置类型(如char和int)尽可能相同。要创建类对象,可以声明类变量,也可以使用new为类对象分配存储空间。可以将对象作为函数的参数和返回值,也可以将一个对象赋给另一个。C++提供了一些工具,可用于初始化对象,让cin和cout识别对象,甚至在相似的类对象之间进行自动类型转换。

  注意,main()只是用来测试Stock类的设计。当Stock()类的运行情况与预期的相同后,便可以在其他程序中将Stock类作为用户定义的类型使用。要使用新类型,最关键的是要了解成员函数的功能,而不必考虑其实现细节。

  客户/服务器模型

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

【类的构造函数和析构函数】

时间: 2024-08-28 00:05:30

第12讲——对象和类的相关文章

第八章第1-2讲:python之对象

第1讲:魔法——对象 1.对象出发点:是提高编程的效率,解决重复的劳动,人性懒惰的使然. 2.对象包含2个方面: 属性:如何描述对象 方法:对该像能做什么? 对象=属性+方法 第2讲:创建对象 1. 类:对象 类:具有相同属性和方法的集合(好比户型图) 对象:讲抽象实现为显示的存在(根据户型图实现的一个个相同的布局格局的房子) 2.定义类: class Classname: 属性 方法 3.创建类与实例化对象 class Person: def setName(self,name): self.

Java核心技术(三) —— 对象与类(1)

本文将对Java程序设计的对象和类进行深入详细介绍,主要涉及以下内容: - 面向对象程序设计 - 如何创建标准Java类库中的类对象 - 如何编写自己的类 1.OOP概述 面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分.在OOP中,不必关心对象的具体实现,只要能够满足用户的需求即可. OOP中,数据是第一位的,然后再考虑操作数据的算法. 1.1 类 类是构造对象的模板或蓝图,可以将类想象成制作小甜饼的切割机,将对象想象成小甜饼.由类构造对象的过程称为创建类的实

python的类和对象(类的静态字段)

转自:http://www.cnblogs.com/Eva-J/p/5044411.html 什么是静态字段 在开始之前,先上图,解释一下什么是类的静态字段(我有的时候会叫它类的静态变量,总之说的都是它.后面大多数情况可能会简称为类变量.): 我们看上面的例子,这里的money就是静态字段,首先看它的位置,是在father类中,而不是在__init__中.那么一个小小的静态字段,我为什么要特意写一篇番外给它呢?耐着性子看下去,你就会发现一个小小的类变量,却折射出了整个类的世界. 首先我们先来解释

[Java学习笔记]-Java对象和类

Java是完全面向对象的高级语言,其基本的操作基本都是针对相应的对象和类.面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分.对应面向对象的语言,还有一种面向过程的语言,如C语言.面向对象的语言是在面向过程语言的基础上发展而来的.面向对象(OOP,全称为Object-Oriented-Programer,下文简称为OOP)相对于面向过程的语言而言,其优势在于很多问题的解决方法被封装在对象里,有时只需要创建这样的对象就可以解决我们的问题,而不必关心其具体实现细节,这

Swift中文教程(五)--对象和类

原文:Swift中文教程(五)--对象和类 Class 类 在Swift中可以用class关键字后跟类名创建一个类.在类里,一个属性的声明写法同一个常量或变量的声明写法一样,除非这个属性是在类的上下文里面,否则,方法和函数的写法也是这样: 1 class Shape { 2 var numberOfSides = 0 3 func simpleDescription() -> String { 4 return "A shape with \(numberOfSides) sides.&q

javascript学习笔记---ECMAScriptECMAScript 对象----定义类或对象

使用预定义对象只是面向对象语言的能力的一部分,它真正强大之处在于能够创建自己专用的类和对象. ECMAScript 拥有很多创建对象或类的方法. 原始的方式 因为对象的属性可以在对象创建后动态定义(后绑定),类似下面的代码: var oCar = new Object; oCar.color = "blue"; oCar.doors = 4; oCar.mpg = 25; oCar.showColor = function() { alert(this.color); };不过这里有一

C++11 function用法 可调用对象模板类

std::function<datatype()> ()内写参数类型 datatype 代表function的返回值 灵活的用法.. 代码如下 1 #include <stdio.h> 2 #include <iostream> 3 #include <map> 4 #include <functional> 5 #include <stdlib.h> 6 using namespace std; 7 8 // 普通函数 9 int

韩顺平循序渐进学java 第12讲 多态

12.1 多态-四大特征之四 12.1.1 概念 所谓多态,就是指一个引用(类型)在不同情况下的多种状态. 可以这样理解:多态是指通过指向父类的指针,来调用在不同子类中实现的方法. 12.1.2 注意事项 在讲解多态的时候,我们注意到这样一个现象-类型的转换. 1.java允许父类的引用变量引用它的子类的实例(对象) Animal animal=new Cat(); 2.关于类型转换还有一些细节要求,比如子类能不能转换成父类,有什么要求等等... 多态代码演示: 1 /**日期:2016-03-

对于python,一切事物都是对象,对象基于类创建

新建列表.新建string字符串 1 li1 = [1, 2, 3, 4] 2 li2 = list([1, 2, 3]) 3 4 s1 = "abc" 5 s2 = str("abc") 6 7 print(li1) 8 print(type(li1)) 9 10 print(li2) 11 12 print(s1, s2) 13 14 #显示结果: 15 #[1, 2, 3, 4] 16 #<class 'list'> 17 #[1, 2, 3] 1