解开缺省参数与函数重载的衣裳

解开缺省参数与函数重载的衣裳

    • 代码是如何由编译器变为可执行文件?
      • 预处理 ->编译->汇编->链接
      • 预处理
      • 编译
      • 汇编
      • 链接
    • 语法了解
      • 缺省参数
      • 语法实践
      • 语法探究
      • 函数重载
      • 语法实践
      • 语法探究
      • 结语

本期和大家一起探究C++中的缺省函数与重载函数的语法说明与汇编过程

请添加图片描述

代码是如何由编译器变为可执行文件?

预处理 ->编译->汇编->链接

代码在由编译器变为可执行文件时,要经过编译和链接在这两个大过程,其中编译又可分为预处理,编译,汇编三个过程。

预处理

在预处理阶段,源文件和头文件会被处理为.i为后缀的文件,在gcc环境中将test.c预处理为test.i的命令如下:

gcc -E test.c -o test.i

预处理的规则如下:

  1. 将所有的#define删除,同时展开所有宏定义
  2. 处理所有的条件编译指令,如:#if , #ifdef , #elif , #else , #endif
  3. 处理#include预编译指令,将包含的头文件内容插入到该预编译指令的位置,这个过程是递归实现的,所以说被包含的头文件也可能包含其他的文件
  4. 删除所有注释
  5. 添加行号和文件名标识,方便后续编译器生成调试信息
  6. 保留所有的#pragma的编译器指令,编译器后续会使用

编译

编译是将预处理后的文件进行词法分析、语法分析、语义分析及优化检查语法)后,生成汇编代码,也就是.s文件
gcc中命令如下:

gcc -S test.i -o test.s

汇编

汇编器是将汇编代码转转变成机器可执⾏的指令,每⼀个汇编语句⼏乎都对应⼀条机器指令,所以在汇编就是将汇编码转成二进制机器码
gcc中指令如下:

gcc -c test.s -o test.o

链接

链接是将一堆文件链接到一起生成可执行文件,其主要过程包括:地址和空间分配,符号决议和重定位等这些步骤。解决了一个项目多文件,多模块之间相互调用的问题。

在了解 预处理 ->编译->汇编->链接 过程之后,我们来以缺省参数与函数重载来实践性的了解了解。

语法了解

缺省参数

概念:缺省参数是声明或定义时为函数的参数指定一个缺省值(默认值),在调用函数值,若没有指定实参则采用该形参的缺省值,否则使用指定的实参。
注意:

  1. 半缺省参数,缺省值必须从右往左给出,不能间隔着给。
  2. 当函数的声明与定义分离时,缺省值以该文件声明时的为主,若在同一文件下则只能声明。
  3. 缺省值必须是常数或者全局变量。
  4. C语言不支持。

语法实践

参考代码如下:

#include<iostream>int add(int a = 0, int b = 1)
{return a + b;
}int main()
{std::cout << add() << std::endl;std::cout << add(2) << std::endl;std::cout << add(2,2) << std::endl;return 0;
}

根据上述代码,我们可以看到当我们使用add函数时,

  1. 在全无实参的情况下,形参a,b使用缺省值,所以输出结果应为1 (0+1);
  2. 在只有一个实参的情况下,传值从左到右,唯一的实参1传给了ab继续使用缺省值,所以输出结果应为3 (2+1);
  3. 在实参都没缺少的的时候,就相当于普通的函数实现一般,所以输出结果应为4 (2+2);
    测试结果如下:
    在这里插入图片描述

语法探究

我们可以看到如下定义的函数参数为全缺省参数

int add(int a = 0, int b = 1)
{return a + b;
}

我们再来看看在main函数中调用该函数语句的汇编码,如下:

	add();
00801AC1  push        1  
00801AC3  push        0  
00801AC5  call        add (0801375h)  
00801ACA  add         esp,8  add(2);
00801ACD  push        1  
00801ACF  push        2  
00801AD1  call        add (0801375h)  
00801AD6  add         esp,8  add(2,2);
00801AD9  push        2  
00801ADB  push        2  
00801ADD  call        add (0801375h)  
00801AE2  add         esp,8  

