StupidBeauty
Read times:4210Posted at:Sat Jan 20 02:43:53 2018 - no title specified

《C++面向对象软件设计及构建》文章翻译:4.3定义具体实现,4.3 Defining the Implementation

一般概念

要实现一个类,需要定义以下事物:

  • •.数据 - 封装起来(隐藏的、私有的)变量,用于记录该对象的当前状态,以及

  • •.代码 - 这是一些成员函数(操作符、方法),它们根据数据和自身的输入参数来进行对应的动作。

数据,通常被称作类的“状态”变量或“实例”变量。状态,这个词,反映的是这样一种观点,即,一个对象,随着它的方法的执行,在时间上,是从一个状态移动到另一个状态。例如,调用某个Frame 对象的MoveTo 方法,那么,该对象就从位于某个位置的状态,迁移到位于另一个不同位置的状态。实例,这个词,表现的是,每个对象都是该类的一个实例,这个事实;对于某个特定的类,它的每个实例,都与其它实例不同,并且拥有着自己的私有数据。

在类中,封装起来的数据,可被该类的成员函数所访问。这种访问,显然是得到允许的,因为,成员函数和数据,是同一个实现的组成部分:要编写这种实现的话,作者就必须知道数据的细节。不过,对于非成员函数来说,数据就完全无法访问了。

简单示例

Location类,展示了,在类中,对数据和代码进行定义的语法,以及放置位置。Location 类的接口和实现,在下面两个表格中分别展示。将这两个表格合并起来,就形成Location 类的完整代码。

Location 类的接口

class Location {

   private:

     int  currentX, currentY;

   public:

         Location(int x, int y);

         Location();

     int Xcoord();

     int Ycoord();

        ~Location();

   };

Location 类的实现

   Location::Location(int x, int y)

      { currentX = x; currentY = y; }

    Location::Location ()

      { currentX = -1; currentY = -1; }

int Location::Xcoord()

      { return currentX; }

int Location::Ycoord()

      { return currentY; }

    Location::~Location() {}

注意,数据,是放置在类定义中的私有区域。自然地,妳可能会觉得奇怪,既然数据是隐藏的,为何又要将它以明文形式写在类定义中:为何不让数据既在概念上隐藏,又在实现代码中隐藏呢?事实上,将数据放置在类的定义中,是为了支持系统软件的整体设计,简化编译器和链接器的工作。所付出的代价就是,类定义的私有区域中声明的变量,会暴露出来,可被查看到(但无法被访问到)。

实现成员函数,一般的语法是:

ReturnType  ClassName::memberFunctionName ( ArgumentList ) { Statements }

其中

ReturnType(返回类型)

这个成员函数所返回的值的类型(例如int)

ClassName(类名字)

本函数,作为一个成员,所从属的那个类的名字(例如,Location)

memberFunctionName(成员函数名字)

这个成员函数的名字(例如,Xcoord)

ArgumentList(参数列表)

是这个成员函数所需要的参数的列表(例如,第一个构造函数需要两个参数,xy);如果函数没有参数,则这个列表为空。

Statements(指令代码)

这些代码,定义的是,当本个成员函数被调用时,所进行的行为。

对于每个成员函数,都要重复写出类名字(ClassName)。这是有必要的,因为,在允许成员函数重载的情况下,不同的类中可能会有些成员函数具有相同的名字和参数列表;在这种情况下,只能依靠类名字来区分这些不同的成员函数。另外,还需要注意,尽管我们之前在类定义中已经写出了完整的参数列表,此处仍然必须再次给出完整的参数列表。

更复杂的例子

Counter类,是又一个例子,它展示了一个类是如何实现的。以下展示了Counter 类的接口。Counter 类的私有数据中,包含着若干个整数,它们定义的是该个Counter 的状态。


Counter 类的私有数据

class Counter

{

  private:

    int initial;

    int value;

    Message* message;

  public:

    Counter(int start = 0);

    void ConnectTo(Message& msg);

    void Next();

    void Reset();

   ~Counter();

 };

Counter 的构造函数中,可以为该Counter 指定一个起始值,这个起始值默认为零。起始 值,起到两个作用: 它是该 Counter 被构造之后所立即具有的值;并且, 在对该 个Counter 调用 Reset() 操作时,它的值也会变成初始值。 Next() 方法 ,会使得该个Counter 的值增加1。 在构造函数中,那个 Message指针 ,被设置为空(null)。注意, 在这种情况下, 向某个变量赋予空指针值的语法如下:

message = (Message*)0

在这行代码中,将0这个整数值,进行类型转换,变成一个“指向Message 的指针”类型。


Counter 类的实现

