zoukankan      html  css  js  c++  java
  • 7.1.2 定义改进的Sales_date类

    7.1.2 定义改进的Sales_date类

             改进后的类的数据成员将与之前定义的版本保持一致,它们包括:bookNO,string类型,表示ISBN编号;units_sold,unsigned类型,表示某本书的销量;以及revenue,double类型,表示这本书的总销售收入。

             如前所述,我们的类将包含两个成员函数:combine和isbn。此外,我们还将赋予Sales_date另一个成员函数用于返回售出书籍的平均价格,这个函数被命名为avg_price。因为avg_price的目的并非通用,所以它应该属于类的实现的一部分,而非接口的一部分。

            定义和声明成员函数的方式与普通函数差不多。成员函数的声明必须在类的内部,它的定义则可以在类的内部也可以在类的外部。作为接口组成部分的非成员函数,例如add、read和print等,它们的定义和声明都在类的外部。

           由此可知,改进的Sales_data类型应该如下所示:

    struct Sales_data{
          // 新成员:关于Sales_data对象的操作
         std :: string isbn()  const { return bookNo; }
         Sales_data & combine(const Sales_data&);
         double avg_price() const;
         // 数据成员相之前没有变化
         sd :: string bookNo;
         unsigned units_sold = 0;
         double revenue = 0.0;
    };
    // Sales_data的非成员接口函数
    Sales_data add(const Sales_data&, const Sales_data&);
    std :: ostream &print(std :: ostream&, const Sales_data&);
    std :: istream &read(std :: istream, Sales_data&);

    注:定义在类的内部的函数是隐式的inline函数。
    定义成员函数

            尽管所有成员都必须在内部声明,但是成员函数体可以定义在类内也可以定义在类外。对于Sales_data类来说,isbn函数定义在了类内,而combine和avg_price定义在了类外。

           我们首先介绍isbn函数,它的形参列表为空,返回值是一个string对象:

            std :: string isbn( )   const { return bookNo; }

    和其他函数一样,成员函数体也是一个语句快。在此例中只有一条return语句,用于返回Sales_data对象的bookNo数据成员。关于isbn函数一间很有意思的事情是:它是如何获得bookNo成员所依赖的对象呢?

    引入this

            让我们再一次观察对isbn成员函数的调用:

            total.isbn( )

    在这里,我们使用了点运算符来访问total对象的isbn成员,然后调用它。

            在后面我们将介绍一种例外的形式,当我们调用成员函数时,实际上是在替某个对象调用它。如果isbn指向Sales_data的成员(例如bookNo),则它隐式地指向调用该函数的对象的成员。在上面所示的调用中,当isbn返回bookNo时,实际上它隐式地返回total.bookNo。

            成员函数通过一个名为this的额外的隐式的参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化this。例如,如果调用

            total.isbn( )

    则编译器负责把total的地址传递给isbn的隐式形参this,可以等价地认为编译器将该调用重写成了如下的形式:

            // 伪代码,用于说明调用成员函数的实际执行过程

            Sales_data :: isbn(&total)

    其中,调用Sales_data的isbn成员时传入了对象total的地址。

            在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无须通过成员访问运算符做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看作this的隐式引用,也就是说,当isbn使用bookNo时,它隐式地使用this指向的成员,就像我们书写了this->bookNo一样。

            对于我们来说,this形参是隐式定义的。实际上,任何自定义名为this的参数或变量的行为都是非法的。我们可以在成员函数体内部使用this,因此尽管没必要,但我们还是能够把isbn定义成如下的形式:

            std :: string isbn( )  const  { return this ->bookNo; }

            因为this的目的总是指向“这个”对象,所以this是一个常量指针,我们不允许改变this中保存的地址。

    引入const成员函数

           isbn函数的另一个关键之处是紧随参数列表之后的const关键字,这里,const的作用是修改隐式this指针的类型。

            默认情况下,this的类型是指向类类型非常量版本的常量指针。例如在Sales_data成员函数中,this的类型是Sales_data *const。尽管this是隐式的,但是它仍然需要遵循初始化规则,意味着(在默认情况下)我们不能把this绑定到一个常量对象上。这一情况也就使得我们不能在一个常量对象上调用普通的成员函数。

            如果isbn是一个普通函数而且this是一个普通的指针参数,则我们应该把this声明成const Sales_data * const。毕竟在isbn函数体内不会改变this所指的对象,所以把this设置为指向常量的指针有助于提高函数的灵活性。

            然而,this是隐式的并且不会出现在参数列表中,所以在哪儿将this声明成指向常量的指针就成为我们必须面对的问题。C++语言的做法是允许把关键字const放在成员函数的参数列表之后,此时,紧跟在参数列表后面的const表示this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数

            可以把isbn的函数体想象成如下的形式:

            // 伪代码,说明隐式的this指针是如何使用的

            // 下面的代码是非法的:因为我们不能显示地定义自己的this指针

            // 谨记此处的this是一个指向常量的指针,因为isbn是一个常量成员

            std :: string Sales_data :: isbn(const Sales_data *const this)  { return this -> bookNo; } 

    注:常量对象,以及常量对象的引用或指针都只能调用常量成员函数。

    类的作用域和成员函数

            回顾之前所学的知识,类本身就是一个作用域。类的成员函数的定义嵌套在类的作用域之内,因此,isbn中用到的名字bookNo其实就是定义在Sales_data内的数据成员。

            值得注意的是,即使bookNo定义在isbn之后,isbn也还是能够使用bookNo。就如后面将要学习的那样,编译器分两步处理类:首先编译成员的声明,然后才轮到成员函数体(如果有的话)。因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序。

    在类的外部定义成员函数

            像其他函数一样,当我们在类的外部定义成员函数时,成员函数的定义必须与它的声明匹配。也就是说,返回类型、参数列表和函数名都得与类内部的声明保持一致。如果成员被声明成常量成员函数,那么它的定义也必须在参数列表后明确指定const属性。同时,类外部定义的成员的名字必须包含它所属的类名

             double Sales_data :: avg_price( )  const  {

                   if (units_sold)

                         return revenue / units_sold;

                   else

                         return 0;

              }

    函数名Sales_data :: avg_price使用作用域运算符来说明如下事实:我们定义了一个名为avg_price的函数,并且该函数被声明在类Sales_data的作用域内。一旦编译器看到这个函数名,就能理解剩余的代码是位于类的作用域内的。因此,当avg_price使用revenue和units_sold时,实际上它隐式地使用了Sales_data的成员。

    定义一个返回this对象的函数

           函数combine的设计初衷类似于复合赋值运算符+=,调用该函数的对象代表的是赋值运算符左侧的运算对象,右侧运算对象则是通过显示的实参被传入函数

            Sales_data & Sales_data :: combine(const Sales_data &rhs)

            {

                     units_sold += rhs.units_sold;  // 把rhs的成员加到this对象的成员上

                     revenue += rhs.revenue;

                     return *this;         // 返回调用该函数的对象

             }

    当我们的交易处理程序调用如下的函数时,

            total.combine(trans);    // 更新变量total当前的值

    total的地址被绑定到隐式的this参数上,而rhs绑定到了trans上。因此,当combine执行下面的语句时,

            units_sold += rhs.units_sold;     // 把rhs的承压un添加到this对象的成员中

    效果等同于求total.units_sold和trans.units_sold的和,然后把结果保存到total.units_sold中。

            该函数一个值得关注的部分是它的返回类型和返回语句。一般来说,当我们定义的函数类似于某个内置运算符时,应该令该函数的行为尽量模仿这个运算符。内置的赋值运算符把它的左侧运算对象当成左值返回,因此为了与它保持一致,combine函数必须返回引用类型。因为此时的左侧运算对象是一个Sales_data的对象,所以返回类型应该是Sales_data&。

            如前所述,我们无须使用隐式的this指针访问函数调用者的某个具体成员,而是需要把调用函数的对象当成一个整体来访问:

            return *this;     // 返回调用该函数的对象

    其中,return语句解引用this指针以获得执行该函数的对象,换句话说,上面的这个调用返回total的引用。

           

  • 相关阅读:
    解决“google快照无法打开”的简单而有效的方法~
    在Struts2里面嵌入Spring
    HDU
    设计模式大总结(二)
    Node.js入门笔记
    草图检索和识别[开源]
    2019-10-31-VisualStudio-断点调试详解
    2019-10-31-VisualStudio-断点调试详解
    2019-9-2-C#-设计模式-责任链
    2019-9-2-C#-设计模式-责任链
  • 原文地址:https://www.cnblogs.com/wyxsq/p/5275614.html
Copyright © 2011-2022 走看看