C++的缺省参数与函数重载(重点!)

目录

缺省参数

缺省参数的分类

全缺省参数

半缺省参数

小应用

函数重载

名字修饰

预处理阶段

编译阶段

汇编阶段

链接阶段

“承诺”与“兑现”的依赖关系

小思考

C++函数名修饰规则

Linux中的引入方式

Windows中的引入方式

小拓展 


缺省参数

基本概念:是声明或定义函数时为函数的参数指定一个缺省值(默认值)。在调用该函数时,如果

没有指定实参则采用该形参的缺省值(默认值),否则使用指定的实参

#include <iostream>
using std::cout;
using std::endl;

void Func(int a = 0)
{
	cout << a << endl;
}

int main()
{
	Func(10);
	Func();
	return 0;
}

注意事项:不允许跳跃式的传递参数

#include <iostream>
using std::cout;
using std::endl;

void Func(int a = 10, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}

int main()
{
	Func(1,2,3);
	Func(1,2);
	Func(1);
	Func();
	
	Func(, 1, );  //wrong
	Func(, , 2);  //wrong
	Func(, 1, 2);  //wrong
	return 0;
}

缺省参数的分类

全缺省参数

特点:所有缺省参数都被赋值

void Func(int a = 10, int b = 20, int c = 30)

半缺省参数

特点:不是所有缺省参数都被赋值

void Func(int a, int b = 20, int c = 30)
  注意事项:
1、半缺省参数必须从右往左依次来给出,不能间隔着给,否则会有歧义,给函数传参时从左向右
依次传递
void Func(int a, int b = 20, int c = 30);  //right

void Func(int a = 20, int b = 30, int c);  //wrong

        若半缺省参数不能按照从右至左的顺序依次进行Func(int a = 20,int b = 30,int c),那么对于传参时只能从左至右依次传递的情况Func(1,2),函数的最后一个参数c是未被赋值的。

2、缺省参数不能在函数声明和定义中同时出现,否则会起冲突,且缺省值要在函数声明时提供(不做过多解释)

//a.h文件

void Func(int a = 10);

// a.cpp文件
void Func(int a = 20)
{

......

}

//wrong!!

3、缺省值必须是常量或全局变量(全局变量一般不用)

4、C语言不支持缺省参数 (编译器不支持)  

小应用

        在之前学习顺序表的时,当顺序表的空间不够用时我们就会申请整数倍的新空间,但是这样可能会造成空间的浪费,我们更希望的是一个萝卜一个坑,我们可以使用缺省参数实现这一效果,设置缺省的默认值为4,当有确定需要的空间大小时就按确定的空间大小来,如果没有就用默认值

#include <stdio.h>
#include <stdlib.h>

//顺序表的扩容
struct Stack
{
	int* a;
	int size;
	int capacity;
	//...
};

//顺序表的初始化
void StackInit(struct Stack* ps, int n = 4)//设置缺省参数n,并设置默认值为4
{
	ps->a = (int*)malloc(sizeof(int) * n); //扩容
}


int main()
{
	struct Stack st1;

	//1、确定要插入一百个数据
	StackInit(&st1, 100);

	//2、确定只插入十个数据
	StackInit(&st1, 10);

	//3、不知道要插入多少数据,那就直接用默认的缺省参数的4
	StackInit(&st1);
	return 0;
}

函数重载

基本概念:在同一个作用域内,可以定义多个具有相同名称但参数列表(参数的类型、参数的个数、参数类型的顺序)不同的函数

#include<iostream>
using namespace std;

// 1、参数类型不同
int Add(int left, int right)
    {
     cout << "int Add(int left, int right)" << endl;
     return left + right;
    }
double Add(double left, double right)
    {
     cout << "double Add(double left, double right)" << endl;
     return left + right;
    }

// 2、参数个数不同
void f()
    {
     cout << "f()" << endl;
    }

void f(int a)
    {
     cout << "f(int a)" << endl;
    }

// 3、参数类型顺序不同
void f(int a, char b)
    {
     cout << "f(int a,char b)" << endl;
    }

void f(char b, int a)
    {
     cout << "f(char b, int a)" << endl;
    }

int main()
{
     Add(10, 20);
     Add(10.1, 20.2);

     f();
     f(10);

     f(10, 'a');
     f('a', 10);

     return 0;
}

注意熟悉:C语言不允许同名函数

名字修饰

问题:为什么C++支持函数重载,而C语言不支持函数重载

        对于C语言而言,在代码执行的过程中会经过预处理、编译、汇编、链接四个阶段,下面我们

通过一个函数声明与定义分离的例子看一看代码在经过这四个阶段都会发生什么:

Stack.h文件

#pragma once
#include<stdlib.h>

