C++ Primer 第7章 类 - 上(零基础学习C++,精简学习笔记)

在这里插入图片描述

🤖 作者简介:努力的clz ,一个努力编程的菜鸟 🐣🐤🐥
 
👀 文章专栏:C++ Primer 学习笔记
 
📔专栏简介: 本专栏是博主学习 C++ Primer 的学习笔记,因为这本书内容超级多,所以博主将其中的 重点 概括提炼出来,于是有了这个专栏的诞生。
 
C++ Primer 学习笔记 第7章 类 笔记导航 🚥🚥🚥

  1. 🥬 C++ Primer 第7章 类 - 上 ⇦当前位置🪂
  2. 🥕 C++ Primer 第7章 类 - 中 (加班中)
  3. 🥪 C++ Primer 第7章 类 - 下 (加班中)
  4. 🎨 C++ Primer 总目录 传送门 🏃‍🏃‍🏃‍
     

如果本篇文章对大家起到帮助的话,跪求各位帅哥美女们,求赞👍 、求收藏 👏、求关注!👀

 

第7章 类 (上)

关于 类的思想与定义 会专门出一期博文来好好聊一聊。
 
对于初学者来说,这个很重要,要好好理解类的基本思想:数据抽象、封装

 

7.1 定义抽象数据类型

本节将围绕 Sales_data 这个例子,展开介绍类的入门知识。直接通过代码去理解书中繁琐的文字描述,节约读者学习时间。

 

7.1.1 设计 Sales_data 类

跳过,直接看 改进的 Sales_data 类

 


7.1.2 定义改进的 Sales_data 类

#include <iostream>
#include <string>

using std::istream;
using std::ostream;
using std::string;

// todo 销售数据类
struct Sales_data
{
    string bookNo;           // 书号
    unsigned units_sold = 0; // 销量
    double revenue = 0.0;    // 总销售收入

    /**
     * @brief 返回书的编号
     *
     * @return string
     */
    string isbn() const
    {
        return this->bookNo;
    }

    /**
     * @brief 合并书的销售数据
     *
     * @return Sales_data&
     */
    Sales_data &combine(const Sales_data &);

    /**
     * @brief 返回售出书籍的平均价格
     *
     * @return double
     */
    double avg_price() const;
};

// Sales_data的非成员接口函数
Sales_data add(const Sales_data &, const Sales_data &);
ostream &print(ostream &, const Sales_data &);
istream &read(istream &, Sales_data &);

 

1. 定义成员函数

类的成员 (变量、函数) 都必须定义在类里面,但成员函数体可以在类里面,也可以在类外面;

在这里插入图片描述

 

2. 引入 this

this 表示 “这个”,也就是所在位置的类对象,成员函数通过 this 隐式参数来访问调用它的对象。
 
this 是隐式定义的。我们只需要知道不可以把 变量或函数参数 命名为 this 即可。
 
this 是一个常量指针,不能改变 this 中保持的地址。

在这里插入图片描述

 

3. 引入 const 成员函数

如下图 isbn() ,在参数列表后添加 const 关键字;这类成员函数称为 常量成员函数 ,表明其不被允许修改类的数据成员。
 
换句话来说,常量成员函数 可以读取变量的值,不能修改变量的值。

在这里插入图片描述

 

4. 类作用域和成员函数

类本身就是一个作用域,成员函数可以随意使用类中的成员,不管它在类中的哪个位置定义(函数前、后都可以);
 
编译器分两步处理类:

  1. 编译成员的声明;
  2. 编译成员函数体。

在这里插入图片描述

 

5. 在类的外部定义成员函数

类外定义成员函数需要注意的点:

  • 返回类型、参数列表和函数名要和类内的声明一致;
  • 若成员被声明为常量成员函数,类外定义也得加上 const
     

注意看:Sales_data::avg_price() 表示告诉编译器 avg_price()函数 被声明在类 Sales_data 的作用域内。

