《C++面向对象高效编程(第2版)》——3.5 this 指针和名称重整的进一步说明

简介:

本节书摘来自异步社区出版社《C++面向对象高效编程(第2版)》一书中的第3章,第3.5节,作者: 【美】Kayshav Dattatri,更多章节内容可以访问云栖社区“异步社区”公众号查看。

3.5 this 指针和名称重整的进一步说明

C++面向对象高效编程(第2版)
在前面介绍的实现中,我们有时使用this指针访问对象的数据成员。如第2章所述,成员函数中的this指针指向调用该成员函数的对象。在前述的main()程序中,如下代码
`
a.Push(i);`
通过a对象调用Push成员函数。在Push成员函数内部,this指针持有a对象的地址。以这样的方式,成员函数可以访问对象内的任何元素(数据成员和成员函数)。如第2章所述,编译器像实现其他函数那样,实现每个成员函数,但是,每个成员函数应该可以通过某种方法访问调用它的对象。为达到这个目的,this指针将作为隐藏的参数传递给每个成员函数,且this指针通常是函数接收的第1个参数(其后是已声明的其他参数)。假定Push成员函数的实现如下:

void Push(TIntStack* this, int what)
{
   // 实现代码如前所述
   // 通过a调用Push时,即a.Push(10),
   // “this”指针指向a对象。
}```
然而,Push可能是一个已经使用的函数名。或者说,在其他类中可能也包含了Push函数。编译器(或链接器)如何区别它们?我们如何表示重载的构造函数和析构函数?要回答这些问题,必须使用函数名重整(function name mangling)的概念。

注意:
ANSI C++ 语言标准对名称重整的样式未作要求(稍后讨论)。编译器的实现者可以自行规定名称重整的方案(甚至包括所有权)。了解一下名称重整有好处,但并不需要理解详细的重整方案。以下示例仅用于介绍概念,其重整方案依不同的编译器而已。

类的每个成员函数都包含类名(实际上是this指针的类型)和一些其他的信息。Push成员函数可能变成:
`
void Push 9TIntStackFi()`
在数字9前面有两条下划线(ASCII _,十进制为95)。数字代表类名的字符个数(TIntStack为9),这对于解析名称很有帮助。如果程序员不得不查找类(某函数所属)的名称,那么只需先查找 和其后的数字(假设为N),然后提取数字后的N个字符,即可获得该函数所属的类名。剩余的字符代表参数类型和返回值类型。注意,只有参数类型(不是参数名)才用于名称重整。 后的字符序列按照该函数已声明的所有参数顺序编码。在上面的重整名称中,Push为原始名,`9TIntStack`表明该函数的第1个参数(即this指针)类型为`TIntStack`,F 1表明该函数为全局函数,i表明该函数接受1个整数参数。返回值类型不是重整名称的一部分。

构造函数并没有特别的名称!它的名称与类名相同。为表示构造函数,在重整过程中,会在类的重整名称前加上

`__ct__`
因此,重整后的默认构造函数应该是:

TIntStack();  // 普通的未重整名称
__ct__9TIntStackFv();  // 重整后的名称`
同样地,此处的9TIntStack仍代表this指针的类型,F表明该函数为全局函数,v表明该函数不接受任何参数(void)。

其他的构造函数(如果有的话)也会包含参数,因此,它们确切的名称应该不同。例如,复制构造函数应该是:

TIntStack(const TIntStack&);  //未重整名称
__ct__9TIntStackF9TIntStackRC();  // 重整名称
// R 代表引用,C 代表const。```
第二个9TIntStack代表参数,RC表明该参数类型为`const`的引用。欲了解名称重整的细节,详见ARM 2。

根据以上的分析,析构函数(每个类只有一个)应该为:

~TIntStack();  // 未重整名称
__dt__9TIntStackFv();  // 重整名称`
类似地,操作符函数也有经过特定编码的重整名。例如,赋值操作符被编码成:

__as__9TIntStack();
全局new()操作符以__nw开始,全局delete操作符以__dl开始。

语言中所有的操作符都有预定义的(pre-defined)编码。

你可能会问,为什么要知道关于名称重整的这些细节?

想象一下,你实现了一个类,但忘记实现一些成员函数。编译过程可能没什么问题,但链接器会对未定义的函数报错。这些显示在报错信息中的函数,看上去与你在类头文件3中的函数不一样。现在,你彻底糊涂了!