我们可以看到由于栈的特性,在缺少实参时向栈中压入的缺省参数是由右到左的。
add()为例,先push 1b=1push 0a=0
这是否就可以间接地解释为什么半缺省参数,缺省值必须从右往左给出,不能间隔着给?
在调用函数时所开辟的栈帧中,先传参在调用,在缺少实参时就向栈中压入(push)缺省值,然后再调用add函数。

我们再来想想为什么当函数的声明与定义分离时,push的缺省值以该文件声明时的为主?
这就不得不来看看编译阶段了。

  • 在编译阶段,首先扫描器会对代码进行词法分析,将代码中的字符分割为一系列的记号(关键字、标识符、字⾯量、特殊字符等)
  • 接下来语法分析器,将对扫描产⽣的记号进⾏语法分析,从⽽产⽣语法树
  • 由语义分析器来完成语义分析,即对表达式的语法层⾯分析,这个阶段会报告错误的语法信息。

如果声明与定义分离,例如将add函数定义在test.cc中,而add函数的声明在C语言是最好的语言.cpp
我们先来看看两段有意思的运行代码:

1.在不同文件下函数的声明与定义分离缺省值以该文件声明时的为主。
在这里插入图片描述

2.在同一文件下,函数的声明与定义分离,缺省值只能在声明中。
在这里插入图片描述
看着这两组图片,我不禁陷入沉思,为什么会是这样的运行结果呢?

  • 对于第一种情况,不同文件下函数的声明与定义分离,编译器并不是对整个项目组同时一起进行检查,而是对每个文件进行检查(即每个文件独立检查)所以在第一种情况下,test.cc的add函数中可以理解为函数的声明和定义并没有分离,而在C语言是最好的语言.cpp中,可以看做add函数只是做了声明。而在后续调用该函数进行计算时能够运行,是因为编译器。在进行了。编译和汇编之后生成了与机器指令对应的汇编码,编译器将一个项目的多个文件链接在一起生成可执行程序,所以才有了如图的输出结果。
  • 而对于第二种情况,在同一文件下,缺省值在函数的声明与定义中同时定义,所以在编译阶段的语法检查时,就直接报错,并且中断了程序。

如下图就是一个add函数的调用过程:
在这里插入图片描述

函数重载

概念:函数重载是一种特殊的情况,C++允许在同一作用域中声明几个功能不同函数名相同,但是要求形参类型或个数或类型顺序不同。
实践作用:常用来处理功能类似数据类型不同的问题。

语法实践

参考代码如下:

#include<iostream>int add(int a, int b)
{return a + b;
}double add(double a, double b)
{return a + b;
}double add(double a, int b)
{return a + b;
}int main()
{std::cout << add(2.2,2.2) << std::endl;std::cout << add(2,2) << std::endl;std::cout << add(2.2,2) << std::endl;return 0;
}

根据上述代码,我们可以看到当我们使用add函数时,

  1. 当两个实参为2.2时,调用double add(double a, double b)返回4.4
  2. 当两个实参为2时,调用int add(int a, int b)返回4
  3. 当一个实参为2.2,另一个实参为2时,调用double add(double a, int b)返回4.2

测试结果如下:
在这里插入图片描述

语法探究

对于一个同名文件为什么可以通过参数的不同来进行调用呢?编译器又是如何区分的呢?

面对着这两个问题,我不经陷入沉思,于是打开了我的Linux来对其一趟究竟。

我们依旧使用上一个举例的代码。
输入Linux指令

g++ -S explore.cc -o explore.s

得到汇编码如下:

	.file	"explore.cc".local	_ZStL8__ioinit.comm	_ZStL8__ioinit,1,1.text.globl	_Z3addii.type	_Z3addii, @function