/**
 * @brief 返回售出书籍的平均价格
 *
 * @return double
 */
double Sales_data::avg_price() const
{
    if (units_sold)
    {
        return revenue / units_sold; // ! avg_price 使用revenue、units_sold时,实际是隐式地使用 Sales_data 的成员变量。
    }
    else
    {
        return 0;
    }
}

在这里插入图片描述

 

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

combine 函数 必须返回引用类型,也就是 Sales_data&
 
return 语句 解引用 this 指针 以获得执行该函数的对象。

/**
 * @brief 合并书的销售数据
 *
 * @param rhs
 * @return Sales_data&
 */
Sales_data &Sales_data::combine(const Sales_data &rhs)
{
    units_sold += rhs.units_sold; // 把rhs的成员加到this对象的成员上
    revenue += rhs.revenue;
    return *this; // 返回调用该函数的对象
}

 


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

实现 read、print 函数 ,需要注意两点:

  • read、print 分别接受一个各自 IO类型的引用作为参数。因为IO类不能被拷贝,只能通过引用来传递它们。
    (“ 拷贝” 现阶段先理解为时用它来给其它变量赋值)
  • print 函数 不负责换行。这个是编码规范(可以不遵守),执行输出任务的函数要求尽可能减少对格式的控制。
/**
 * @brief 从给定流中将数据读到给定得对象里
 *
 * @param is
 * @param item
 * @return istream&
 */
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;
}

/**
 * @brief 负责将给定对象的内容打印到给定的流中
 *
 * @param os
 * @param item
 * @return ostream&
 */
ostream &print(ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " "
       << item.revenue << " " << item.avg_price();
    return os;
}

 

定义 add 函数

add() 函数 ,求两个Sales_data对象的和。

/**
 * @brief 求两个Sales_data对象的和
 *
 * @param lhs Sales_data对象
 * @param rhs Sales_data对象
 * @return Sales_data
 */
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum = lhs; // 把lhs的数据成员拷贝给sum
    sum.combine(rhs);     // 把rhs的数据成员加到sum当中
    return sum;
}

 


7.1.4 构造函数

构造函数 负责初始化类对象的数据成员,具有如下几个特点:

  • 构造函数 的名字和类相同;
  • 构造函数 没有返回类型;
  • 构造函数 参数列表、函数体可以有也可以为空;
  • 构造函数 不能被声明为 const 。
     

需要注意的是,构造函数 在对 const 对象 进行初始化时,是可以修改值的。只有完成构造初始化,对象才真正具备 常量 属性。

 

1.合成的默认构造函数

到目前为止,我们还未给 Sales_data 类 添加构造函数,但当我们声明一个Sales_data对象:Sales_data sd 时,仍会完成初始化工作。
 
编译器会创建一个 默认构造函数 ,去完成如下工作:

  • 若存在类内的初始值,用它来初始化成员;
  • 否则,默认初始化该成员。
     

例如下方代码:

  • revenue 被赋值为 0.0;
  • bookNo 默认初始化为空字符串;

在这里插入图片描述

 

2. 某些类不能依赖于合成的默认构造函数

合成的默认构造函数 仅适用于简单类 (变量成员是基本数据类型,不包含复合类型或类类型),建议用户自己编写默认构造函数,原因:

  • 只有当类没有什么任何构造函数时,编译器才会自动生成默认规则函数;
  • 对于某些类,合成的默认构造函数可能执行错误的操作;
  • 有些时候编译器不能为某些类合成默认的构造函数。

 

3. 定义 Sales_data 的构造函数

根据参数的不同,给 Sales_data 定义了4个不同的构造函数:

  1. 空参数列表 (默认构造函数);
  2. 一个 istream& 参数,从数据流中读取信息完成初始化;
  3. 一个 const string &s,对书号赋值,其它变量默认值;
  4. 一个 const string &s 书号、一个 unsigned 销售数量、一个 double 价格。
