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

news2025/3/1 16:08:57

在这里插入图片描述

🤖 作者简介:努力的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 总目录 传送门 🏃‍🏃‍🏃‍
     

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

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/412420.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【NX2023/1847】UG软件安装详细指南教程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录安装包一、安装包内容检查二、安装步骤1.安装JAVA_WIN64.exe2.运行Launch.exe3.安装许可3.直接重启电脑&#xff08;小白直接重启稳妥&#xff09;4.重启后继续运行L…

网络威胁情报项目:为什么仍然很疯狂

大约五年前&#xff0c;向首席信息安全官&#xff08; CISO&#xff09;询问他们的网络威胁情报 (CTI) 计划时&#xff0c;得到了两种截然不同的回答。 资源丰富的大型企业正在投资他们的威胁情报计划&#xff0c;目的是为了战术、运营和战略目的更好地实施它。 规模较小、资…

Day942.独立编译调试 -系统重构实战

独立编译调试 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于独立编译调试的内容。 当组件做 独立的版本演进时&#xff0c;如果开发在本地每次修改代码时&#xff0c;都需要进行集成打包验证&#xff0c;反而会影响日常的开发效率。所以如果能够让组件独立进行编译…

Object方法

私人博客 许小墨のBlog —— 菜鸡博客直通车 系列文章完整版&#xff0c;配图更多&#xff0c;CSDN博文图片需要手动上传&#xff0c;因此文章配图较少&#xff0c;看不懂的可以去菜鸡博客参考一下配图&#xff01; 系列文章目录 前端系列文章——传送门 JavaScript系列文章—…

Node.js -- 模块化

1.模块化的基本概念 模块化是指解决一个复杂问题时&#xff0c;自顶向下逐层吧系统划分成若干模块的过程。对于整个系统来说&#xff0c;模块是可组合&#xff0c;分解和更换的单元。 将代码进行模块化拆分的好处&#xff1a; 提高代码的复用性提高代码的可维护性可以实现按…

元宇宙:新的数字模式——元宇宙会场

一、引言 元宇宙是一个充满无限可能的虚拟空间&#xff0c;人们可以在其中创建和参与各种虚拟场景和体验。元宇宙技术的兴起&#xff0c;为传统的会场提供了一个新的方向。元宇宙会场将线下会场的物理空间转化为虚拟空间&#xff0c;通过数字技术和互联网实现了人们在虚拟环境…

我的第一台电脑的故事

第一台电脑啊&#xff0c;多么遥远的故事了&#xff0c;又似乎就在眼前。今天重回往事&#xff0c;就简单记录一下吧。 &#x1f331;缘起 那是初一&#xff0c;至今已13年&#xff0c;遂觉遥远&#xff0c;而又是立志我学习的起点&#xff0c;至今还在校园&#xff0c;又觉就…

断开连接图的 BFS

在上一篇文章中,仅对特定顶点执行 BFS,即假设所有顶点都可以从起始顶点到达。但是在断开连接的图或所有顶点都无法访问的任何顶点的情况下,之前的实现将不会给出所需的输出,因此在这篇文章中,在 BFS 中进行了修改。 所有顶点都是可达的。因此,对于上图,简单的BFS就可以…

【消息队列】细说Kafka消费者位移机制

什么是位移 位移说白了就是消费者消费对应的Topic的分区的消费位置&#xff0c;之前存储到ZK中&#xff0c;后来转移到Kafka默认的Topic中。结构是采用keyvalue形势存储的&#xff0c;key是groupIdtopic分区号&#xff0c;value是offset的值。 而上述的存储就在_consumer_offse…

微信小程序02

小程序tabBar 普通页面跳转到 带有tabBar页面的时候不能使用 wx.navigateTo() 小程序中跳转到选项卡页面使用 wx.switchTab()跳转 到底部 onReachBottom() 函数 &#xff0c;&#xff0c; 在下拉刷新显示取消loading &#xff1a; wx.showNavigationBarLoading() wx.hideNavi…

深入讲解Linux内核中常用的数据结构和算法