struct Stack
{
	int* a;
	int size;
	int capacity;
	//...
};

void StackInit(struct Stack* ps, int n = 4);
void StackPush(struct Stack* ps, int x);

Stack.cpp文件(.cpp也行懒得改成.c了😘)

#include "Stack.h"

void StackInit(struct Stack* ps, int n)
{
	ps->a = (int*)malloc(sizeof(int) * n);
}

void StackPush(struct Stack* ps, int x)
{}

Test.cpp文件

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include"Stack.h"
using namespace std;

int main()
{
	struct Stack st1;

	// 1、确定要插入100个数据
	StackInit(&st1, 100);  // call StackInit(?)

	// 2、只插入10个数据
	struct Stack st2;
	StackInit(&st2, 10);   // call StackInit(?)

	// 3、不知道要插入多少个
	struct Stack st3;
	StackInit(&st3);

	return 0;
}

预处理阶段

        Stack.h文件会被展开并与Stcak.cpp的文件合并成新文件Stack.i,Test.cpp文件经预处理后

会变为Test.i文件

编译阶段

        检查完Stack.i和Test.i文件的语法无误后,会将它们生成由汇编指令组成的文件Stack.s和

Test.s,其中StackInit(&st1,100)对应的汇编指令是 :

        我们注意到其中一条汇编指令是“call + 地址”,表示调用StackInit函数这段代码转变为汇编

