当前位置: 首页 > news >正文

《C++ Primer》第7章 类(二)

参考资料:

  • 《C++ Primer》第5版
  • 《C++ Primer 习题集》第5版

7.4 类的作用域(P253)

每个类都有自己的作用域,在类的作用域之外,普通的数据和函数成员只能由对象、引用或指针使用成员访问运算符访问,类型成员则通过作用域运算符访问。

作用域和定义在类外部的成员

对于定义在类外部的函数成员而言,一旦遇到类名,定义的剩余部分就是在类的作用域之内了,这就是为什么参数列表和函数体可以直接使用类的其他成员:

void Window_mgr::clear(ScreenIndex i){    // ScreanIndex是类Window_mgr中的类型成员Screen &s = screens[i];s.contents = string(s.height*s.width, ' ');
}

需要注意的是,由于返回类型出现在类名之前,所以它位于类的作用域之外,所以如果我们的返回类型是 ScreenIndex ,就必须写成 Window_mgr::ScreenIndex

7.4.1 名字查找与类的作用域(P254)

一般的**名字查找(name lookup)**过程比较直接:

  1. 在名字所在块中寻找声明语句。
  2. 如果没找到,继续查找外层作用域。
  3. 如果最终没有找到,则报错。

这里名字查找只考虑出现名字使用之前出现的声明

由于类的定义分两步处理:首先编译成员的声明,然后编译函数体。所以成员函数体可以使用类中的任何名字,而无需考虑这个名字是否在前面出现过。

用于类成员声明的名字查找

需要注意的是,只有成员函数体具有特殊性,成员函数的参数列表和返回类型仍然遵循一般名字查找的规则:

using Money = double;
string bal;
class Account{
public:Money balance() { return bal; }    // 这里bal的类型是Money,Money是double的别名
private:Money bal;
};

类型名要特殊处理

如果类成员使用了某个名字,这个名字代表某种类型,则类不能再重新定义这个名字。

using Money = double;
class Account{
public:Money balance() { return bal; }using Money = double;    // 错误,Money不能重新定义,即使与原定义一致也不行
};

需要说明是,某些编译器会忽略这个约定(比如 visual studio )。

成员定义中的普通块作用域的名字查找

成员函数体内的名字按照如下方式查找:

  1. 在函数内查找该名字,只有在使用之前出现的声明才被考虑。
  2. 如果在函数内没找到,则在类内查找,此时无需考虑先后顺序。
  3. 如果类内没找到,则在成员函数定义之前的作用域内寻找。
// 最好不要隐藏同名成员,这里仅作演示
pos height;
void Screen::dummy_fcn(pos height){cursor = width * height;    // 参数中的heightcursor = width * this->height;    // 类中的heightcursor = width * Screen::height;    // 类中的heightcursor = width * ::height;    // 全局变量height
}

在文件中名字的出现处对其进行解析

class X{void func(int);
};
int y;
void X::func(int i){i = y;    // y的声明已经出现过了,所以可以正常使用
}

7.5 构造函数再探(P257)

7.5.1 构造函数初始值列表(P258)

如果没有在构造函数初始值列表中显式初始化成员,则该成员将在函数体之前执行默认初始化:

Sales_data::Sales_data(const string &s, unsigned cnt, double price){bookNo = s;units_sold = cnt;revenue = cnt * price;
}

上面这段代码相当于先默认初始化成员,然后再给成员重新赋值

构造函数的初始值有时必不可少

如果成员是 const 、引用或某种没有定义默认构造函数的类类型,则必须将其初始化。

一旦构造函数体开始执行初始化完成了。

成员初始化的顺序

在构造函数初始值列表中,每个成员只能出现一次。构造函数初始值列表只说明用于初始化成员的值,而没有限定初始化的顺序。成员的初始化顺与它们在类定义中的出现顺序一致:

class X {int i;int j;
public:// 想用未定义的j初始化i,再用val初始化jX(int val):j(val), i(j) { }
};

默认实参和构造函数

class Sales_data {
public:Sales_data(string s = "") :bookNo(s) { }
};

如果一个构造函数为所有参数都提供了默认实参,则它实质上也定义了默认构造函数

7.5.2 委托构造函数(P261)

C++11 新标准允许我们使用委托构造函数(delegating constructor)。委托构造函数使用本类中其他构造函数执行自己的初始化过程:

