C/C++进阶-函数

C/C++入门-函数起始

  • 函数
    • 引用与指针
    • 函数参数 指针写法 和 数组写法
    • 数组的引用
    • 右值引用
      • 概念:
      • **反汇编:**
      • 总结
      • 用结构体的示例再理解一遍
    • 函数的本质
    • 栈分析
      • 栈溢出攻击
  • 函数重载
    • 函数重载 进阶 思考
    • 函数重载补充
  • 函数模板
    • (1)
    • (2)函数模板的大坑
    • (3)显示的为函数模板指定一个类型
    • 函数模板 例外情况

函数

引用与指针

在函数中的应用:
如果直接传递内容,一些比较大的内容(如自定义的结构体)传递会非常耗费系统资源。
于是可以用引用和指针传递内容的地址,如此函数便可以通过地址获取到内容,避免了传递整个内容带来的开销。

用做函数参数时,没有太多区别,凭个人习惯
重点:
例如void test(int a)
①需要在函数中修改变量的值:
void test(int& a)
或者
void test(int* a)
②不需要在函数中修改变量的值:
void test(const int& a)
void test(const int&& a)//右值引用

函数参数 指针写法 和 数组写法

示例:

void test(int a[])
{
	cout << *a << endl;
}
void test(int* a)
{
	cout << *a << endl;
}

这两种方式并无区别,本质都是传递指针,使用方法也一样:

int a{ 100 };
test(&a);

非要说区别,那可能是传递数组时用数组写法更直观;

数组的引用

int a[100]
int (&b)[100] = a;

示例:

int getSum(int arr[], int count)
{
	int sum{};
	for (int i = 0; i < count; i++)
		sum += arr[i];
	return sum;
}
int getSum(int(&arr)[10])
{//数组的引用 的方式
	//cout << "大小:" << sizeof(arr);
	int sum{};
	for (auto x : arr)
		sum += x;
	return sum;
}
int main()
{
	int arr[10]{ 11,12,13,14,15,16,17,18,19,20 };
	cout << "求和:" << getSum(arr, 10)<<endl;
	cout << "求和:" << getSum(arr) << endl;
}

输出
求和:155
求和:155

优点:不知道
缺点:大小得固定,像这里函数形参大小就得写死

右值引用

概念:

左值:有明确内存空间,可以改值的
右值:没有明确的内存空间,临时拿来用的

声明语法:

int&& q{ 100 + 200 };
q = 300 + 400;

应用右值引用的示例

void printInt(int&& a)
{
	cout << a;
}

int main()
{
	int a{ 99 };

	printInt(a + 100);
}

输出
199
上述代码实现的结果 等同于:

void printInt(int a)//注意这里传的时int类型值,不是引用
{
	cout << a;
}

int main()
{
	int a{ 99 };

	printInt(a + 100);
}

那么区别在哪里呢

反汇编:

	printInt(a + 100);//void printInt(int a)
00F1236D  mov         eax,dword ptr [a]  
00F12370  add         eax,64h  
00F12373  push        eax  
00F12374  call        printInt (0F11492h)  
00F12379  add         esp,4  
//对比
	printInt(a + 100);//void printInt(int&& a)
00A5236D  mov         eax,dword ptr [a]  
00A52370  add         eax,64h  
00A52373  mov         dword ptr [ebp-0D4h],eax  
00A52379  lea         ecx,[ebp-0D4h]  
00A5237F  push        ecx  
00A52380  call        printInt (0A51497h)  
00A52385  add         esp,4   

可见右值引用会给值找一个内存先存起来

那么如果用左值引用呢?
反例:在这里插入图片描述

总结

函数 用引用传递参数是为了避免传输过大的内容导致时空的浪费,
但普通的左值引用无法解决上述问题,若采用先运算再参,如下:
int ad{ a + 100 };
printInt(ad);
此时变量ad又多占了内存,
于是,便可以采用右值应用解决问题;

用结构体的示例再理解一遍

struct Role
{
int HP;
unsigned MP;
};
Role createRole()
{
Role role{ 100,100 };
return role;
}
需求:通过打印查看createRole的效果,无需保存创建的结构体;
方案1:

void showRole(Role role)
{
	cout << role.HP << endl;
	cout << role.MP << endl;
}
int main()
{
	showRole(createRole());
}

传递了整个结构体,开销太大

方案2:

void showRole(Role& role)
{
	cout << role.HP << endl;
	cout << role.MP << endl;
}
int main()
{
	Role role = createRole();
	showRole(role);
}

方案3:

void showRole(Role&& role)
{
	cout << role.HP << endl;
	cout << role.MP << endl;
}
int main()
{
	showRole(createRole());
}

函数的本质