_Z3addii:
.LFB971:.cfi_startprocpushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6movl	%edi, -4(%rbp)movl	%esi, -8(%rbp)movl	-8(%rbp), %eaxmovl	-4(%rbp), %edxaddl	%edx, %eaxpopq	%rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE971:.size	_Z3addii, .-_Z3addii.globl	_Z3adddd.type	_Z3adddd, @function
_Z3adddd:
.LFB972:.cfi_startprocpushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6movsd	%xmm0, -8(%rbp)movsd	%xmm1, -16(%rbp)movsd	-8(%rbp), %xmm0addsd	-16(%rbp), %xmm0movsd	%xmm0, -24(%rbp)movq	-24(%rbp), %raxmovq	%rax, -24(%rbp)movsd	-24(%rbp), %xmm0popq	%rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE972:.size	_Z3adddd, .-_Z3adddd.globl	_Z3adddi.type	_Z3adddi, @function
_Z3adddi:
.LFB973:.cfi_startprocpushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6movsd	%xmm0, -8(%rbp)movl	%edi, -12(%rbp)cvtsi2sd	-12(%rbp), %xmm0addsd	-8(%rbp), %xmm0movsd	%xmm0, -24(%rbp)movq	-24(%rbp), %raxmovq	%rax, -24(%rbp)movsd	-24(%rbp), %xmm0popq	%rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE973:.size	_Z3adddi, .-_Z3adddi.globl	main.type	main, @function
main:
.LFB974:.cfi_startprocpushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6subq	$16, %rspmovabsq	$4612136378390124954, %rdxmovabsq	$4612136378390124954, %raxmovq	%rdx, -8(%rbp)movsd	-8(%rbp), %xmm1movq	%rax, -8(%rbp)movsd	-8(%rbp), %xmm0call	_Z3addddmovsd	%xmm0, -8(%rbp)movq	-8(%rbp), %raxmovq	%rax, -8(%rbp)movsd	-8(%rbp), %xmm0movl	$_ZSt4cout, %edicall	_ZNSolsEdmovl	$_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %esimovq	%rax, %rdicall	_ZNSolsEPFRSoS_Emovl	$2, %esimovl	$2, %edicall	_Z3addiimovl	%eax, %esimovl	$_ZSt4cout, %edicall	_ZNSolsEimovl	$_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %esimovq	%rax, %rdicall	_ZNSolsEPFRSoS_Emovabsq	$4612136378390124954, %raxmovl	$2, %edimovq	%rax, -8(%rbp)movsd	-8(%rbp), %xmm0call	_Z3adddimovsd	%xmm0, -8(%rbp)movq	-8(%rbp), %raxmovq	%rax, -8(%rbp)movsd	-8(%rbp), %xmm0movl	$_ZSt4cout, %edicall	_ZNSolsEdmovl	$_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %esimovq	%rax, %rdicall	_ZNSolsEPFRSoS_Emovl	$0, %eaxleave.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE974:.size	main, .-main.type	_Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB981:.cfi_startprocpushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6subq	$16, %rspmovl	%edi, -4(%rbp)movl	%esi, -8(%rbp)cmpl	$1, -4(%rbp)jne	.L9cmpl	$65535, -8(%rbp)jne	.L9movl	$_ZStL8__ioinit, %edicall	_ZNSt8ios_base4InitC1Evmovl	$__dso_handle, %edxmovl	$_ZStL8__ioinit, %esimovl	$_ZNSt8ios_base4InitD1Ev, %edicall	__cxa_atexit
.L9:leave.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE981:.size	_Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii.type	_GLOBAL__sub_I__Z3addii, @function
_GLOBAL__sub_I__Z3addii:
.LFB982:.cfi_startprocpushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6movl	$65535, %esimovl	$1, %edicall	_Z41__static_initialization_and_destruction_0iipopq	%rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE982:.size	_GLOBAL__sub_I__Z3addii, .-_GLOBAL__sub_I__Z3addii.section	.init_array,"aw".align 8.quad	_GLOBAL__sub_I__Z3addii.hidden	__dso_handle.ident	"GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)".section	.note.GNU-stack,"",@progbits

汇编码虽然有点长,但是我们可以看到在Linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
在该例子中代码与修饰后的名字如下:

int add(int ,int )  --> _Z3addii
int add(double ,double )  -->_Z3adddd
int add(double ,int )  -->_Z3adddi

所以这就是为什么有函数重载


Linux下g++的命名修饰规则:

_z + 后面接的数字表示函数名字的字符个数 + 函数名字 + 从左到右参数类型的依次缩写


这就很好的说明了形参类型或个数或类型顺序不同,可以支持函数重载。
同时,我们也可以看到Linux下g++的命名修饰规则并没有对函数的返回类型进行修饰,所以函数的返回类型不同不能理解为函数重载。