class Sales_data {
public:Sales_data(string s, unsigned cnt, double price) :bookNo(s), units_sold(cnt), revenue(cnt *price) { }// 委托构造函数Sales_data() :Sales_data("", 0, 0) { }Sales_data(string s) :Sales_data(s, 0, 0) { }// 先委托默认构造函数,然后默认构造函数在委托三参数构造函数// 执行完受委托的构造函数后再执行委托函数的函数体Sales_data(istream &is) :Sales_data() { read(is, *this); }
};

7.5.3 默认构造函数的作用(P262)

当类类型的对象被默认初始化值初始化时自动执行默认构造函数

使用默认构造函数

Sales_data obj1();    // 声明了一个函数
Sales_data obj2;    // 使用默认构造函数

7.5.4 隐式的类类型转换(P263)

如果某个类类型定义了能通过一个实参调用的构造函数,则它实际上定了转换成此类类型隐式转换机制,这种构造函数称作转换构造函数(converting constructor)

class Sales_data {
public:// 转换构造函数Sales_data(string s) :bookNo(s), units_sold(0), revenue(0) { }
};
void func(Sales_data x) { ; }string null_book = "9-999-99999-9";
func(null_book);    // 正确

可以理解为用这个参数隐式构造一个临时量。

只允许一步类类型转换

func("9-999-99999-9");    // 错误,需要先将const char*转换成string,再由string转换成Sales_data

类类型转换不是总有效

以上面的代码为例,并不是所有的 string 都是我们需要的 bookNo

抑制构造函数定义的隐式转换

我们可以将构造函数声明为 explicit 来阻止隐式类型转换:

class Sales_data {
public:// 阻止隐式类型转换explicit Sales_data(string s) :bookNo(s), units_sold(0), revenue(0) { }
};
void func(Sales_data x) { ; }string null_book = "9-999-99999-9";
func(null_book);    // 错误

只能在类内声明构造函数时使用 explicit ,不能在类外使用 explicit

explicit构造函数只能用于直接初始化

Sales_data item1(null_book);    // 正确
Sales_data item2 = null_book;    // 错误

为转换显式使用构造函数

func(static_cast<Sales_data>(null_book));    // 正确

7.5.5 聚合类(P266)

当一个类满足如下条件时,它是聚合类(aggregate class)

  • 所有成员都是 public
  • 没有定义任何构造函数。
  • 没有类内初始值。
  • 没有基类,也没有 virtual 函数。

聚合类允许用户直接访问其成员,并且具有特殊的初始化形式:

struct Data {int ival;string s;
};Data val1{ 0, "hello" };

如上面的代码所示,我们可以用一个花括号括起来的初始值列表构造聚合类,初始值的顺序必须与类中定义的顺序相同。如果初始值列表中的元素个数少于类的成员数量,则靠后的成员被值初始化。

7.5.6 字面值常量类

数据成员都是字面值类型的聚合类是字面值常量类;如果一个类不是聚合类,但它符合下述要求,则它也是字面值常量类:

  • 数据成员都是字面值类型。
  • 类必须至少有一个 constexpr 构造函数。
  • 如果一个数据成员有类内初始值,这个初始值或是一条常量表达式,或是调用数据成员自己的 constexpr 构造函数。
  • 类必须使用析构函数的默认定义。

constexpr构造函数

前面提到,字面值常量类至少有一个 constexpr 构造函数。构造函数可以声明成 = default 。否则, constexpr 必须既满足构造函数的要求(不需要 return 语句),又满足 constexpr 函数的要求(唯一可执行语句就是返回语句)。综合上述两点可知,constexpr 构造函数体一般为空:

class Debug {
public:constexpr Debug(bool b = true) :hw(b), io(b), other(b) { }constexpr Debug(bool h, bool i, bool o):hw(h), io(i), other(o) { }constexpr bool any() { return hw || io || other; }
private:bool hw;bool io;bool other;
};

constexpr 构造函数必须初始化所有数据成员(有默认初始值的成员可以不显式初始化),初始值或者使用constexpr 构造函数,或者是一条常量表达式。

7.6 类的静态成员(P268)

有时候,类需要一些成员与类本身直接相关,而不是与类的各个对象保持关联。

声明静态成员

我们可以在成员的声明前加上关键字 static 使之与类关联在一起:

class Account {
public:void calculate() { amount += amount * interestRate; }static double rate() { return interestRate; }static void rate(double);
private:string owner;double amount;static double interestRate;static double initRate();
};

类的静态成员存在于任何对象之外,所以每个 Account 对象将包含两个数据成员 owneramountinterestRate 对象只有一个,并被所有对象共享。静态函数成员也不与任何对象绑定在一起,所以不包含 this 指针,也不能声明成 const