函数调用过程:
1.参数压栈
2.调用函数,汇编指令call
执行函数之前,会将当前执行的位置压栈push,以便执行完函数再回到这里
3.函数执行完毕"}",pop退栈,回到之前执行的位置

栈分析

栈的本质是一段提前分配好的内存空间,主要用来存放临时变量

esp 用来保存栈顶指针
ebp 保存当前栈帧的基址指针,以便在函数执行完毕后能够正确的恢复调用者的栈帧

push 32 指令的操作
①esp =esp-4//先移动栈顶指针
②esp的内存地址里写入32

call CD1020的操作
esp=esp-4
esp的内存地址里写入call的下一条指令的地址CD107F
CPU执行跳转到CD1020

pop ebp 操作
①esp里的内存地址里的值写入ebp
②esp = esp+4

ret 操作
CPU执行跳转到esp的内存地址里的值
esp = esp+4
20minute

关键点:
1、栈是一个连续的内存空间,可以通过int类型(因为指针是4字节/8字节)的数组访问栈的内容
2、跳转之前,会保存下一条指令的地址,以便返回来继续执行

栈溢出攻击

利用栈是一块连续地址的特性,一旦破坏掉栈中用于返回的函数地址信息,就会引发程序崩溃,甚至系统崩溃;或者将这个地址换成植入的病毒的运行地址,就可以进一步控制对方。

函数重载

概念:不同类型返回值、不同类型参数、参数个数不同的函数,函数名可以重复。
作用:使用时方便,比如:int add(int a,int b),float add1(int a,int b),…;都是求和函数,用的时候却要区分,就不太方便,而C++支持函数重载,可以写成:

int add(const int& a, const int& b)
{
	return (a + b);
}
float add(const float& a, const float& b, const float& c)
{
	return (a + b + c);
}

调用时:
在这里插入图片描述
在这里插入图片描述
注意:返回类型可以不同,但函数重载不能只通过返回类型区分

函数重载 进阶 思考

(情况1)

void showType(int a)
{
	cout <<"传递了一个int" << endl;
}
void showType(float a)
{
	cout << "传递了一个float" << endl;
}
int main()
{
	char a{ 100 };
	showType(a);
}

在这里插入图片描述
(情况2)

void showType(int& a)
{
	cout <<"传递了一个int" << endl;
}
void showType(float a)
{
	cout << "传递了一个float" << endl;
}
int main()
{
	char a{ 100 };
	showType(a);
}

在这里插入图片描述
(情况3)

void showType(int& a)
{
	cout <<"传递了一个int" << endl;
}
void showType(float a)
{
	cout << "传递了一个float" << endl;
}
int main()
{
	char a{ 100 };
	showType((int)a);
}

在这里插入图片描述

提示:临时变量没有引用

可以自己多练几个示例,看看不同类型参数 传递时的情况
在补充两个
(情况4)

void showType(int a)
{
	cout <<"传递了一个int" << endl;
}
void showType(float a)
{
	cout << "传递了一个float" << endl;
}
int main()
{
	double a{ 100 };
	showType(a);
}

在这里插入图片描述

(情况5)

void showType(int a)
{
	cout <<"传递了一个int" << endl;
}

int main()
{
	double a{ 100 };
	showType(a);
}

在这里插入图片描述

函数重载补充

(1)常量引用 和 变量引用是可以重载的;如:void test(int& a);和void test(const int& a);
(2)重载函数的参数如果全有默认值,那么调用时就不能完全不传入值,因为会产生歧义

函数模板

(1)

示例:
首先,我们有如下两个函数,这两个函数只有类型不一样。

int add(const int& a, const int& b)
{
	return (a + b);
}
float add(const float& a, const float& b)
{
	return (a + b);
}

如这样的求和功能,如果再加上char,short,long long等类型,那么就需要写很多个函数。
为了方便,C++引入函数模板的概念

将上述求和函数重写为函数模板:

template <typename type1>type1 add(type1 a,type1 b)
{
	return (a+b);
}

使用:

int a{ 6 }, b{ 3 };
cout << add(a, b);

结果打印:9

(2)函数模板的大坑

(2.1)函数模板的typename可以是任何类型,取决于用的时候给什么类型,可以是引用、指针等,另外也可以在模板上加上*,&,&&,** 等
(2.2)还有const、static等关键字,直接让函数模板变得极其复杂。
(2.3)并且,在函数的实现里,诸如运算、赋值等操作,面对传入进来的是指针、常量等情况,在使用时需要非常注意。

(示例1)函数模板 结合 函数重载

template <typename type1>type1 add(type1&& a, type1&& b)
{
	return (a + b);
}
template <typename type1>type1 add(type1& a, type1& b)
{
	return (a + b);
}

