C++ Primer 类的作用域

news2025/2/22 0:02:00

欢迎阅读我的 【C++Primer】专栏

专栏简介:本专栏主要面向C++初学者,解释C++的一些基本概念和基础语言特性,涉及C++标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级程序设计技术。希望对读者有帮助!

在这里插入图片描述
在这里插入图片描述

目录

  • 7.4 类的作用域
    • 作用域和定义在类外部的成员
    • 名守查找与类的作用域
      • 用于类成员声明的名字查找
    • 类型名要特殊处理
    • 成员定义中的普通块作用域的名字查找
    • 类作用域之后,在外围的作用域中查找
    • 在文件中名字的出现处对其进行解析

7.4 类的作用域

每个类都会定义它自己的作用域。在类的作用域之外,普通的数据和函数成员只能由对象、引用或者指针使用成员访问运算符来访问。对于类类型成员则使用作用域运算符访问。不论哪种情况,跟在运算符之后的名字都必须是对应类的成员:

Screen::pos ht=24,wd=80;//使用Screen定义的pos类型
Screen scr(ht,wd,' ');
Screen *p=&scr;
char c = scr.get();//访问scr对象的get成员
c = p->get();//访问p所指对象的get成员

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

一个类就是一个作用域的事实能够很好地解释为什么当我们在类的外部定义成员函数时必须同时提供类名和函数名。在类的外部,成员的名字被隐藏起来了。

一旦遇到了类名,定义的剩余部分就在类的作用域之内了,这里的剩余部分包括参数列表和函数体。结果就是,我们可以直接使用类的其他成员而无须再次授权了。

例如,我们回顾一下Window_mgr类的clear成员,该函数的参数用到了Window_mgr类定义的一种类型:

void Window_mgr::clear(ScreenIndex i)
{
Screen&s=Screens[i];
s.contents=string(s.height*s.width,' ');
}

因为编详器在处理参数列表之前已经明确了我们当前正位于Window_mgr类的作用域中,所以不必再专门说明ScreenIndex是Window_mgr类定义的。出于同样的原因,编译器也能知道函数体中用到的screens也是在Window_mgr类中定义的。

另一方面,函数的返回类型通常出现在函数名之前。因此当成员函数定义在类的外部时,返回类型中使用的名字都位于类的作用域之外。这时,返回类型必须指明它是哪个类的成员。例如,我们可能向windowmgz类添加一个新的名为addscreen的函数,它负责向显示器添加一个新的屏幕。这个成员的返回类型将是ScreenIndex,用户可以通过它定位到指定的Screen:

class Window_mgr{
public:
//向窗口添加一个Screen,返回它的编号
ScreenIndex addScreen(const Screen&);
//其他成员与之前的版本一致
};
//首先处理返回类型,之后我们才进入Window_mgr的作用域
Window_mgr::ScreenIndex
Window_mgr::addScreen(const Screen&s)
{
    screens.push_back(s);
    return screens.size()-1;
}

因为返回类型出现在类名之前,所以事实上它是位于Window_mgr类的作用域之外的。在这种情况下,要想使用ScreenIndex作为返回类型,我们必须明确指定哪个类定义了它。

名守查找与类的作用域

在目前为止,我们编写的程序中,名字查找(name lookup)(寻找与所用名字最匹配的声明的过程)的过程比较直截了当:

  • 首先,在名字所在的块中寻找其声明语句,只考虚在名字的使用之前出现的声明。
  • 如果没找到,继续查找外层作用域。
  • 如果最终没有找到匹配的声明,则程序报错。

对于定义在类内部的成员函数来说,解析其中名字的方式与上述的查找规则有所区别,不过在当前的这个例子中体现得不太明显。类的定义分两步处理:

  • 首先,编译成员的声明。
  • 直到类全部可见后才编译函数体。

按照这种两阶段的方式处理类可以简化类代码的组织方式。因为成员函数体直到整个类可见后才会被处理,所以它能使用类中定义的任何名字。相反,如果函数的定义和成员的声明被同时处理,那么我们将不得不在成员函数中只使用那些已经出现的名字。

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