使用类的静态成员

可以使用作用域运算符直接访问静态成员:

double r;
r = Account::rate();

尽管静态成员不属于类的某个对象,但我们仍然可以通过类的对象、引用或指针来访问静态成员。

成员函数不通过作用域运算符也能直接使用静态成员:

class Account {
public:void calculate() { amount += amount * interestRate; }
private:static double interestRate;
}

定义静态成员

在类外部定义静态成员时,不能出现 static 关键字,static 只能出现在类内部的声明语句。

由于静态数据成员不属于类的任何一个对象,所以它们不是在创建类的对象时被定义的,也不是由构造函数初始化的。

一般来说,我们通常在任何函数外部定义和初始化静态成员:

double Account::interestRate = initRate();

由于见到 Account 后就知道当前处在类的作用域,所以后面调用 initRate() 不需要作用域运算符;同函数成员在类外部定义一样,interestRate 的定义也可以访问类中的私有成员

静态成员的类内初始化

前面提到,类的静态成员不应该在类内初始化。但我们可以为静态成员提供 const 整数初始值(似乎没有这个要求?),但是要求静态成员必须是字面值常量类型 constexpr 且初始值必须是常量表达式。

class Account {
private:static constexpr int period = 30;double daily_tbl[period];
};

尽管一个常量静态数据成员已经在类内部初始化了,我们通常还是要在类外部定义一下这个成员,此时不能再指定初始值

constexpr int Account::period;

静态成员能用于某些场景,而普通成员不能

静态数据成员可以是不完全类型,比如它本身所属的类类型:

class X{
public:static X mem1;    // 正确X mem2;    // 错误
};

此外,我们可以使用静态成员作为函数成员的默认实参

相关文章:

《C++ Primer》第7章 类(二)

参考资料&#xff1a; 《C Primer》第5版《C Primer 习题集》第5版 7.4 类的作用域&#xff08;P253&#xff09; 每个类都有自己的作用域&#xff0c;在类的作用域之外&#xff0c;普通的数据和函数成员只能由对象、引用或指针使用成员访问运算符访问&#xff0c;类型成员则…...

git仓库代码克隆

说明&#xff1a; 由于服务项目的厂商不同且需求不断变化且交付周期临近&#xff0c;所以想把之前一个仓库的代码弄到一个新的仓库&#xff0c;待交付完毕后再进行代码整合。 第一步 先创建一个远程仓库用来放你要克隆的代码。 第二步 克隆一份裸版本代码库 git clone --ba…...

AM@向量代数@向量基本概念和向量线性运算

文章目录 abstract向量的基本概念向量向量的坐标分解式和坐标&#x1f47a;向量的模向量的长度(大小)&#x1f47a;零向量单位向量&#x1f47a;方向向量非零向量的单位向量正规化向量夹角&#x1f47a; 向量方向角和向量间夹角投影几何描述向量的线性运算向量的加减运算向量的…...

2023-11-08 LeetCode每日一题(最长平衡子字符串)

2023-11-08每日一题 一、题目编号 2609. 最长平衡子字符串二、题目链接 点击跳转到题目位置 三、题目描述 给你一个仅由 0 和 1 组成的二进制字符串 s 。 如果子字符串中 所有的 0 都在 1 之前 且其中 0 的数量等于 1 的数量&#xff0c;则认为 s 的这个子字符串是平衡子…...

Web3.0的测试题

任务&#xff1a; 在前端开发一个查询UI&#xff0c;查询当前用户账户的ETH余额和指定ERC20合约中的余额 目标&#xff1a; UI框架指定使用 MUI (https://mui.com)需要查询到当前账户的ETH余额并展示在UI界面上需要输入ERC20合约地址后&#xff0c;查询到到当前账户在此ERC20…...

Javascript知识点详解:对象的继承、原型对象、原型链

目录 对象的继承 原型对象概述 构造函数的缺点 prototype 属性的作用 原型链 constructor 属性 instanceof 运算符 构造函数的继承 多重继承 对象的继承 面向对象编程很重要的一个方面&#xff0c;就是对象的继承。A 对象通过继承 B 对象&#xff0c;就能直接拥有 B …...

学之思开源考试系统部署至Centos7

学之思开源考试系统部署至Centos7 1、下载源码 源码下载&#xff1a; https://gitee.com/mindskip/xzs-mysql 数据库脚本下载&#xff1a; https://www.mindskip.net:999/ 2、项目打包 分别在\source\vue\xzs-student目录和source\vue\xzs-admin目录&#xff0c;执行前端打…...

