Chapter7: Classes(First Part)

来源:互联网 发布:md5解密算法java 编辑:程序博客网 时间:2024/06/06 00:13

这一章介绍类的特性, 不同以往, 我参照课本, 用构造Sales_data类的过程来介绍类的基本特性, 并在下篇介绍类的其他特性. 我会集中关注这样三个问题: 代码这样写是什么意思? 代码这样写行不行? 为什么要从这个版本改进到下一个版本?


首先介绍一下要构造的Sales_data类:

  • Sales_data类用来记录书籍的销售记录, 以及对销售记录进行基本操作.

  • 数据成员包括:

    • string类型的bookNo成员, 存储书籍ISBN号

    • unsigned类型的units_sold成员, 代表书籍的销售量

    • double类型的revenue, 代表书籍的销售额

  • 成员函数包括:

    • combine函数, 将一个Sales_date对象加到另一个对象上

    • isbn函数, 获取Sales_data对象的bookNo数据成员

    • avg_price函数, 计算某本书籍的平均售价

  • 非成员函数包括:

    • add函数, 将两个Sales_data对象相加

    • print函数, 将Sales_data对象的数据成员输出到ostream

    • read函数, 从istream读入Sales_data对象


版本1

// 文件:Sales_data.h// 版本1struct Sales_data{    // 成员函数    std::string isbn() const { return bookNo; }    Sales_data &combine(const Sales_data &);    double avg_price() const;    // 数据成员    std::string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};// 非成员函数Sales_data add(const Sales_data &, const Sales_data &);std::ostream &printf(std::ostream &, const Sales_data &);std::istream &read(std::istream &, Sales_data &);
// 文件:Sales_data.cpp// 成员函数定义Sales_data &Sales_data::combine(const Sales_data &rhs){    units_sold += rhs.units_sold;    revenue += rhs.revenue;    return *this;}double Sales_data::avg_price() const{    if(units_sold)        return revenue / units_sold;    else        return 0;}// 非成员函数定义Sales_data add(const Sales_data &lhs, const Sales_data &rhs){    Sales_data sum = lhs;    sum.combine(rhs);    return sum;}ostream &print(ostream &os,const Sales_data &item){    os << item.isbn() << " " << item.units_sold << " "       << item.revenue << " " << item.avg_price();    return os;}istream &read(sitream &is, Sales_data &item){    double price = 0;    is >> item.bookNo >> item.units_sold >> price;    item.revenue = price * item.units_sold;    return is;}
  • this指针
    如果我想获取一个Salas_data对象的ISBN号可以这样写:
Sales_data book;isbnNu = book.isbn();

但是, isbn()函数是怎么知道要获取的是book对象的bookNu数据成员, 而不是其他对象的数据成员的呢? 通过this指针. 当我们调用一个成员函数时, this指针被初始化为调用成员函数的对象的地址. 并且, this指针作为额外的隐式参数传递给成员函数. isbn函数的定义事实上是:

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

关于this指针要注意以下几点:
1. 由于this指针始终指向调用成员函数的对象, 所以this指针被定义成const指针类型(指针指向的地址不变)
2. 在成员函数内部可以使用this指针

  • const成员函数

isbn函数以及avg_price 函数声明后的const是什么意思? 我们试想这样一种情况: 我们定义了一个 const Sales_data 类型的对象book, 我们想获取它的bookNu成员(ISBN号). 显然, 由于book是const Sales_data类型, 它的指针应当是const Sales_data*类型, 而this指针是Sales_data*const类型的, 我们无法将const Sales_data*类型转换为Sales_data*const类型(如果可以将会改变不希望改变的对象的内容), 也就是说我们无法调用isbn成员函数, 解决的办法就是更改this指针的类型为 const Sales_data*const类型. 由于this是隐式的, 所以std::string isbn() const的const意思是该成员函数this指针为指向const对象的const指针. isbn为const成员函数.

  • 类外定义成员函数
    代码中isbn成员函数在类内声明并定义, 其他成员函数在类内声明, 在类外定义.
    注意:
    1. 成员函数必须在类内声明
    2. 成员函数可以在类内或者类外(与类定义在同一文件或不同文件)定义

  • 为什么要从版本1改进到版本2?
    版本1中有一部分数据成员进行了类内初始化了, 有一部分数据成员没有(显式)初始化. 事实上, 编译器自动为类添加了默认合成构造函数, 该函数的作用就是对类的数据成员进行默认初始化, 即:
    1. 如果数据成员有类内初始化值, 则使用类内初始化值初始化
    2. 如果没有, 则采用默认初始化方式
      注意:
    3. 当且仅当我们没有定义构造函数时编译器才会添加合成默认构造函数
    4. 根据默认初始化规则, 定义在代码块内的内置类型和复合类型的默认初始化值是未定义的
    5. 有些类无法用合成默认构造函数初始化, 比如类的数据成员含有未定义默认构造函数的类
      所以我们需要自己定义默认构造函数, 于是产生了版本2.