struct Sales_data
{
    // * 新增成员 - 构造函数
    Sales_data() = default;
    Sales_data(istream &);
    Sales_data(const string &s) : bookNo(s) {}
    Sales_data(const string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p * n) {}

    // ? 下面是之前已有的成员
    /**
     * @brief 返回书的编号
     *
     * @return string
     */
    string isbn() const
    {
        return this->bookNo;
    }

    /**
     * @brief 合并书的销售数据
     *
     * @return Sales_data&
     */
    Sales_data &combine(const Sales_data &);

    /**
     * @brief 返回售出书籍的平均价格
     *
     * @return double
     */
    double avg_price() const;

    string bookNo;           // 书号
    unsigned units_sold = 0; // 销量
    double revenue = 0.0;    // 总销售收入
};

 

4. = default 的含义

默认构造函数不接受任何实参,如果我们想让它功能和 合成默认构造函数 一样,只需要在参数列表后添加 = default 即可。


Sales_data() = default;

 

5. 构造函数初始值列表

下图中红框部分,叫做 构造函数初始值列表,负责给数据成员赋初值。
 
第一个红框的构造函数,只对 bookNo 赋值,其余的两个变量成员会以 合成默认构造函数 相同的方式隐式初始化。
 
两个 构造函数初始值列表 后方的函数体都为空,主要是构造函数唯一任务是为数据成员赋初值。

Sales_data(const string &s) : bookNo(s) {}

// 上行代码等价于下列
Sales_data(const string &s) : bookNo(s), units_sold(0), revenue(0) {}

// 函数体为空
Sales_data(const string &s) : bookNo(s) {
	cout<<"一般这里没有代码,函数体为空!"<<endl;
}

在这里插入图片描述

 

6. 在类的外部定义构造函数

在类的外部定义构造函数,和之前 7.1.2 节中的 5. 在类的外部定义成员函数 语法格式几乎相同,可以对比学习。
 
下方的构造函数并没有 初始值列表 ,但是可以通过函数体代码去数据成员进行赋初值。

/**
 * @brief Construct a new Sales_data::Sales_data object
 *
 * @param is
 */
Sales_data::Sales_data(std::istream &is)
{
    read(is, *this); // read 函数的作用是从 is 读取一条交易信息,然后存入 this 对象中
}

// read 函数 (7.1.3节 学习过该代码)
/**
 * @brief 从给定流中将数据读到给定得对象里
 *
 * @param is
 * @param item
 * @return istream&
 */
istream &read(istream &is, Sales_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;	// bookNo,units_sold 此时被赋值了
    item.revenue = price * item.units_sold;			// revenue 此时被赋值了
    return is;
}

在这里插入图片描述

 


7.1.5 拷贝、赋值和析构

除了 初始化操作 之外,类还需要控制 拷贝、赋值和销毁对象 时发生的行为。
 
如果我们不主动定义这些操作,则编译器会替我们合成它们。

 

某些类不能依赖于合成的版本

编译器替我们合成 拷贝、赋值和销毁对象 的操作,仅能适用于简单场景,对于某些类就无法正常工作了。
 
具体将在 第12章 动态内存第13章 拷贝控制 中介绍。

 


7.2 访问控制与封装

7.1 节 中定义的 Sales_data 类 中并未使用任何机制去限制用户对类中数据成员的访问。
 
在 C++ 语言中,可以使用 访问说明符 来加强类的封闭性:

  • 定义在 public 说明符 之后的成员在整个程序内可被访问;
  • 定义在 private 说明符 之后的成员可以被类的成员函数访问,不能被使用该类的代码访问;
     

下面将使用 访问说明符 来对 7.1 节 中的 Sales_data 类 进行改造。

#include <iostream>
#include <string>

using std::istream;
using std::ostream;
using std::string;

// todo 销售数据类
class Sales_data
{
public:
    Sales_data() = default;
    Sales_data(istream &);
    Sales_data(const string &s) : bookNo(s) {}
    Sales_data(const string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p * n) {}