如何利用浏览器的可见性API优化网站性能

最近在使用微软AI聊天工具Bing时&#xff0c;发现一个有趣的东西。我向它提问后&#xff0c;它在持续输出的过程中&#xff0c;如果我离开了当前它的浏览器会话&#xff0c;比如切屏&#xff0c;看当前浏览器的其它标签页&#xff0c;它会默认停止它的输出&#xff0c;等我回来…...

还不知道IP地址不够用是怎么被大牛们解决的?(NAT/NAPT, IPv6, DHCP)

文章目录 前言1. DHCP网络管理协议什么是 DHCPDHCP 两种分配机制 2. NAT网络地址转换协议什么是 NATNAT 技术使用NAT网络设备间如何通信两个内网设备相互通信不同内网中的设备相互通信NAT IP转换过程 NAPT 技术NAT 技术的缺陷 3. IPv6 协议什么是 IPv6 总结 前言 在之前的文章…...

使用决策树预测隐形眼镜类型

任务描述 本关任务&#xff1a;编写一个例子讲解决策树如何预测患者需要佩戴的隐形眼镜类型。使用小数据集&#xff0c;我们就可以利用决策树学到很多知识&#xff1a;眼科医生是如何判断患者需要佩戴的镜片类型&#xff0c;一旦理解了决策树的工作原理&#xff0c;我们甚至也…...

[ACTF2020 新生赛]BackupFile 1

题目环境&#xff1a; 好好好&#xff0c;让找源文件是吧&#xff1f;咱们二话不说直接扫它后台 使用dirsearch工具扫描网站后台&#xff08;博主有这个工具的压缩包&#xff0c;可以私聊我领取&#xff09;python dirsearch.py -u http://0d418151-ebaf-4f26-86b2-5363ed16530…...

解决vuex刷新数据丢失

Vuex 是一个 Vue.js 的状态管理库&#xff0c;它使得你可以在 Vue 组件之间共享状态。当你在 Vuex 中更新状态时&#xff0c;如果你遇到数据丢失或数据不一致的问题&#xff0c;可能需要进行深度复制或者使用其他方式来确保数据的完整性。 假设你有一个 Vuex 存储&#xff0c;…...

linux系统下读取当前硬盘的温度

这个其实很简单&#xff0c;借助于smartctl工具&#xff08;Ubuntu默认安装好了&#xff09;&#xff0c;标红的部分就是当前温度&#xff0c;单位是摄氏度。 sudo smartctl -l scttempsts /dev/sda...

python 深度学习 解决遇到的报错问题8

本篇继python 深度学习 解决遇到的报错问题7-CSDN博客 目录 一、OSError: [WinError 127] 找不到指定的程序。 Error loading "D:\my_ruanjian\conda-myenvs\deeplearning\lib\site-packages\torch\lib\caffe2_detectron_ops.dll" or one of its dependencies. 二、…...

Linux pipe()系统调用示例

Linux系统调用pipe函数&#xff0c;创建一个pipe&#xff0c;通过传入的fd数组返回pipe的读、写两端。 其中fd[ 0 ]用于读&#xff0c;fd[ 1 ]用于写。 一个pipe是单向数据传输的&#xff0c;不用用于父子进程双向读写。创建2个pipe实现父子进程间的双线读写。 #include <u…...

音频中的采样率和比特率

音频中的采样率和比特率 采样频率千比特率音频比特率 采样频率 参考&#xff1a;https://blog.csdn.net/qq_38907791/article/details/88925224 采样频率&#xff0c;也称为采样速度或者采样率&#xff0c;定义了每秒从连续信号中提取并组成离散信号的采样个数&#xff0c;它…...

Python常用脚本

1.解压指定文件夹内的zip包&#xff0c;解压到当前位置 import os import zipfile# 指定文件夹路径 folder_path "/path/to/your/folder"# 获取文件夹下所有的zip文件 zip_files [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.e…...

Redis5 分布式系统之主从模式

目录 分布式系统 引子 分布式系统类型 主从模式 一个主节点和多个从节点 创建多个节点方法 配置主从结构 主从模式知识 主从复制 拓扑结构 1.一主一从 2.一主多从 3.树形主从 主从实现原理 psync数据同步 全量复制和部分复制 psync流程 1.全量数据同步 2.部…...

【黑马程序员】Maven 进阶