版本2

// 版本2struct 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 &combine(const Sales_data &);    double avg_price() const;    // 数据成员    std::string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};// 非成员函数Sales_data add(const Sales_data &, const Sales_data &);std::ostream &printf(std::ostream &, const Sales_data &);std::istream &read(std::istream &, Sales_data &);
// 文件:Sales_data.cpp// 默认构造函数Sales_data::Sales_data(std::istream &is){    read(is, *this);}其他与版本1相同...
  • = default是什么意思?
    试想一下如果我们既需要自己定义的默认构造函数, 又需要编译器定义的合成默认构造函数该怎么办呢? = default的意思就是显式的告诉编译器生成合成默认构造函数.

  • 构造初始化参数列表
    其他的默认构造函数都接收不定数量的参数, 而 :{之间的代码是初始化参数列表, 它将数据成员后括号内的值作为该数据成员的初始化值.
    注意:

    1. 未出现在初始化列表中的数据成员将采用类内初始化值初始化(如果有的话)或者默认初始化值初始化.
  • 类外定义默认构造函数
    最后一个默认构造函数从istream对象中接收参数, 并在类外定义该函数.


  • 为什么要从版本2改进到版本3?
    在版本2中, 用户(指使用类的程序员)可以使用接口(成员函数)访问类的数据成员, 但是也可以直接访问数据成员, 我们需要对类进行进一步的封装. 于是使用了访问说明符加强封装性.

// 文件:Sales_data.h// 版本3class Sales_data{public:    // 构造函数    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 &combine(const Sales_data &);private:    // 成员函数    double avg_price() const;    // 数据成员    std::string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;}// 非成员函数Sales_data add(const Sales_data &, const Sales_data &);std::ostream &printf(std::ostream &, const Sales_data &);std::istream &read(std::istream &, Sales_data &);
  • 访问说明符

    • public说明符之后的成员在整个程序内可被访问.
    • private说明符之后的成员只可以被成员函数访问.
      注意:
      1. 一个类可以有0至多个访问说明符, 可从重复使用.
      2. 每个访问说名符的作用范围为冒号之后到下一个访问说明符出现之前或类的结尾.
  • 使用用class还是struct?
    使用struct或者class定义类唯一的区别在于默认的访问权限:

    • struct在第一个访问说明符出现之前默认为public.
    • class在第一个访问说明符出现之前默认为private.

  • 为什么要从版本3改进到版本4?
    对于版本3的private数据成员, 我们只能利用成员函数访问它们.
    但是, 对于非成员函数的接口, 如IO操作也需要访问它们, 怎么办呢? 使用友元机制. 于是产生了版本4.

// 文件:Sales_data.h// 版本4class Sales_data{    // 友元函数    friend Sales_data add(const Sales_data &, const Sales_data &);    friend std::ostream &printf(std::ostream &, const Sales_data &);    friend std::istream &read(std::istream &, Sales_data &);public:    // 构造函数    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 &combine(const Sales_data &);private:    // 成员函数    double avg_price() const;    // 数据成员    std::string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;}// 非成员函数Sales_data add(const Sales_data &, const Sales_data &);std::ostream &printf(std::ostream &, const Sales_data &);std::istream &read(std::istream &, Sales_data &);
  • 什么是友元?
    一个类可以通过将另一个类或者函数声明为它的友元, 以允许它们访问该类的私有成员. 可见, 友元分为友元类和友元函数, 友元类的所有成员函数都可以访问私有成员, 友元函数只有本函数可以访问私有成员.
    注意:

    1. 友元不是类的成员, 不受访问说明符的影响.
    2. 友元声明只可以出现在类内.
  • 为什么友元函数会有两个声明?
    第一个带有friend关键字的是友元声明, 它必须出现在类内, 其目的是告诉编译器friend下的函数或类可以访问私有成员; 第二个才是函数或者类的真正声明.

0 0
原创粉丝点击