这是因为名称重整的缘故。链接器在报错时绝不会打印原始的函数名,它只会给出由编译器提供的等价重整名。链接器对重整一无所知(至少到目前为止,我还未见过链接器了解此事),它只会抱怨那些未定义的名称。此时,如果了解一些名称重整会对你有帮助。如果链接器打印出的名称以 ct 开始,则说明出问题的是某个构造函数,这缩小了查找未定义成员函数的范围。随着你编写的C++代码越来越多,重整名称会成为你破译报错信息的秘密武器!相信我。
    

警告:
记住,this指针是个非常神圣的指针。不能修改this指针,绝不能给它赋值!需要在成员函数内修改this指针的情况非常少见,稍后将会介绍这样的示例。
你可能也注意到,在每个成员函数内,我们并未用this指针访问数据成员。例如,在Pop函数中,无需任何限定符,即可直接访问数据成员_count。无论何时在成员函数中使用数据成员的名称,编译器都会为其预先添加“this”限定符。因此,_count成为this->_count。除非我们明确需要指向对象的指针,否则不必显式使用它。

样式:

在本书中,只要有可能出现混淆的地方,都会显式使用this限定符。例如,在之前的赋值操作符中,有如下语句:

this->_count = source._count;
也可以写成:

_count = source._count;  // 这样写也正确!
但是,同一语句中,通过不同的对象多次用相同的名称_count会令人困惑。因此,要显式使用this限定符。

什么时候必须使用this指针?当我们希望返回对调用某函数的对象的引用时,必须使用*this。否则,如何显式表示“我的对象”这个概念?另一种情况是(如前所述的赋值操作符中),我们希望获得对象的地址,也必须显式使用this名称。到目前为止,这是显式使用this名称最常见的两种情况。

1一些编译器用N和F区别near和far函数,即用N代表near函数,用F代表far函数。
2译者注:ARM指的是Margaret A. Ellis与Bjarne Stroustrup合著的书:Annotated C++ Reference Manual。ISBN 0-201-51459-1。
3尽管链接器(和开发环境)越来越智能,开发环境为用户显示未经重整的名称也越来越普遍。但是,粗略了解一下名称重整仍然有好处。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

相关文章
|
4月前
|
缓存 安全 编译器
C++面试周刊(3):面试不慌,这样回答指针与引用,青铜秒变王者
《C++面试冲刺周刊》第三期聚焦指针与引用的区别,从青铜到王者级别面试回答解析,助你21天系统备战,直击高频考点,提升实战能力,轻松应对大厂C++面试。
477 132
C++面试周刊(3):面试不慌,这样回答指针与引用,青铜秒变王者
|
4月前
|
存储 C++
C++语言中指针变量int和取值操作ptr详细说明。
总结起来,在 C++ 中正确理解和运用 int 类型地址及其相关取值、设定等操纵至关重要且基础性强:定义 int 类型 pointer 需加星号;初始化 pointer 需配合 & 取址;读写 pointer 执向之处需配合 * 解引用操纵进行。
457 12
|
11月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
246 19
|
11月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
447 13
|
11月前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
356 5
|
11月前
|
存储 C++
【C++面向对象——输入输出流】处理二进制文件(头歌实践教学平台习题)【合集】
本任务要求使用C++读取二进制文件并在每行前添加行号后输出到控制台。主要内容包括: 1. **任务描述**:用二进制方式打开指定文件,为每一行添加行号并输出。 2. **相关知识**: - 流类库中常用的类及其成员函数(如`iostream`、`fstream`等)。 - 标准输入输出及格式控制(如`cin`、`cout`和`iomanip`中的格式化函数)。 - 文件的应用方法(文本文件和二进制文件的读写操作)。 3. **编程要求**:编写程序,通过命令行参数传递文件名,使用`getline`读取数据并用`cout`输出带行号的内容。 4. **实验步骤**:参考实验指
305 5
|
11月前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
262 5
|
11月前
|
Serverless 编译器 C++
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
269 4
|
11月前
|
设计模式 IDE 编译器
【C++面向对象——类的多态性与虚函数】编写教学游戏:认识动物(头歌实践教学平台习题)【合集】
本项目旨在通过C++编程实现一个教学游戏,帮助小朋友认识动物。程序设计了一个动物园场景,包含Dog、Bird和Frog三种动物。每个动物都有move和shout行为,用于展示其特征。游戏随机挑选10个动物,前5个供学习,后5个用于测试。使用虚函数和多态实现不同动物的行为,确保代码灵活扩展。此外,通过typeid获取对象类型,并利用strstr辅助判断类型。相关头文件如<string>、<cstdlib>等确保程序正常运行。最终,根据小朋友的回答计算得分,提供互动学习体验。 - **任务描述**:编写教学游戏,随机挑选10个动物进行展示与测试。 - **类设计**:基类
221 3
|
12月前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。