这种两阶段的处理方式只适用于成员函数中使用的名字。声明中使用的名字,包括返回类型或者参数列表中使用的名字,都必须在使用前确保可见。如果某个成员的声明使用了类中尚未出现的名字,则编译器将会在定义该类的作用域中继续查找。例如:

typedef double Money;
string bal;
class Account{
public:
Money balance(){ return bal; }
private:
Money bal;
};

当编译器看到balance函数的声明语句时,它将在Account类的范围内寻找对Money的声明.编译器只考虑Account中在使用Money前出现的声明,因为没找到匹配的成员,所以编译器会接着到Account的外层作用域中查找.在这个例子中,编译器会找到Money的typedef语句,该类型被用作balance函数的返回类型以及数据成员bal的类型。另一方面,balance函数体在整个类可见后才被处理,因此,该函数的return语句返回名为bal的成员,而非外层作用域的string对象。

类型名要特殊处理

-般来说,内层作用域可以重新定义外层作用域中的名字,即使该名字已经在内层作用域中使用过。然而在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字:

typedef double Money;
class Account{
public:
Money balance(){ return bal;}//使用外层作用域的Money
private:
typedef double Money;//错误:不能重新定义Money
Money bal;
// ...
}

需要特别注意的是,即使Account中定义的Money类型与外层作用域一致,上述代码仍然是错误的。

尽管重新定义类型名守是一种错误的行为,但是编译器并不为此负责。一些编译器仍将顺利通过这样的代码,而忽略代码有错的事实。

类型名的定义通常出现在类的开始处,这样就能确保所有使用该类的成员都出现在类名的定义之后。

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

成员函数中使用的名字按照如下方式解析:

  • 首先,在成员函数内查找该名字的声明。和前面一样,只有在函数使用之前出现的声明才被考虑。
  • 如果在成员函数内没有找到,则在类内继续查找,这时类的所有成员都可以被考虑。
  • 如果类内也没找到该名字的声明,在成员函数定义之前的作用域内继续查找。

一般来说,不建议使用其他成员的名字作为某个成员函数的参数。不过为了更好地解释名字的解析过程,我们不妨在dummy_fcn函数中暂时违反一下这个约定:

//注意:这段代码仅为了说明而用,不是一段很好的代码
//通常情况下不建议为参数和成员使用同样的名字
int height;//定义了一个名字,程后将在Screen中使用
class Screen{
public:
typedef std::string::size_type pos;
void dummy_fcn(pos height){
    cursor = width*height;//哪个height?是那个参数
}
private:
pos cursor=0;
pos height=0,width=0;
};

当编译器处理dummy_fcn中的乘法表达式时,它首先在函数作用域内查找表达式中用到的名字。函数的参数位于函数作用域内,因此dummy_fcn函数体内用到的名字height指的是参数声明。

在此例中,height参数隐藏了同名的成员。如果想绕开上面的查找规则,应该将代码变为:

//不建议的写法:成员函数中的名字不应该隐藏同名的成员
void Screen::dummy_fcn(pos height){
cursor=width*this->height;//成员height
//另外一种表示该成员的方式
cursor= width* Screen::height;//成员height
}

尽管类的成员被隐藏了,但我们任然可以通过加上类的名字或者显示的使用this指针来强制访问成员。

其实最好的确保我们使用height成员的方法是给参数起个其他名字:

//建议的写法:不要把成员名字作为参数或其他局部变量使用
void Screen::dummy_fcn(pos ht){
cursor=width*height;//成员height
}

在此例中,当编译器查找名字height时,显然在dummy_fcn函数内部是找不到的。编译器接着会在Screen内查找匹配的声明,即使height的声明出现在dummy_fcn使用它之后,编诙器也能正确地解析函数使用的是名为height的成员。

类作用域之后,在外围的作用域中查找

如果编译器在函数和类的作用域中都没有找到名字,它将接着在外围的作用域中查找。在我们的例子中,名字height定义在外层作用域中,且位于screen的定义之前。然而,外层作用域中的对象被名为height的成员隐藏掉了。因此,如果我们需要的是外层作用域中的名字,可以显式地通过作用域运算符来进行请求:

//不建议的写法:不要隐藏外层作用域中可能被用到的名守
void Screen::dummy_fcn(pos height){
    cursor=width*::height;//哪个height?是那个全局的
}

尽管外层的对象被隐藏掉了,但我伴依然可以用作用域运算符访问它。

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

当成员定义在类的外部时,名字查找的第三步不仅要考虑类定义之前的全局作用域中的声明,还需要考虑在成员函数定义之前的全局作用域中的声明。例如:

int height;//定义了一个名字,稍后将在Screen中使用
class Screen{
public:
typedef std::string::size_type pos;
void setHeight(pos);
pos height=0;//隐藏了外层作用域中的height
};

Screen::pos verify(Screen::pos);
void Screen::setHeight(pos var){
    //var:参数
    //height:类的成员
    //verify:全局函数
    height=verify(var);
}

请注意,全局函数verify的声明在Screen类的定义之前是不可见的。然而,名字查找的第三步包括了成员函数出现之前的全局作用域。在此例中,verify的声明位于 setHeight的定义之前,因此可以被正常使用。

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

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

相关文章

50页PDF|数字化转型成熟度模型与评估(附下载)

一、前言 这份报告依据GBT 43439-2023标准,详细介绍了数字化转型的成熟度模型和评估方法。报告将成熟度分为五个等级,从一级的基础转型意识,到五级的基于数据的生态价值构建与创新,涵盖了组织、技术、数据、资源、数字化运营等多…

机器学习实战(8):降维技术——主成分分析(PCA)