使用:

int a{ 6 }, b{ 3 };
cout << add(a,b) << endl;
cout << add(100.1 + 200.3, 1.1 + 2.2) << endl;

输出
9
303.7

(示例2)

(3)显示的为函数模板指定一个类型

(示例:当输入参数类型不统一时,编译器无法判断)
在这里插入图片描述
解决方法:
在这里插入图片描述

函数模板 例外情况

当一个模板满足不了需求时,例如:

template <typename type1>type1 bigger(type1 a,type1 b)
{
	return (a > b) ? a : b;
}

int main()
{
	int	a{ 999 }, b{ 300 };
	cout << bigger(a, b) << endl;
	cout << bigger(&a, &b) << endl;
}

运行结果:
999
00B5FE08
显然这里的00B5FE08不是我们想要的结果

为了解决这种问题,我们可以再写一个模板:

template <typename type1>type1 bigger(type1 a,type1 b)
{
	return (a > b) ? a : b;
}
template <>int* bigger(int* a, int* b)
{
	return (*a > *b) ? a : b;
}
int main()
{
	int	a{ 999 }, b{ 300 };
	cout << bigger(a, b) << endl;
	cout << *bigger(&a, &b) << endl;
}

在这里插入图片描述
输出结果:
999
999

注意:这里的模板
template <>int* bigger(int* a, int* b)
{
return (*a > *b) ?a : b;
}
模板上的类型必须有已知模板与其对应,如下面这种就不行:
在这里插入图片描述

但类似的需求我们依旧可以结合函数重载来解决
总之 函数模板+例外处理+函数重载 就是非常灵活
处理调用的优先级:更为具体的函数重载 > 模板例外 > 模板

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

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

相关文章

通俗易懂之线性回归时序预测PyTorch实践

线性回归&#xff08;Linear Regression&#xff09;是机器学习中最基本且广泛应用的算法之一。它不仅作为入门学习的经典案例&#xff0c;也是许多复杂模型的基础。本文将全面介绍线性回归的原理、应用&#xff0c;并通过一段PyTorch代码进行实践演示&#xff0c;帮助读者深入…

分布式主键ID生成方式-snowflake雪花算法

这里写自定义目录标题 一、业务场景二、技术选型1、UUID方案2、Leaf方案-美团&#xff08;基于数据库自增id&#xff09;3、Snowflake雪花算法方案 总结 一、业务场景 大量的业务数据需要保存到数据库中&#xff0c;原来的单库单表的方式扛不住大数据量、高并发&#xff0c;需…

在 C# 中显示动画 GIF 并在运行时更改它们

您可以通过将按钮、图片框、标签或其他控件的Image属性设置为 GIF 文件 来显示动画 GIF 。&#xff08;如果您在窗体的BackgroundImage属性中显示一个&#xff0c;则不会获得动画。&#xff09; 有几种方法可以在运行时更改 GIF。 首先&#xff0c;您可以将 GIF 添加为资源。…

【技术支持】安卓无线adb调试连接方式

Android 10 及更低版本&#xff0c;需要借助 USB 手机和电脑需连接在同一 WiFi 下&#xff1b;手机开启开发者选项和 USB 调试模式&#xff0c;并通过 USB 连接电脑&#xff08;即adb devices可以查看到手机&#xff09;&#xff1b;设置手机的监听adb tcpip 5555;拔掉 USB 线…

【网络】计算机网络的分类 局域网 (LAN) 广域网 (WAN) 城域网 (MAN)个域网(PAN)

局域网是通过路由器接入广域网的 分布范围 局域网Local Area Network&#xff1a;小范围覆盖&#xff0c;速度高&#xff0c;延迟低(办公室&#xff0c;家庭&#xff0c;校园&#xff0c;网络) 广域网Wide Area Network 大范围覆盖&#xff0c;速度相对低&#xff0c;延迟高…

scanf:数据之舟的摆渡人,静卧输入港湾的诗意守候

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。* 这一节我们主要来学习scanf的基本用法&#xff0c;了解scanf返回值&#xff0c;懂得scanf占位符和赋值…

win10 gt520+p106双卡测试

安装391.35驱动失败,虽然gpuz和设备管理器显示正常但没有nvidia控制面板 重启进安全模式,ddu卸载,再次重启到安全模式,安装391.01驱动,显示3dvision安装失败,重启再看已经有nvidia控制面板了 修改p106注册表 AdapterType 1 EnableMsHybrid 1 计算机\HKEY_LOCAL_MACHINE\SYSTE…

C# OpenCV机器视觉:霍夫变换