Linux内核代码中广泛使用了数据结构和算法&#xff0c;其中最常用的两个是链表和红黑树。 链表 Linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种数据结构。链表所包含的元素可以动态创建并插入和删除。链表的每个元素都是离散存…

【网络原理】网络通信与协议

✨个人主页&#xff1a;bit me&#x1f447; ✨当前专栏&#xff1a;Java EE初阶&#x1f447; 目 录一. 网络发展史二. 网络通信基础1. IP地址2. 端口号3. 认识协议&#xff08;核心概念&#xff09;4. 五元组5. 协议分层6. 封装和分用一. 网络发展史 独立模式&#xff1a;计…

C++入门demo(从最简单的案例学习C++)

通过案例学习Cdemo01 在屏幕上输出内容demo02 规格不同的箱子&#xff08;变量&#xff09;demo03 物品存放&#xff08;变量赋值&#xff09;demo04 交换物品&#xff08;变量之间交换数值&#xff09;demo05 消失的重量&#xff08;隐式类型变换&#xff09;demo06 游泳池的容…

Melis4.0[D1s]:7.lvgl添加物理按键

文章目录1.lvgl注册keypad驱动1.1 在melis的ADC按键中发送消息1.1.1 创建消息队列&#xff0c;并初始化1.1.2 扫描按键时&#xff0c;发送按下和松开消息1.2 编写读取按键的回调函数1.3 lvgl按键驱动注册2.在gui中测试物理按键效果2.1 测试效果参考资料&#xff1a; 1.韦东山老…

第七章 基于 RNN 的生成文本

目录7.1 使用语言模型生成文本7.1.1 使用 RNN 生成文本的步骤7.1.2 文本生成的实现7.1.3 更好的文本生成7.2 seq2seq 模型7.2.1 seq2seq 的原理7.2.2 时序数据转换的简单尝试7.2.3 可变长度的时序数据7.2.4 加法数据集7.3 seq2seq 的实现7.3.1 Encoder类7.3.2 Decoder类7.3.3 S…

静态时序分析Static Timing Analysis3——特殊路径(多周期、半周期、伪路径)的时序检查

文章目录前言一、多周期路径1、建立时间检查2、保持时间检查二、半周期路径1、建立时间检查2、保持时间检查三、伪路径前言 2023.4.12 一、多周期路径 对于建立时间&#xff0c;要设置为N&#xff08;向后移&#xff09;&#xff1b;对于保持时间&#xff0c;要设置为N-1&…

9.8.0.32:ProEssentials数据可视化2D和3D图表:Crack

下面是我们的Winforms、Wpf、C MFC、VCL、ActiveX图表组件示例项目中的屏幕捕获。 有关下图&#xff0c;请参见我们的示例项目和演示中的030。 ProEssentials Winforms 图表, WPF 图表, C/MFC/VCL 图表. Gigasoft拥有20多年帮助企业开发大型客户端和嵌入式图表项目的经验。图…

JavaScript基础-02

常量&#xff08;字面量&#xff09;&#xff1a;数字和字符串 常量也称之为“字面量”&#xff0c;是固定值&#xff0c;不可改变。看见什么&#xff0c;它就是什么。 常量有下面这几种&#xff1a; 数字常量&#xff08;数值常量&#xff09;字符串常量布尔常量自定义常量…

传输线的物理基础(九):N 截面集总电路模型

理想的传输线电路元件是一种分布式元件&#xff0c;可以非常准确地预测实际互连的测量性能。下图显示了 1 英寸长传输线在频域中的实测阻抗和仿真阻抗对比。我们看到甚至高达 5 GHz 的测量带宽也能达成出色的协议。 1英寸长、50欧姆传输线的测量&#xff08;圆圈&#xff09;和…

Java实现hdfs的8个api操作

Java实现hdfs的8个api操作一、预处理准备1. 配置本地hadoop3.1.3目录文件2. 配置环境变量二、Maven项目依赖三、Java源代码四、api操作的实现1. 实现前的准备2. 创建hdfs上的路径3. 删除hdfs上的路径4. 创建hdfs文件并写入数据5. 删除hdfs上的文件6. hdfs上的文件移动路径并改名…