【C++初阶】第一站:C++入门基础(中)-1

简介: 【C++初阶】第一站:C++入门基础(中)-1

5. 函数重载 (续)

C++支持函数重载的原理--名字修饰(name Mangling)

编译器是如何编译的?


Test.cpp


预处理头文件展开/宏替换/去掉注释/条件编译


Test.i


编译检查语法,生成汇编代码(指令级代码) -- 右击鼠标打开反汇编



66f628361f6649ff99a3f919b9c1d9b7.png

Test.s


汇编将汇编代码生成二进制机器码


Test.o


链接合并链接,生成可执行程序(a.out / xxx.exe)


在整个编译的过程中涉及到的一个问题是什么呢?


 

在一个项目里面写了一个stack.h(栈定义的各种接口)和stack.cpp ,这些各种接口不在Test.o内,而在stack.o内,那么怎么去这里找呢。那就涉及到名字去找地址,在链接的时候怎么用名字去找地址呢?


C语言的特点呢 -- 直接用函数名去充当函数的名字

98902d164d4b43f59d81bc63ba82e74f.png



       这样的后果就是自己都区分不开来,所以C语言是不允许重名的。


那么C++是如何这块的问题呢?如何把两个同名的但参数类型顺序不一样的函数区分开来呢?


🎯 那就是函数名修饰规则解决这个问题


当函数只有声明没有定义的时候,就会出现以下的链接错误


0eca1551fbd24a39ab5c178818d1dbcb.png


       🌼当函数只有定义的时候,没有实现的时候,它就没有一堆汇编指令,没有指令就不能生成地址(就没有建立函数栈帧的过程,寄存器没有存地址)。所以在符号表里面拿这个名字去找的时候就找不到,以下是修饰以后的函数名,本质上它是用类型带入这个名字里面去了(函数修饰规则)


52919aef0c5f4bf38cf0afbe40c41334.png


而C语言是直接用函数名去找:

796665e1443349f191dd3c3aeea7849e.png



在linux环境底下去看:


以下两张图均是函数名修饰规则


521d8c5f28344eeab3ee8d49742c54c4.png


       Linux底下:c++区分函数不同的依据是去找函数的地址(本质也就是第一句指令的地址),找到地址之后,将其函数名修饰成特殊函数名:


<_Z4funcid> 意思是:四个字节,func(int,double)
<_Z4funcdi> 意思是:四个字节,func(double,int)

🍔在符合表里面:


       用一个独特的符号去代表一个类型,跟据类型的个数不同、类型不同、类型的顺序不同,修饰出了的名字就是不一样的,所以根据这点,就可以在函数名相同的情况下区分不同的函数。


b9c4adec43a8473cab6c491eac1b6099.png


vs2019底下:


void __cdecl func(int,double)" (?func@@YAXHN@Z)
void __cdecl func(double,int)" (?func@@YAXNH@Z)
因为这两个函数虽然函数名字相同,但是函数类型的顺序不同,所以编译器会根据情况修饰成特殊的函数名


问题:


函数名修饰规则带入返回值,返回值不能能否构成重载? 不能。


6b8360a2e7c94b9aba927d38ed330993.png


为什么C++支持函数重载,而C语言不支持函数重载呢?

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。


d0ef3f05105648ee80657b1557d7cb3c.png


7280f5cd631d4aca93838e7b50f64d6d.png


1.实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们
可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标
文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么
怎么办呢?
2. 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就
会到b.o的符号表中找Add的地址,然后链接到一起。(老师要带同学们回顾一下)
3. 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的
函数名修饰规则。
4. 由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使
用了g++演示了这个修饰后的名字。
5. 通过下面我们可以看出gcc的函数修饰后名字不变。
而g++的函数修饰后变成:【_Z+函数长度+函数名+类型首字母】

采用

617794edf03e48d8b27ecf855ca39beb.png


结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。

采用C语言编译器编译后结果

468b9eb6d0914a5380e06e2f2c78dc8c.png


结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参

数类型信息添加到修改后的名字中。

Windows下名字修饰规则

