C++ Primer第七章①

C++ Primer

第七章 类

这一章是C++ 面向对象的重头戏,首先我来说一下我对面向对象的理解,对象就是生活中的一些事物,比如汽车,电脑等,面向对象的意思是,我们把这些汽车等对象做成一个类,这个类很好地描述了汽车这个对象的特征,那我们就可以去实例化这个类,从而产生对象,就是一辆辆的汽车,而汽车的使用者不需要关心汽车是怎么造出来的,只要会开车,是老司机就好了,以后要转Java或者C#的同学这一章要格外认真地看,因为如果你完全搞定了C++里面的类,其他的也就不在话下了,因为毕竟它是最烦的。 类的基本思想是数据抽象和封装:

  • 数据抽象: 这是最难的部分,因为你要设计出符合项目的一些类是比较难的,这是从无到有的过程,要多多练习才行。数据抽象有一些基本的概念了解下:
    1. 接口:用户所能执行的操作
    2. 类的实现:包括类的数据成员、负责接口实现的函数以及定义类所需的各种函数
  • 封装:实现了类的接口和实现的分离。封装后的类隐藏了它的实现细节,类的用户只能使用接口而无法访问实现部分;说人话:我们来实现一个类给人家用,就相当于我们造了一个汽车模板来生产汽车,人家只需要知道怎么开车,不需要知道也不能知道这个车怎么造。

定义抽象数据类型

书店老板现在有一个任务给我们,完成后奖励2000块。写一个程序来自动计算算每一本书的总销售额、售出册数和平均售价。

设计类

设计部分我就直接写出来了: 这个类命名为Sales_data,它的接口要包含以下操作

  • isbn成员函数,返回对象的ISBN编号(用来确认是不是同一种书)
  • combine成员函数,用来相加(两本书相加的时候保证是它们的售价相加)
  • read函数,将数据从istream读入到Sales_data对象中
  • print函数,将Sales_data对象输出ostream

设计类的一个很好的窍门是,你先假装它实现了,去用它,然后再去实现它: 接下来的代码注意啦,第一次使用类,来看看有多好用(假如你没有类的话,你可以用C写写试试):

Sales_data current; //保存当前求和结果的变量
if(read(cin, current)) //如果有交易输入
{
    Sales_data next; //用于保存下一条交易数据
    while(read(cin, next))
    {
        if(current.isbn() == next.isbn()) //如果是同一种书
        {
            current.combine(next); //加上这个交易额和交易数量
        }
        else
        {
            print(cout, current) << endl; //输出目前这种书的结果
            current = next; //更新书本
        }
    }
    print(cout, current) << endl; //输出最后一条
}
else
{
    cout << "没输入啊老板" << endl; //提醒用户没输入
}

这个程序要求书本按照书籍种类输入,就是说ABA这样输入会打印出ABA,最好是AAB输入,这样A就被加和了。 总之,使用类可以让我们专注在业务逻辑上,不用管类的实现,上面的程序基本是人去做这个事情的顺序,我们现在用这个小程序就代替了,当然,我们还没实现类呢。

定义类

如下代码,我还加了一些使用的函数定义:

struct Sales_data
{
    //数据成员
    string bookNo; //书号
    unsigned units_sold = 0; //售出册数
    double revenue = 0; //总销售收入

    //成员函数
    string isbn() const //返回书本isbn号,这里的const待会解释
    {
        return bookNo;
    }
    Sales_data& combine(const Sales_data&); //函数声明
    double avg_price() const; //返回售出书籍的均价,这里的const也待会解释
}
定义成员函数

我们来看一下isbn函数,你别看这个函数简单,细细推导的话,我们可以从这个函数中学到很多东西。 我们先来看一下我们是怎么调用这个函数的:

current.isbn()

我们是用点运算符来访问current对象的isbn成员来调用的,想想是谁在调用成员函数呢?是这个类的对象在调用,所以isbn函数返回的bookNo是current这个对象的bookNo,所以实际的过程是这样的:

Sales_data::isbn(&current); //调用的时候传入了current的地址

而在类内部,任何对类成员的直接访问都被看作this的隐式引用,所以,我们也可以把isbn函数写成:

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

因为this总是指向调用它的这个对象,所以C++把this看作常量指针,意思是指针本身是常量,我们不允许改变this中保存的地址。 第二个可以从isbn函数中学习的就是const,我们先来看没有const的情况:

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

那我们来调用试试:

const Sales_data test;
test.isbn(); //这里会报错

为什么会报错呢?我们来分析下,上面这句调用的话用this写出来的话等价于:

Sales_data::isbn(&test);
//在类内部执行的时候是这样的
string isbn() 
{
    return this->bookNo;
}

问题就出在this指针上,因为this是指针常量,它只是本身值不变,但它可以改变它所指对象的值(它是顶层const),而它所指向的test对象是const的,所以引发了矛盾。 也就是说,我们如果不加const的话,这个函数只能被非const对象调用。 现在,我们知道了const的作用了:修改隐式this指针的类型,使它成为顶层和底层都是const的类型。 本质上isbn的函数是这样的:

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

怎么样,还是有点复杂吧,然而你知其所以然之后,你就可以一直写成最简单的形式了啊:

string isbn() const //返回书本isbn号,这里的const待会解释
{
    return bookNo;
}

C++官方说法:this是隐式地,没办法直接将它声明成指向常量的指针,于是设计者们就想出了这样的办法:把const放在参数列表之后表示this是一个指向常量的指针(它本身也是个常量哦)。 像这样使用const的成员函数被称作常量成员函数

graph LR
常量成员函数-->常量和非常量对象都可调用
graph LR
普通成员函数-->非常量对象才能调用

一定要理解上面的图,不理解我说了那么多就白说了。

编译器对类的处理顺序

编译器分两步处理类:

  1. 首先编译成员变量的声明
  2. 再编译成员函数 所以啊,成员函数可以随意使用类中的其他成员变量不用管它们出现的次序。
在类的外面也可以定义其成员函数

我们来定义一下类中声明的avg_price函数,只要在函数名前面加一个作用域说明符::就行了:

double Sales_data::avg_price() const
{
    if(units_sold>0) //有卖出书的话
    {
        return revenue/units_sold;
    }
    else
    {
        return 0;
    }
}

编译器看到 avg_price 这个函数是在 Sales_data 中,就会把它当成在类内部了。


下面我们来看一下combine函数的定义:

Sales_data& Sales_data::combine(const Sales_data &rhs)
{
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this; //返回调用该函数的对象
}

上面的&都是引用,不是取地址啊,不要连这个都搞错了。 这个函数值得介绍的是返回值,为什么要这样返回呢?为了可以这样调用: a.combine(b).combine(c) 至少我觉得是这样。

定义类相关的非成员函数

类的设计者常常需要定义一些辅助函数,我们接下来就会以Sales_data类为例来介绍一些函数;尽管这些函数定义的操作从概念上来说属于类的接口组成部分,但它们其实不属于类本身。

定义read和print函数

我们来定义一下前面用过的这两个函数:

//输入的信息包括ISBN、售出总数和售出价格
istream &read(istream &is, Sales_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}
//print自己写

这个函数就解释一个地方,返回类型是istream,因为IO类不能被拷贝。 这样2000块差不多到手了,之后的章节会介绍更多细节。

#C++工程师#
全部评论

相关推荐

头像
11-09 17:30
门头沟学院 Java
TYUT太摆金星:我也是,好几个华为的社招找我了
点赞 评论 收藏
分享
点赞 评论 收藏
分享
8 3 评论
分享
牛客网
牛客企业服务