指令后就是call这个函数的地址(从底层的角度来讲函数调用的本质就是call+函数的地址,从语法

的角度来讲函数名就是其对应的地址而函数是一系列功能代码的集合故从底层的角度来讲函数

就是这些功能代码转变成的汇编指令的集合,而函数的地址就是这些指令的第一句指令的地址(有

像数组的地址,数组的地址就是它的第一个元素的地址)最后我们可以得到的结论是:调用函

的本质就是call一个函数的地址,然后通过该地址找到函数的指令集中的第一句指令的地址,然

后将这些指令依次取出交给CPU去依次执行,依次执行完后该函数的功能就完成了(也跟数组相

似,各个元素之间地址的距离相似)

        但是实际上在调试反汇编时,call的下一步会跳转到地址为0A001357的另一条汇编指令jmp处(相当于套了一个壳子,调用函数之前还要先把盒子jmp打开才行)关于这点作者能力有限就不过多解释,为了方便理解你也可以忽略这一步。

结论1:本质上讲一个函数的地址就是函数中第一条汇编指令的地址

结论2:函数声明不能得到函数的地址(空壳无内部指令),函数定义可以得到函数的地址(有内部指令)

        所以,有函数定义的Stack.s文件有StackInit函数的地址,而有函数声明的Test.s文件无该函

数的地址此外,只有函数声明的程序依然可以执行的原因是:针对只有函数声明没有定义的情

况,编译阶段的语法检查只负责检查声明是否存在以及是否正确(有并且还正确就行有多少不管)

汇编阶段

        将Stack.s和Test.s文件中的汇编指令转换为两份二进制的机器码文件Stcak.o和Test.o文件

(汇编指令是符号式的,为了方便我们理解而存在的,但是CPU看不懂这些只能看懂二进制的

机器码,比如汇编指令push可能就就会变为111代替之类的) 

链接阶段

        链接器尝试将源文件Stack.o和Test.o连接成一个可执行文件,但Test.o文件只有StackInit函

数的声明而没它的定义,此时系统就会用StackInit这个函数名去其它源文件中(Stack.o)找函数

地址(实际上就是在找该函数的定义)(编译阶段会有一个统计整个项目中各种内容比如变量、函

数以及其它标志符号的符号表)链接阶段会利用函数名在符号表中找到函数的地址

        如果此时将StackInit函数的定义内容取消,那么链接器就无法找到对应的 StackInit 函数定

义,会提示链接错误(出现链接问题就是函数只给了声明但是没给定义)

综合上述内容我们可以得到结论3:对编译器而言函数声明相当于承诺,函数定义相当于兑现承诺

“承诺”与“兑现”的依赖关系

  • 函数声明:可以被看作是对编译器做出的一种承诺。通过函数声明,你向编译器保证说“这个函数我会在程序中实现”,它告诉编译器有关该函数名称、参数类型和返回类型等信息。这使得在代码中可以使用该函数而不需要知道具体实现细节。

  • 函数定义:相当于兑现了之前所做出的承诺。在函数定义中,你提供了完整的代码实现,让编译器知道如何执行这个功能。只有当给定了完整定义并且提供了可执行代码时,该功能就真正变得可用,并且能够被其他部分调用。

        在链接阶段,在已经生成目标文件后(包含已经编写好但未被实际执行过程所需的信息),链接程序会去查找对应所有符号地址以建立最终可执行文件或库文件。

小思考

        让我们再回头看看对C++种的函数重载的定义:“在同一个作用域内,可以定义多个具有相同

名称但参数列表(参数的类型、参数的个数、参数类型的顺序)不同的函数”,这说明在C++中可

以存在多个名字相同的函数,而C语言链接阶段寻找函数地址的方法是在符号表中根据函数名找函

数地址,而如果出现多个重名函数那么就无法判断函数的地址谁是谁的。

        那么C++是如何处理这一问题的呢?

C++函数名修饰规则

基本概念:一种编译器将函数和变量名转换成特定格式的技术,以便支持函数重载和链接器能够区

分不同作用域下的同名函数

实现方法:为每个函数名引入它们各自的参数类型

注意事项:各个编译器有自己不同的引入方式

Linux中的引入方式

格式:_Z + 函数名字的长度 + 函数名 + 参数类型首字母

void f(int a,char b):call f(?)——>  call _Z1fic(?)

void f(int a,char b):call f(?)——>  call _Z1fci(?)

int Add(int left,int right):call Add(?)——>  call _Z3Addii(?)

double Add(double left,double right):call Add(?)——>call _Z3Addii(?)

  • _Z是前缀
  • 1和3表示函数名所占的字节数(在字符表中查找时是字符匹配,如果要找的函数名所占字节数为1就不会再花时间去找所占字节数为3的)
  • f和Add就是原来的函数名
  • ii、ic、ci就是函数中的参数类型的首字母

Windows中的引入方式

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

小拓展 

在Linux中用gcc和g++分别编译两个函数并查看编译后的结果:

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

结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中

~over~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python炒股自动化(3):分析取回的实时数据和历史数据

Python炒股自动化&#xff08;3&#xff09;&#xff1a;分析取回的实时数据和历史数据 这一节比较简单&#xff0c;但也有用&#xff0c;绝不是为了充数的&#xff08;狗头表情&#xff09;&#xff0c;上一节取到了实时和历史数据&#xff0c;都是这样的&#xff0c;不知道怎…

半导体行业案例:Jira与龙智插件助力某半导体企业实现精益项目管理

近日&#xff0c;龙智Atlassian技术团队收到了国内一家大型半导体企业的感谢信。龙智团队提供的半导体行业项目管理解决方案和服务受到了客户的好评&#xff1a; 在龙智团队的支持下&#xff0c;我们的业务取得了喜人的成果和进步。龙智公司的专业服务和产品&#xff0c;是我们…

android开发电子书,android基础编程

内存泄漏是什么&#xff1f; 内存泄漏即 ML &#xff08;Memory Leak&#xff09; 指 程序在申请内存后&#xff0c;当该内存不需再使用 但 却无法被释放 & 归还给 程序的现象 内存泄漏有哪些情况&#xff0c;对应的解决方案&#xff1f; 内存泄漏的原因归根到底就是当需…

C++笔记(五)--- 虚函数(virtual)

目录 虚函数介绍 虚函数、覆盖和重载区别 虚函数介绍 C的虚函数是多态性的表现 1.构造函数不能为虚函数2.子类继承时虚函数仍为虚函数3.虚函数类外实现时&#xff0c;不需要加virtual4.有虚函数的类&#xff0c;析构函数一定要写成虚函数&#xff08;否则可能会造成内存泄漏&…

2024-2-28-网络基础作用

1>思维导图 2>面试问题 I、 &#xff08;1&#xff09;什么是回调函数&#xff1f; 回调函数是作为参数传递给其他函数的函数。通过函数指针&#xff0c;例如异步编程、线程的创建函数。 &#xff08;2&#xff09;结构体与共用体的区别: 结构体是一种数据结构&…

WPF应用程序使用MVVM模式

文章目录 一、前言二、正文&#xff1a;模式 - WPF应用程序使用MVVM设计模式2.0 一些术语2.1 秩序与混乱2.2 MVVM模式的演变2.3 为何WPF开发者喜爱MVVM2.4 Demo应用程序2.5 路由命令逻辑2.6 ViewModel类层次结构2.7 ViewModelBase类2.8 CommandViewModel类2.9 MainWindowViewMo…

游戏小技巧-守卫羊村

春节期间玩了玩美团中的小游戏“守卫羊村”&#xff0c;发现个小技巧&#xff0c;或者可能也算个bug&#xff1a; 当小羊进入矿洞后&#xff0c;便可以在所属的封闭区域中建造建筑物。假如此时&#xff0c;有其它角色&#xff08;羊或狼均可&#xff09;在该封闭区域内&#xf…

面试笔记系列七之多线程+分布式系统基础知识点整理及常见面试题

介绍一下线程的生命周期及状态&#xff1f; 1.创建 当程序使用new关键字创建了一个线程之后&#xff0c;该线程就处于一个新建状态&#xff08;初始状态&#xff09;&#xff0c;此时它和其他Java对象一样&#xff0c;仅仅由Java虚拟机为其分配了内存&#xff0c;并初始化了其成…

flutter 人机验证实战

先看效果 基本思路 接口进行触发是否进行图像验证&#xff0c;验证后将结果携带到接口里面去&#xff0c;进行人机验证 使用的技术(可惜只有web版本的) 验证码2.0智能人机验证(VAPTCHA)- 安全、易用、完全免费手势验证码VAPTCHA是基于人工智能和大数据的次世代人机验证解决方案…

HTML列表

想要在HTML中实现列表功能&#xff0c;无序用<ul>&#xff0c;有序用<ol>&#xff0c;有手就行。 效果图&#xff1a; CODE: <!DOCTYPE html> <html> <body><h2>一个无序 HTML 列表</h2><ul><li>咖啡</li><…

网络爬虫的危害,如何有效的防止非法利用

近年来&#xff0c;不法分子利用“爬虫”软件收集公民隐私数据案件屡见不鲜。2023年8月23日&#xff0c;北京市高级人民法院召开北京法院侵犯公民个人信息犯罪案件审判情况新闻通报会&#xff0c;通报侵犯公民个人隐私信息案件审判情况&#xff0c;并发布典型案例。在这些典型案…

Apache Paimon Append Scalable表解析

1.Append Scalable Table a) 定义 在表属性中配置 ‘bucket’ ‘-1’&#xff0c;将进入 “unaware-bucket mode”&#xff0c;在此模式下不再有桶的概念&#xff0c;也不保证流任务读取数据的顺序&#xff0c;可以将此表视为批量离线表&#xff0c;所有记录都将进入一个目录…