结语

以上就是本期的全部内容,若有错误请务必指出,喜欢就请多多关注吧!!!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SpringBoot技术专题】「开发实战系列」Undertow web容器的入门实战及调优方案精讲

Undertow web容器的入门实战及调优方案精讲 Undertow web容器Undertow 介绍官网API给出一句话概述Undertow&#xff1a;官网API总结特点&#xff1a;Lightweight&#xff08;轻量级&#xff09;HTTP Upgrade Support&#xff08;支持http升级&#xff09;、HTTP/2 Support支持H…

鸿蒙开发-UI-布局-弹性布局

地方 鸿蒙开发-UI-布局 鸿蒙开发-UI-布局-线性布局 鸿蒙开发-UI-布局-层叠布局 文章目录 前言 一、基本概念 二、布局方向 1、主轴为水平方向 2、主轴为垂直方向 三、布局换行 四、对齐方式 1、主轴对齐方式 2、交叉轴对齐方式 2.1、容器组件设置交叉轴对齐 2.2、子组件设置交叉…

EtherNet/IP开发:C++搭建基础模块,EtherNet/IP源代码

这里是CIP资料的协议层级图&#xff0c;讲解协议构造。 ODVA&#xff08;www.ODVA.org&#xff09;成立于1995年&#xff0c;是一个全球性协会&#xff0c;其成员包括世界领先的自动化公司。结合其成员的支持&#xff0c;ODVA的使命是在工业自动化中推进开放、可互操作的信息和…

python实现图片式PDF转可搜索word文档[OCR](已打包exe文件)

目录 1、介绍 1.1、痛点 1.2、程序介绍 2、安装方式 2.1、&#x1f53a;必要环节 2.2、脚本安装 2.2.1、不太推荐的方式 2.2.2、节约内存的方式 2.3、⭐完整版安装 3、使用 3.1、最终文件目录 3.2、主程序 3.2.1、绝对路径 3.2.2、是否为书籍 3.2.3、⭐截取区域 …

二维码地址门牌管理系统:智能便捷的社区管理

文章目录 前言一、全面智能化管理功能二、智能门牌与便捷服务三、提升管理效率与安全四、系统带来的活力与便利五、期待未来的创新与突破 前言 随着科技的飞速发展&#xff0c;社区管理正在迎来前所未有的变革。二维码地址门牌管理系统作为一款创新工具&#xff0c;为居民和管…

【优化技术专题】「性能优化系列」针对Java对象压缩及序列化技术的探索之路

针对Java对象压缩及序列化技术的探索之路 序列化和反序列化为何需要有序列化呢&#xff1f;Java实现序列化的方式二进制格式 指定语言层级二进制格式 跨语言层级JSON 格式化类JSON格式化&#xff1a;XML文件格式化 序列化的分类在速度的对比上一般有如下规律&#xff1a;Java…

【音视频】基于ffmpeg对视频的切割/合成/推流

背景 基于FFmpeg对视频进行切割、合成和推流的价值和意义在于它提供了一种高效、灵活且免费的方式来实现视频内容的定制、管理和分发。通过FFmpeg&#xff0c;用户可以轻松地剪辑视频片段&#xff0c;根据需要去除不必要的部分或提取特定时间段的内容&#xff0c;从而优化观看…

一遍文章教你快速入门vue3+ts+Echarts

之前做得项目有vue2和vue3,使用echarts的方式大同小异&#xff0c;这篇文章就先介绍vue3的用法 下载echart 可以看官方文档&#xff0c;其实说得很清楚echart官方 npm install echarts --save按需引入echart 由于我得项目中使用到得echart不多&#xff0c;所以这里我引入几个…

当 OpenTelemetry 遇上阿里云 Prometheus

作者&#xff1a;逸陵 背景 在云原生可观测蓬勃发展的当下&#xff0c;想必大家对 OpenTelemetry & Prometheus 并不是太陌生。OpenTelemetry 是 CNCF&#xff08;Cloud Native Computing Foundation&#xff09;旗下的开源项目&#xff0c;它的目标是在云原生时代成为应…

Vue 实例创建流程