文章目录 前言一、分模块开发与设计1. 分模块开发意义2. 分模块开发&#xff08;模块拆分&#xff09;2.1 创建 Maven 模块2.2 书写模块代码2.3 通过 Maven 指令安装模块到本地仓库&#xff08;install 指令&#xff09; 二、依赖管理1. 依赖传递1.1 依赖传递冲突问题 2. 可选依…...

231108 C语言memset当第三个参数为0,即设置个数为零也不报错

memset语法&#xff1a; void *memset(void *s, int c, size_t n); 犹豫第三个参数为0会不会报错&#xff0c;测试不会。 代码&#xff1a; #include"stdio.h" #include"stdlib.h" // memset memcpy int main() { int sig[100] { 0 }; int …...

HMM与LTP词性标注之马尔科夫模型(HMM原理剖析)

文章目录 问题描述viterbi算法联合概率与条件概率维特比算法实例 问题描述 viterbi算法 联合概率与条件概率 维特比算法实例...

Python自动化测试selenium指定截图文件名方法

这篇文章主要介绍了Python自动化测试selenium指定截图文件名方法&#xff0c;Selenium 支持 Web 浏览器的自动化&#xff0c;它提供一套测试函数&#xff0c;用于支持 Web 自动化测试&#xff0c;下文基于python实现指定截图文件名方法&#xff0c;需要的小伙伴可以参考一下 前…...

Linux 实现文件后半部分的复制

继上次实现文件从后往前数2k的数据进行复制&#xff0c;此次要求是文件的一半且是后半部分。 即复制源文件sour_file的后半部分到dest_file 除了数据上从后2K变化到后一半之外&#xff0c;其他的几乎没有什么变化。 这道题的关键点就在于后一半怎么求&#xff0c;在经历了用 …...

阿里开源中间件一览

1. 概述以及竞品对比 间件介绍官方链接竞品竞品介绍异同点对比Dubbo高性能的RPC框架&#xff0c;用于实现分布式服务的调用和管理。DubbogRPC gRPC是由Google开源的一款高性能、通用的RPC框架&#xff0c;支持多种编程语言 链接&#xff1a;gRPC Dubbo更注重于服务治理和可扩展…...

Ubuntu20.04下Salome_meca 2022软件安装(支持GPU加速)

一、什么是Salome_meca &#xff1f; Salome_meca 是一个开源的有限元分析软件套件&#xff0c;主要用于模拟和分析复杂的力学问题。它是 Salome 平台的一部分&#xff0c;Salome 是一个通用的集成化软件环境&#xff0c;用于建模、预处理、模拟和后处理各种复杂的工程和科学问…...

uniapp:打包ios配置隐私协议框

使用uniapp打包ios 上架商店需要配置隐私协议政策弹窗。当用户点击确定后才能继续操作。 首先manifest.json中配置使用原生隐私政策提示框是不支持ios的。不用勾选。 解决思路&#xff1a; 1、新建页面&#xff1a;iosLogin.vue&#xff0c;pages.json中 这个页面需要放在第一…...

JS逆向爬虫---请求参数加密③【比特币交易爬虫】

查询参数确定 t无加密 请求头参数加密 X-Apikey参数加密确定 X-Apikey逆向 const API_KEY "a2c903cc-b31e-4547-9299-b6d07b7631ab" function encryptApiKey(){ var t API_KEY, e t.split(""), n e.splice(0, 8);return t e.concat(n).join("&…...

云计算:未来科技的超级英雄

随着科技的不断发展&#xff0c;云计算已经成为了现代社会的核心驱动力之一。从智能家居到无人驾驶&#xff0c;从虚拟现实到人工智能&#xff0c;云计算的崭新时代已经到来&#xff0c;为我们的生活带来了智能、便捷和有趣的体验。本文将带领读者穿越时空&#xff0c;探索未来…...

【Node.js入门】1.3 开始开发Node.js应用程序

1.3 开始开发Node.js应用程序 学习目标 &#xff08;1&#xff09;熟悉开发工具Visual Studio Code的基本使用&#xff1b; &#xff08;2&#xff09;掌握Node.js应用程序的编写、运行和调试的基本方法。 构建第一个 Node.js应用程序 代码 const http require("htt…...

ansible-playbook之file模块

1、file模块功能说明 file模块主要用于远程主机上的文件操作&#xff0c;file模块包含如下选项&#xff1a; force&#xff1a;需要在两种情况下强制创建软链接&#xff0c;一种是源文件不存在但之后会建立的情况下&#xff1b;另一种是目标软链接已存在,需要先取消之前的软…...