在一个阳光灿烂得近乎放肆的午后&#xff0c;阿强的实验室就像被施了魔法的科学城堡&#xff0c;到处闪耀着神秘的科技光芒。阿强呢&#xff0c;像个即将踏上惊险征程的探险家&#xff0c;一屁股坐在那堆满奇奇怪怪设备的桌前&#xff0c;眼神中透露出按捺不住的兴奋劲儿&#…

【深度学习基础】线性神经网络 | 线性回归的简洁实现

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上&#xff0c;结合当代大数据和大算力的发展而发展出来的。深度学习最重…

工业级手持地面站(支持Android和IOS)技术详解!

一、硬件平台的选择 无人机遥控器为了支持Android和iOS系统&#xff0c;通常会选择高性能的处理器和操作系统作为硬件基础。例如&#xff0c;一些高端遥控器可能采用基于ARM架构的高性能处理器&#xff0c;这些处理器能够高效地运行Android或iOS操作系统&#xff0c;并提供足够…

CatLog的使用

一 CatLog的简介 1.1 作用 CAT&#xff08;Central Application Tracking&#xff09; 是基于 Java 开发的实时应用监控平台&#xff0c;为美团点评提供了全面的实时监控告警服务。 1.2 组成部分 1.2.1 Transaction 1.Transaction 适合记录跨越系统边界的程序访问行为&a…

vue elementui 大文件进度条下载

下载进度条 <el-card class"box-card" v-if"downloadProgress > 0"><div>正在下载文件...</div><el-progress :text-inside"true" :stroke-width"26" :percentage"downloadProgress" status"…

TensorRT-LLM中的MoE并行推理

2种并行方式&#xff1a; moe_tp_size&#xff1a;按照维度切分&#xff0c;每个GPU拥有所有Expert的一部分权重。 moe_ep_size: 按照Expert切分&#xff0c;每个GPU有用一部分Expert的所有权重。 二者可以搭配一起使用。 限制&#xff1a;二者的乘积&#xff0c;必须等于模…

计算机的错误计算(二百零五)

摘要 基于一位读者的问题&#xff0c;提出题目&#xff1a;能用数值计算证明 吗&#xff1f;请选用不同的点&#xff08;即差别大的数&#xff09;与不同的精度。实验表明&#xff0c;大模型理解了题意。但是&#xff0c;其推理能力值得商榷。 例1. 就摘要中问题&#xff0…

关于TCP/IP五层结构的理解

关于TCP/IP五层结构的理解 TCP/IP五层模型 是目前被广泛采用的一种模型,我们可以将 TCP / IP 模型看作是 OSI 七层模型的精简版本&#xff0c;由以下 5 层组成&#xff1a; 1. 应用层&#xff1a;应用层是体系结构中的最高层&#xff0c;定义了应用进程间通信和交互的规则。本…

Unity3D仿星露谷物语开发19之库存栏丢弃及交互道具

1、目标 从库存栏中把道具拖到游戏场景中&#xff0c;库存栏中道具数相应做减法或者删除道具。同时在库存栏中可以交换两个道具的位置。 2、UIInventorySlot设置Raycast属性 在UIInventorySlot中&#xff0c;我们只希望最外层的UIInventorySlot响应Raycast&#xff0c;他下面…

Sprint Boot教程之五十:Spring Boot JpaRepository 示例

Spring Boot JpaRepository 示例 Spring Boot建立在 Spring 之上&#xff0c;包含 Spring 的所有功能。由于其快速的生产就绪环境&#xff0c;使开发人员能够直接专注于逻辑&#xff0c;而不必费力配置和设置&#xff0c;因此如今它正成为开发人员的最爱。Spring Boot 是一个基…

C++ STL map和set的使用

序列式容器和关联式容器 想必大家已经接触过一些容器如&#xff1a;list&#xff0c;vector&#xff0c;deque&#xff0c;array&#xff0c;forward_list&#xff0c;string等&#xff0c;这些容器统称为系列容器。因为逻辑结构为线性的&#xff0c;两个位置的存储的值一般是…

人工智能及深度学习的一些题目(三)

1、【填空题】 使用RNNCTC模型进行语音识别&#xff0c;在产生预测输出时&#xff0c;对于输入的音频特征序列通过网络预测产生对应的字母序列&#xff0c;可以使用&#xff08; beamsearch &#xff09;算法进行最优路径搜索。 2、【填空题】 逻辑回归模型属于有监督学习中的&…

《C++11》右值引用深度解析:性能优化的秘密武器

C11引入了一个新的概念——右值引用&#xff0c;这是一个相当深奥且重要的概念。为了理解右值引用&#xff0c;我们需要先理解左值和右值的概念&#xff0c;然后再理解左值引用和右值引用。本文将详细解析这些概念&#xff0c;并通过实例进行说明&#xff0c;以揭示右值引用如何…