✨ 专栏介绍 在当今Web开发领域中&#xff0c;构建交互性强、可复用且易于维护的用户界面是至关重要的。而Vue.js作为一款现代化且流行的JavaScript框架&#xff0c;正是为了满足这些需求而诞生。它采用了MVVM架构模式&#xff0c;并通过数据驱动和组件化的方式&#xff0c;使…

怎么把文件资料做成二维码?扫码下发文件更方便

想要快速的将一份或者多分资料下发给其他人时&#xff0c;如果群发之外有什么其他的方法可以使用呢&#xff1f;现在大家一般都是在手机上获取内容&#xff0c;如果通过群发的方式还需要接收下载&#xff0c;占用自己手机一定的空间容量&#xff0c;而且会有有效期的限制。那么…

4.servera修改主机名,配置网络,以及在cmd中远程登录servera的操作

1.先关闭这两节省资源 2.对于新主机修改主机名&#xff0c;配置网络 一、配置网络 1.推荐图形化界面nmtui 修改完成后测试 在redhat ping一下 在redhat远程登录severa 2、使用nmcli来修改网络配置 2.1、配置要求&#xff1a;主机名&#xff1a; node1.domain250.exam…

项目管理流程

优质博文 IT-BLOG-CN 一、简介 项目是为提供某项独特产品【独特指:创造出与以往不同或者多个方面与以往有所区别产品或服务&#xff0c;所以日复一日重复的工作就不属于项目】、服务或成果所做的临时性【临时性指:项目有明确的开始时间和明确的结束时间&#xff0c;不会无限期…

CLion调试Nodejs源码

【环境】 macOS node-v20.11.0源码 CLion 2023.3.2 【1】下载源码 https://nodejs.org/en/download/ 【2】编译源码 解压后的目录如下 进入解压后的目录进行编译 ./configure --debug make -C out BUILDTYPEDebug -j 4需要好久… 编译成功之后在node-v20.11.0目录下会有一个…

赛车游戏简单单车C语言版

#include<stdio.h> #include<easyx.h> #include<time.h>#define WIDTH 512 #define HEIGHT 768//定义一个汽车类 struct FCar {//坐标float x, y;// 汽车种类int type;//汽车速度float speed; };//定义全局变量 图片坐标 IMAGE BG_IMG; //背景图片坐标 float…

HarmonyOS 通过Web组件嵌套网络应用

我们今天来说说 在程序中嵌套一个网址地址 HarmonyOS中是通过一个简单的WEB组件来实现 网络应用就是相当于网址地址 通过链接将应用嵌入到手机当中 WEB组件需要两个参数 一个是 src 地址 要嵌套的网址 另一个是 控制器 我们可以先编写代码如下 import webview from "o…

*Maven依赖管理之排除传递性依赖的实例

Maven依赖管理之排除传递性依赖的实例 在使用Maven构建项目时&#xff0c;我们通常会依赖于一系列库和框架&#xff0c;其中一些依赖可能会引入其他依赖&#xff0c;这就是所谓的传递性依赖。有时候&#xff0c;为了解决冲突或者更精确地控制项目中所使用的库的版本&#xff0c…

LV.13 D12 Linux内核调试及rootfs移植 学习笔记

一、根文件系统 1.1 根文件系统 根文件系统是内核启动后挂载的第一个文件系统系统引导程序会在根文件系统挂载后从中把一些基本的初始化脚本和服务等加载到内存中去运行 1.2 根文件系统内容 bin shell命令(elf格式)(通过busybox编译生成) dev …

基于深度学习的细胞感染性识别与判定

基于深度学习的细胞感染性识别与判定 基于深度学习的细胞感染性识别与判定引言项目背景项目意义项目实施数据采集与预处理模型选择与训练模型评估与优化 结果与展望结论 基于深度学习的细胞感染性识别与判定 引言 随着深度学习技术的不断发展&#xff0c;其在医学图像处理领域…

Java开发分析 -- JProfiler 14

JProfiler 14是一款专业的Java性能分析工具&#xff0c;用于分析运行中的JVM内部情况。它能够帮助开发人员解决生产系统遇到的问题&#xff0c;优化性能&#xff0c;并定位到具体的代码问题。JProfiler 14提供了四大功能模块&#xff1a;方法调用、分配、线程和锁以及高层子系统…