Codeforces Round 929 (Div. 3)

Codeforces Round 929 (Div. 3) Codeforces Round 929 (Div. 3) A. Turtle Puzzle: Rearrange and Negate 题意&#xff1a;可以对整数数组进行两个操作&#xff0c;一是随意重新排列或保持不变&#xff0c;二是选择连续子段元素符号倒转&#xff0c;求可能最大的所有元素和…

hadoop学习中遇到的问题一

由于看视频总是断断续续&#xff0c;经常遇到各种报错&#xff0c;现将遇到的问题进行总结。 hadoop学习中遇到的问题&#xff1a;hadoop拒绝连接 hadoop安装好之后&#xff0c;在本地浏览器输入地址http://192.168.222.102:9870&#xff0c;提示拒绝连接。在网上找了很多相关…

【Quarto】Markdown导出PPT

title: “Quarto Basics” mainfont: “LXGW WenKai Mono” format: revealjs: theme: default incremental: true pptx: incremental: true html: code-fold: true beamer: incremental: true aspectratio: 169 QUARTO 这段代码是一个 YAML 头部&#xff08;front matter&…

Unity(第十一部)场景

游戏有多个场景组成&#xff08;新手村&#xff0c;某某副本&#xff0c;主城&#xff09; 场景是有多个物体组成&#xff08;怪物&#xff0c;地形&#xff0c;玩家等&#xff09; 物体是有多个组件组成&#xff08;刚体组件&#xff0c;自定义脚本&#xff09; 创建场景 编辑…

刷题笔记 洛谷 P1162 填涂颜色

思路来自 大佬 hat.openai.com/c/9c30032e-5fb9-4677-8c15-9ea6530dc6db 题目链接 P1162 填涂颜色 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路 搜索 首先 在外面围上一圈0开始搜素 因为题目说将封闭区域内的0变成2 我们可以在外面进行搜索 把外面所有可以搜索…

【LabVIEW 】串口如何读取长度不一致的字符串

工程经验 1、在循环中&#xff0c;加入定时器&#xff0c;这样可以一段时间读取一次。 2、只要获取完整的一帧数据&#xff0c;就可以进行过滤筛选。

Leetcode—82. 删除排序链表中的重复元素 II【中等】

2024每日刷题&#xff08;117&#xff09; Leetcode—82. 删除排序链表中的重复元素 II 实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val…

【踏雪无痕的痕四】——0到底是有还是没有?

目录 一、背景介绍三、过程1.0的历史发展&#xff1f;2.如何将0讲给一个刚上一年级的孩子&#xff1f;3.0的边界和意义&#xff1f;4.那四年&#xff0c;到底在培养什么&#xff1f;和0有什么关系&#xff1f; 四、总结 一、背景介绍 最近在看一年级数学&#xff0c;其中介绍到…