fa9309dfd2944805ac7b9a839ccf9dea.png


对比Linux会发现,windows下vs编译器对函数名字修饰规则相对复杂难懂,但道理都

是类似的,我们就不做细致的研究了。

6. 通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修

饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。

7. 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。


6. 引用

引用概念

   

引用不是新定义一个变量,而 是给已存在变量取了一个别名 ,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

💤现实生活来说:比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。

     


       C++为了在拓展语法的过程中,为了防止创新符号太多不好记忆,直接沿用C语言的符号,让其一个符号赋予了多重意思,&在这边不是取地址的意思,而是引用操作符

1f22c924116a4db4b12460300da409d6.png



当b++时,a也会同时++,当两条语句都++时,那么这个值就变为2

11d3db5af4874b80ae2465e3c3db5757.png



代码实现:


int main()
{
  int a = 0;
  int& b = a;//引用
  cout << &a << endl;
  cout << &b << endl;
  return 0;
}

输出:

25688f16146542d4934c93fc3c145ce4.png


注意: 引用类型必须和引用 实体是 同种类型的

关于引用的应用:

1️⃣简单的应用,值的交换:

void swap(int& x1, int& x2)
 {
     int tmp = x1;
     x1 = x2;
     x2 = tmp;
 }
 int main()
 {
     int size;
     int x = 0, y = 1;
     swap(x,y);
     printf("%d %d", x, y);
 }

执行:


1b048a7d44e74f94affc1311e45ee816.png


2️⃣二叉树前序遍历的应用

int TreeSize(struct TreeNode* root)
 {
  //写法一
  if (root == NULL)
    return 0;
    //写法二
  return TreeSize(root->left)
    + TreeSize(root->right)
    + 1;
 }
 void _preorder(struct TreeNode* root, int* a, int& pi)
 {
  if (root == NULL)
    return;
  //用指针的方式是为了不在不同栈帧内创建i
  a[pi] = root->val;
    pi++;
  _preorder(root->left, a, pi);
  _preorder(root->right, a, pi);
 }
 int* preorderTraversal(struct TreeNode* root, int& returnSize)
 {
  int size = TreeSize(root);
  int* a = (int*)malloc(sizeof(int) * sizeof(int));
  int i = 0;
  _preorder(root,a,i);
  return a;
 }
 int main()
 {
     int size = 0;
     preorderTraversal(nullptr, size);
 }

执行:


809736f4eebc4c8aa8cec193e8399da5.png


3️⃣关于单链表的链接

前后代码对比:


827ccc49a1ad4a969357bf7def3943b3.png

【C++初阶】第一站:C++入门基础(中)-2

https://developer.aliyun.com/article/1456972


相关文章
|
6月前
|
存储 安全 编译器
c++入门
c++作为面向对象的语言与c的简单区别:c语言作为面向过程的语言还是跟c++有很大的区别的,比如说一个简单的五子棋的实现对于c语言面向过程的设计思路是首先分析解决这个问题的步骤:(1)开始游戏(2)黑子先走(3)绘制画面(4)判断输赢(5)轮到白子(6)绘制画面(7)判断输赢(8)返回步骤(2) (9)输出最后结果。但对于c++就不一样了,在下五子棋的例子中,用面向对象的方法来解决的话,首先将整个五子棋游戏分为三个对象:(1)黑白双方,这两方的行为是一样的。(2)棋盘系统,负责绘制画面。
99 0
|
10月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
9月前
|
存储 分布式计算 编译器
C++入门基础2
本内容主要讲解C++中的引用、inline函数和nullptr。引用是变量的别名,与原变量共享内存,定义时需初始化且不可更改指向对象,适用于传参和返回值以提高效率;const引用可增强代码灵活性。Inline函数通过展开提高效率,但是否展开由编译器决定,不建议分离声明与定义。Nullptr用于指针赋空,取代C语言中的NULL。最后鼓励持续学习,精进技能,提升竞争力。
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
133 0
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
179 0
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
10月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
6月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
191 0
|
6月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
281 0