Counter::Counter (int start)

{ initial = start;

  value   = start;

  message = (Message*)0;

 }

 void Counter::ConnectTo(Message& msg)

 { message =&msg; }

 void Counter::Next()

 { value = value + 1;

   char asString[10];

   ostrstream convert(asString, 10);

   conver << value << '\0';

   if (message) message->SetText(asString);

 }

 void Counter::Reset()

 { value = initial; }

 Counter::~Counter() {}

注意, Next() 方法中,使用 了字符串流来将整数值转换成字符串。

再来回顾封装

为了更深层次地理解封装这个概念,考虑一下这种情况:对Counter 类进行扩展,使得,可以对两个Counter 对象进行比较。通常情况下,需要对两个Counter 对象进行比较,以确定它们是否代表着同一个值;也就是说,这两个对象所封装的整数值,是否相等。在已有的Counter 类接口中,无法做这种比较,因为,没有哪个方法能够返回Counter 的值。当然,我们可以向Counter 类的公有接口中添加这样的一个取值方法,但是,通常情况下来说,将某个类的内部组件暴露给类外部的代码来查看,并不是一件好事。

我们所需要的这个比较方法,名为equal,可按照如下所示的方式来定义及实现。但是,要注意,此处只展示了Counter 类中与之相关的那一部分。


Counter 类中添加 Equal 方法

class Counter{                        

   private:

     ...

     int  value;

     ...

   public:

         ...

     int Equal(Counter& other);  // 测试两个数值是否相等

         ...

  };      

 // 以下是实现代码

  int Counter::Equal(Counter& other)

   { if (value == other.value)

          return 1;  // 相等

     else return 0;  // 不相等

   }            

此处,Equal 方法的参数("other"),是通过引用来传递的。这样做的目的是,避免对被调用的对象所要与之相比较的那个Counter 对象进行复制。

由于封装是一个类属性,而不是一个对象属性,所以,Equal 方法的这种实现,并未破坏封装属性。表面上来看,似乎是破坏了封装性,因为,在此处,我们直接访问了"other"对象的值,而并未使用任何的公有取值方法。具体来说,"other.value",这个表达式,直接地访问到了"other"对象中那个本来预期是被封装起来的数据。然而,实际上,对于封装性,应当在类的角度来看:在类中定义的私有数据,只能够被该个类的方法(成员函数)所访问到。由于Equal方法是Counter 类的一个成员函数,所以,对于它能够访问到的任何一个Counter 对象,它都能够访问到该个对象中属于该个类的任何一个私有数据成员。这并未破坏封装性,因为,封装性,这个概念,它的含义在于,限制了哪些方法可以访问到某个类的私有数据,而不是限制哪些对象可以访问某个类的私有数据。为了更清晰地理解这个概念,假设一下,Counter 类的内部数值的名字或类型发生了改变。在这种情况下,唯一需要针对这种改变做出相应调整的代码,就是Counter 类本身的代码。另外,也要注意,由于某个类的方法可以访问到该个类的所有对象的数据,因此,可以避免引入某些不必要的取值方法。

任务

  1. 1.展示,该如何在Location 类中定义及实现一个Equal 方法,这个方法的作用是,确定两个Location 对象的x 和y 坐标是否相同。利用Location 类的取值方法Xcoord 和Ycoord 来实现这个方法。

  2. 2.展示,该如何在Location 类中定义及实现一个Equal 方法,这个方法的作用是,确定两个Location 对象的x 和y 坐标是否相同。在实现这个方法的过程中, 不要 使用Location 类的取值方法Xcoord 和Ycoord。

  3. 3.展示,该如何在Counter 类中定义及实现一个LessThan 方法,这个方法的作用是,确定,某个Counter 对象的值,是否严格小于另一个Counter 对象的值。

  4. 4. 不要参考本书提供的代码,展示,如何实现Shape 类。

  5. 5. 为Location 类定义及实现一个扩展,具体来说,加入一个方法MoveBy(int dx, int dy)。这个方法的作用是,按照两个参数中提供的值,改变Location 对象的实际坐标。例如,假设某个Location 对象的坐标是(100,100),那么,在调用了MoveBy(20, -10)操作之后,该对象的坐标将变成(120, 90)。

  6. 6.设计、实现并测试某个代表简单分数的类。例如,可以像这样使用这个类:

    Fraction half(1,2);

    Fraction quarter(1,4);

    Fraction sum = half.plus(quarter);

    向这个类中加入其它有趣且有用的方法。

叙利亚难民

Your opinions
Your name:Email:Website url:Opinion content:
- no title specified

HxLauncher: Launch Android applications by voice commands