第8集:降维技术——主成分分析(PCA) 在机器学习中,降维(Dimensionality Reduction) 是一种重要的数据处理技术,用于减少特征维度、去除噪声并提高模型效率。主成分分析(Principal C…

前端插件使用xlsx-populate,花样配置excel内容,根据坐添加标替换excel内容,修改颜色,合并单元格...。

需求要求:业务人员有个非常复杂得excel表格,各种表头等,但是模板是固定得。当然也可以实现在excel上搞出各种表格,但是不如直接用已有模板替换其中要动态得内容方便,这里我们用到CSDN得 xlsx-populate 插件。 实列中我…

分布式大语言模型服务引擎vLLM论文解读

论文地址:Efficient Memory Management for Large Language Model Serving with PagedAttention 摘要 大语言模型(LLMs)的高吞吐量服务需要一次对足够多的请求进行批处理。然而,现有系统面临困境,因为每个请求的键值…

如何开发一个大模型应用?

1. 背景 AIGC技术的突破性进展彻底改变了技术开发的范式,尤其是以GPT为代表的LLM,凭借其强大的自然语言理解与生成能力,迅速成为全球科技领域的焦点。2023年末,随着ChatGPT的爆火,AIGC技术从实验室走向规模化应用&…

[数据结构]二叉搜索树详解

目录 一、二叉搜索树的概念 二、二叉搜索树的性能分析 三、二叉搜索树的中序遍历用于排序去重 四、二叉搜索树的查找 1、查找的非递归写法 2、查找的递归写法 五、二叉搜索树的插入 1、插入的非递归写法 2、插入的递归写法 六、二叉搜索树的删除 1、删除的非递归写法…

撕碎QT面具(2):groupBox内容居中显示

问题描述: 当笔者在GroupBox中使用Form Layout构建图中内容时,不能居中显示。 解决方案: 1、首先在form layout左右添加横向弹簧,并ctrl进行选中这三个控件。点击水平布局,让中间的控件不变形。 2、选中groupBox&#…

SpringBoot速成(14)文件上传P23-P26

1. 什么是 multipart/form-data? 想象一下,你有一个包裹要寄给朋友,但包裹里有不同类型的东西:比如一封信(文字)、一张照片(图片)和一个小礼物(文件)。为了确…

图论入门算法:拓扑排序(C++)

上文中我们了解了图的遍历(DFS/BFS), 本节我们来学习拓扑排序. 在图论中, 拓扑排序(Topological Sorting)是对一个有向无环图(Directed Acyclic Graph, DAG)的所有顶点进行排序的一种算法, 使得如果存在一条从顶点 u 到顶点 v 的有向边 (u, v) , 那么在排序后的序列中, u 一定…

【iOS】SwiftUI状态管理

State ObservedObject StateObject 的使用 import SwiftUIclass CountModel: ObservableObject {Published var count: Int 0 // 通过 Published 标记的变量会触发视图更新init() {print("TimerModel initialized at \(count)")} }struct ContentView: View {State…

自制简单的图片查看器(python)

图片格式:支持常见的图片格式(JPG、PNG、BMP、GIF)。 import os import tkinter as tk from tkinter import filedialog, messagebox from PIL import Image, ImageTkclass ImageViewer:def __init__(self, root):self.root rootself.root.…

ChatGPT行业热门应用提示词案例-AI绘画类

AI 绘画指令是一段用于指导 AI 绘画工具(如 DALLE、Midjourney 等)生成特定图像的文本描述。它通常包含场景、主体、风格、色彩、氛围等关键信息,帮助 AI 理解创作者的意图,从而生成符合要求的绘画作品。 ChatGPT 拥有海量的知识…

Visual Studio Code的下载安装与汉化

1.下载安装 Visual Studio Code的下载安装十分简单,在本电脑的应用商店直接下载安装----注意这是社区版-----一般社区版就足够用了---另外注意更改安装地址 2.下载插件 重启后就是中文版本了

分词器(Tokenizer) | 有了分词器,为什么还需要嵌入模型

文章目录 什么是tokenizer有了分词器,为什么还需要嵌入模型分词器为什么在transformers 里Hugging Face的Tokenizer大模型不同tokenizer训练效果对比分词器库选择当前顶尖大模型所采用的 Tokenizer 方法与词典大小 参考 什么是tokenizer Tokenizers huggingface官方…

scala中 隐式转换

一、 隐式转换: 编译器 偷偷地,自动地帮我们把一种数据类型转换为另一种类型 例如: int --> double object test {// 复习隐式转换// 隐式转换: 编译器 偷偷地,自动地帮我们把一种数据类型转换为另一…

实战开发coze应用-姓氏头像生成器(上)

​欢迎关注【AI技术开发者】 上次,我们开发了一个对话形式的头像生成器智能体(Agents),广受大家欢迎。 同时也接收到一些用户的反馈,生成前无法看到头像样式、初次使用不会用等等。 对此,我准备使用Coze开…

【Node.js】express框架

目录 1初识express框架 2 初步使用 2.1 安装 2.2 创建基本的Web服务器 2.3 监听方法 2.3.1 监听get请求 2.3.2 监听post请求 2.4 响应客户端 2.5 获取url中的参数(get) 2.5.1 获取查询参数 2.5.2 获取动态参数 2.6 托管静态资源 2.6.1 挂载路径前缀 2.6.2 托管多…

JS逆向实战三:1688工厂信息

本文说明:B站学习笔记整理,仅供学习参考~~ 网站:https://sale.1688.com/factory/category.html 1. 页面分析与解密 刷新页面,通过对关键词进行搜索,实现接口定位。 通过多次刷新页面或者页面翻页,找到变化…

Pipeline 获取 Jenkins参数

Pipeline 获取 Jenkins参数 Jenkins 提供了一系列默认的环境变量,这些变量在构建过程中可以被使用。以下是一些常见的 Jenkins 默认环境变量: WORKSPACE: 当前构建的工作目录路径 JOB_NAME: 当前构建的作业名称 BUILD_NUMBER: 当前构建的编号&#xff…

ESP32 在IDF_V5.3.1版本下实现AP无线热点模式!(带WIFI事件处理)

一、什么是ESP32的AP无线热点模式? ESP32 的 AP(Access Point)模式 是指 ESP32 作为无线接入点运行,它自己创建一个 Wi-Fi 网络,允许其他设备(如手机、电脑、平板等)直接连接到它上面&#xff0…