    /**
     * @brief 返回书的编号
     *
     * @return string
     */
    string isbn() const
    {
        return this->bookNo;
    }

    /**
     * @brief 合并书的销售数据
     *
     * @return Sales_data&
     */
    Sales_data &combine(const Sales_data &);

private:
    string bookNo;           // 书号
    unsigned units_sold = 0; // 销量
    double revenue = 0.0;    // 总销售收入

    /**
     * @brief 返回售出书籍的平均价格
     *
     * @return double
     */
    double avg_price() const
    {
        return units_sold ? revenue / units_sold : 0;
    }
};

// 1. Sales_data的成员接口函数
/**
 * @brief Construct a new Sales_data::Sales_data object
 *
 * @param is
 */
Sales_data::Sales_data(std::istream &is)
{
    read(is, *this); // read 函数的作用是从 is 读取一条交易信息,然后存入 this 对象中
}

/**
 * @brief 合并书的销售数据
 *
 * @param rhs
 * @return Sales_data&
 */
Sales_data &Sales_data::combine(const Sales_data &rhs)
{
    units_sold += rhs.units_sold; // 把rhs的成员加到this对象的成员上
    revenue += rhs.revenue;
    return *this; // 返回调用该函数的对象
}


// 2. Sales_data的非成员接口函数
istream &read(istream &, Sales_data &);
ostream &print(ostream &, const Sales_data &);
Sales_data add(const Sales_data &, const Sales_data &);

/**
 * @brief 从给定流中将数据读到给定得对象里
 *
 * @param is
 * @param item
 * @return istream&
 */
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;
}

/**
 * @brief 负责将给定对象的内容打印到给定的流中
 *
 * @param os
 * @param item
 * @return ostream&
 */
ostream &print(ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " "
       << item.revenue << " " << item.avg_price();
    return os;
}

/**
 * @brief 求两个Sales_data对象的和
 *
 * @param lhs Sales_data对象
 * @param rhs Sales_data对象
 * @return Sales_data
 */
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum = lhs; // 把lhs的数据成员拷贝给sum
    sum.combine(rhs);     // 把rhs的数据成员加到sum当中
    return sum;
}

 

使用 class 或 struct 关键字

在这里插入图片描述

 


7.2.1 友元

按照 7.2 节 给的代码修改,发现程序爆红,read、print 函数 报错了!😟😟😟
 
这是因为在 7.2 节 代码中,将 bookNo,units_sold、revenue 设置成 priavte,所以这三个变量只能被类的成员函数访问。

在这里插入图片描述

 

如果想要 read、print 函数 访问,需要将其设置为类的 友元
 
Sales_data 类 中,添加以 friend 关键字 开头的函数声明即可。(添加完下方代码,read、print 函数就不报错了😀😀😀)
 
PS:友元声明只能出现在类定义的内部。

	// 为Sales_data的非成员函数所作的友元声明
	friend Sales_data add(const Sales_data &, const Sales_data &);
	friend std::istream &read(std::istream &, Sales_data &);
	friend std::ostream &print(std::ostream &, const Sales_data &);

在这里插入图片描述

 

友元的声明

友元的声明仅仅指定了访问的权限,而非真正的函数声明,在类外需要专门对函数再进行一次声明。

在这里插入图片描述

 


C++ Primer 学习笔记 第7章 类 笔记导航 🚥🚥🚥

  1. 🥬 C++ Primer 第7章 类 - 上 ⇦当前位置🪂
  2. 🥕 C++ Primer 第7章 类 - 中 (加班中)
  3. 🥪 C++ Primer 第7章 类 - 下 (加班中)
  4. 🎨 C++ Primer 总目录 传送门 🏃‍🏃‍🏃‍
     

如果本篇文章对大家起到帮助的话,跪求各位帅哥美女们,求赞👍 、求收藏 👏、求关注!👀

在这里插入图片描述

在这里插入图片描述

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的clz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值