C++ Primer 5th学习笔记6 类

  类的基本思想是数据抽象和封装,类的接口包括用户所能执行的操作;类的实现包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。

1 定义抽象数据类型

 从改进的Sales_data开始,其结构如下图所示:

struct Sales_data
{
    std::string  isbn() const {return bookNo; }
    Sales_data& conbine(const Sales_data&);
    double avg_price() const;
    std::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&);

1.1 定义成员函数

  成员函数可以通过一个名为this的额外隐式参数来访问调用它的那个对象。如下面的isbn函数,可以改为另外一种方式:

//原函数
std::string  isbn() const {return bookNo; }
//采用this定义
std::string  isbn() const {return this->bookNo; }

isbn函数中参数列表之后的const关键字使用是:修改隐式this指针的类型

类的作用域和成员函数
  编译器在处理类时,有两步:首先编译成员的声明,若有成员函数体的话,才去编译成员函数体,因此函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序

在类的外部定义成员函数
  当在类的外部定义成员函数时,成员函数的定义必须与它的声明匹配。如果成员函数被声明成常量成员函数,则其定义也必须在参数列表后明确指定const属性,同时,类外部定义的成员的名字必须包含它所属的类名

double Sales_data::avg_price() const
{
    if (units_sold)
        return revenue/units_sold;
    else
        return 0;
    
}

定义一个返回this对象的函数
  函数combine的设计类似于复合赋值运算符±,调用该函数的对象代表的是赋值运算符左侧的运算对象,右侧运算对象则通过显式的实参被传入函数:

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

return语句解引用this指针以获得执行该函数的对象,即函数调用返回tatal的引用

1.2 定义类相关的非成员函数

定义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;
}
ostream &print(ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " "
       << item.revenue << " " << item.avg_price();
    return os;
}

read函数从给定流中将数据读到给定的对象,print函数则负责将给定内容的对象打印到给定的流中。
Tip:read和print分别手一个各自IO类型的引用作为其参数,这是因为IO类属于不能被拷贝的类型,因此我们只能通过引用来传递;由于读取和写入的操作会改变流的内容,因此两个函数接受的都是普通引用。

1.3 构造函数

  构造函数:控制类的对象初始化的函数,作用是初始化类对象的数据成员。构造函数的名字和类名相同;构造函数不能被声明成const,构造函数在const对象的构造过程中可以向其写值。
定义Sales_data的构造函数,此时最初的类的程序变为:

struct Sales_data
{
    //新增的构造函数
    Sales_data() = default;        //声明默认构造函数
    //构造函数初始值列表
    Sales_data(const std::string &s): bookNo(s) {}
    Sales_data(const std::string &s, unsigned n, double p):
              bookNo(s), units_sold(n), revenue(p *  n){}
              
    Sales_data(std::istream &);
    //之前已有的其他成员
    std::string  isbn() const {return bookNo; }
    Sales_data& conbine(const Sales_data&);
    double avg_price() const;
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
    
}

在类的外部定义构造函数
  在类的外部定义构造函数时,必须指明该构造函数是哪个类的成员,示例如下

Sales_data::Sales_data(std::istream &is)
{
     read(is, *this);
}

默认构造函数的作用
 默认初始化在以下情况下发生:

  • 当我们在块作用域内不使用任何初始值定义一个非静态变量或数组时;
  • 当一个类本身含有类类型的成员且使用合成的默认构造函数是;
  • 当类类型的成员没有在构造函数初始值列表中显示地初始化时;

 值初始化在以下情况下发生:

  • 在数组初始化的过程中若提供的初始值数量少于数组的大小时
  • 当不使用初始值定义一个局部静态变量时
  • 当通过书写形如T()的表达式显式地请求值初始化时,其中T是类型名

2 访问控制与封装

  使用访问说明符加强类的封装性:

  • 定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口
  • 定义在private说明符之后后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private封装了类的实现细节
    再次定义Sales_data类,其程序如下:
class Sales_data
{
    public:        //添加访问说明符
        Sales_data() = default;        //声明默认构造函数
        //构造函数初始值列表
        Sales_data(const std::string &s, unsigned n, double p):
              bookNo(s), units_sold(n), revenue(p *  n){}
        Sales_data(const std::string &s): bookNo(s) {}
        Sales_data(std::istream &);
        std::string  isbn() const { return bookNo; }
        Sales_data& conbine(const Sales_data&);
    private:        //添加访问说明符
        double avg_price() const
            { return units_sold ? revenue/units_sold : 0;}
        std::string bookNo;
        unsigned units_sold = 0;
        double revenue = 0.0;
};

使用classstruct关键字
  struct与class的区别在于默认的访问权限不同。如果使用的是struct关键字,则定义在第一个访问说明符之间的成员是pubilc的,相反,如果我们使用class关键字,则这些成员是private的。

2.1 友元

  类可以允许其他类或者函数访问它的非公有成员,方法是令其类或函数成为它的友元,在函数声明语句的开始之前加上friend关键字即可。示例如下:

class Sales_data
{
    //为Sales_data的非成员函数所做的友元声明
    friend Sales_data add(const Sales_data&, const Sales_data&);
    friend std::ostream &print(std::ostream&, const Sales_data&);
    friend std::istream &read(std::istream&, Sales_data&);
    public:        //添加访问说明符
        Sales_data() = default;        //声明默认构造函数
        //构造函数初始值列表
        Sales_data(const std::string &s, unsigned n, double p):
              bookNo(s), units_sold(n), revenue(p *  n){}
        Sales_data(const std::string &s): bookNo(s) {}
        Sales_data(std::istream &);
        std::string  isbn() const { return bookNo; }
        Sales_data& conbine(const Sales_data&);
    private:        //添加访问说明符
        double avg_price() const
            { return units_sold ? revenue/units_sold : 0;}
        std::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&);

友元声明只能出现在类定义的内部

封装的益处
  封装有两个重要的特点:

  • 确保用户代码不会无意间破坏封装对象的状态
  • 被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。

猜你喜欢

转载自blog.csdn.net/qq_18150255/article/details/88913618