C#中的函数(三)参数传递及返回值
接前面二篇,继续开始新的研究
前面忘了说什么是主调函数与被调函数
主调函数:执行调用其它函数语句所在的函数
被调函数:被其它函数所调用的函数
简单说就是一个是发起调用者,另一个是被调用者
写个小例子说明下,一看就懂
Main函数就是主调函数,test_A()这句语句所在的函数就是主调函数
tset_A就是被调函数, 它是被主调函数Main中的语句test_A()进行调用的

重归正题
参数传递分为2类
1.普通传递(形参数据类型前面没有ref或者out关键字,传递的是变量中的数据)
2.引用传递(形参数据类型前面加上ref或者out关键字,传递的是变量在栈中的地址)
普通传递根据参数的数据类型分为普通传递值类型跟普通传递引用类型
普通传递值类型
小例子如下:

主调函数Main部份
1.定义二个变量a与b
2.主调函数里调用MyAdd,把变量a与b当作实参进行传递
被调函数MyAdd部份
1.把参数a与参数b进行相加,保存在临时变量c中
2.返回前对a与b进行修改,然后返回变量c中的二个数相加的结果
最终返回到Main函数中,变量a与b的值没有发生改变
总结下:
普通传递值类型,传递实参后被调用函数内部对它进行修改不会影响到主调函数中的变量
因为变量是值类型,变量中的数据是数值
普通传递引用类型
小例子如下:

主调函数Main部份
1.主调函数main中先实例化一个自定义的类TestClass,然后保存在引用类型变量temp中
2.调用被调函数test_A, 把变量temp当作实参进行传递
被调函数tset_A部份
1.把参数TestClass类型的对象temp中的MyName属性修改为大白腿
最终返回到Main函数中,引用类型变量temp中的Myname字段发生改变
总结下:
普通传递引用类型,传递实参后被调用函数内部对它进行修改会影响到主调函数中的变量
因为变量是引用类型,变量中的数据是引用地址,引用地址是对象在托管堆中的内存地址
当外部对引用类型变量进行修改时,相当于直接对托管堆中的数据进行了修改.
引用传递(形参数据类型前面加上ref或者out关键字,传递的是变量在栈中的地址)
引用传递的主要目的是对参数进行修改,然后让外部数据进行同步更新,返回多个数据的作用
写到这估计又有人有疑问了,直接在函数前面定义返回值用来接收返回值不行吗?
一来麻烦,因为如果需要对返回值进行接收,同步更新变量,那接收返回值还得进行赋值操作
二来返回值只能返回一个数据,如果函数有多个形参,需要对多个参数进修改,让外部数据进行同步更新,
起到返回多个数据的目的,那就只能使用引用传递的方式。
引用传递根据参数的数据类型分为引用传递值类型跟引用传递引用类型
根据前面说的普通传递,那引用类型的变量需要引用传递吗?大多数情况下不需要,因为前面说过,
引用类型的实参,传递的是引用地址,其它地方对它进行修改,会直接对堆中数据进行更改
真正常用的是多个值类型的实参,才需要使用引用传递的方式
写了一大堆引用传递,还是写个值类型的引用传递来看下效果
引用传递值类型 (out)
小例子如下:

主调函数Main部份
1.定义四个变量a,b,addNum,maxNum
2.调用Test函数,前二个实参a,b只是用来计算的,并不需要返回数据或者修改数据,所以不使用引用传递.
后二个参数是用来接收返回的二个数相加的结果,所以需要使用引用传递参数,这里使用的是out方式
被调函数
1. 对二个数进行相加,然后根据传递过来的addNum在主函数main中的变量地址,对这个地址写入新的数据
2. 对二个数进行比较,然后根据传递过来的maxNum在主函数main中的变量地址,对这个地址写入新的数据
从这里就能看出使用引用传递的好处了
顺带验证Test函数中addNum下到底是不是主函数main中的变量地址呢?
在Test中的addNum = a + b; 下个断点,添加临视,看下addNum中是什么
从图中看,addNum是值啊,不是地址…估计到这有人迷茫了.
其实这是vs特意隐藏了内部的细节,这里的值是是addNum在主函数main中的变量地址下的值,
并不是真正addNum下的数据…

为了证明这点,在主调函数的Test(a,b, out addNum, out maxNum); 这一行再下个断点

F5运行调试,此时断下来,我们转到反汇编,好好分析下它是怎么传递参数的
这七行反汇编代码对应的就是Test(a,b, out addNum, out maxNum);

第一句Lea eax,[ebp - 48h] //ebp-48h 是变量addNum在栈中的地址,Lea是传地址操作,相当于mov eax,ebp-0x48
第二句push eax //看过前二篇的就知道这是因为参数多于2个,使用压栈的方式传递参数,这里push的是addNum在栈中的地址
第三句Lea eax,[ebp - 4ch] //ebp-4ch 是变量maxNum在栈中的地址,Lea是传地址操作,相当于mov eax,ebp-0x4c
第四句push eax //看过前二篇的就知道这是因为参数多于2个,使用压栈的方式传递参数,这里push的是maxNum在栈中的地址
第五句mov ecx,dowrd ptr [ebp-40h] //这是传递的临时变量a
第六句mov edx,dowrd ptr [ebp-44h] //这是传递的临时变量b
第七句 call 00360c30 //通过间接调用的方式调用Test函数
这里可以单步执行,纪录下ebp-0x48 与ebp-0x4c的值
addNum = 071CE950
maxNum = 071CE94C
进入到Test函数内部, 先单步执行完前二句,查看下堆栈中的情况
[Ebp] = 0x071CE928 = 当前ESP栈顶
[EBP + 4] = 执行完当前函数后的返回地址
[EBP + 8 ] = 传递过来的第四个实参maxNum
[EBP + 0xC] = 传递过来的第三个实参addNum
这里的参数顺序是因为主调函数中是从左向右往堆栈中压入参数三跟参数四,但是在堆栈中栈的增长是从内存地址的高位往
内存地址的低位进行增长的,每压入一个数据,esp - 4开辟四个字节空间,然后esp指针指向esp-4的位置

后面的一些不需要关心了,转回源代码,直接在addNum = a + b; 这一行下个断点

F5运行调试,转到反汇编继续看
这四行反汇编代码对应的就是addNum = a + b;

第一行mov eax,dword ptr [ebp - 3ch]; //取得第一个实参a,赋值给寄存器eax
第二行add eax,dowrd ptr [ebp - 40h]; //取得第二个实参b,把寄存器eax与b进行相加,再赋值给eax
第三行mov edx,word ptr[ebp + 0ch]; //取得堆栈中的addNum的变量地址
第四行mov dword ptr [edx],eax //把二个数相加的结果写入到addNum的变量地址下
其实这里的引用传递的实参就是C++中的一个指针变量,不过C#没有指针这玩意就没法讲
确定了引用传递参数是传递地址,继续回来,使用ref来写个小例子
引用传递值类型 (ref)
小例子如下:

总结下:
ref跟out都是引用传递参数, 传递的是实参在主调函数的变量地址,在被调函数中参数是个指针变量,
参数指向了主调函数中的变量地址, 对参数进行操作,
实际就是对指针所指向的主调函数中的变量地址进行了读取或者写入数据
ref 跟 out的区别
ref 传递的实参在主调函数部份必须对它进行赋值,调用函数时实参前面加上ref关键字
在被调函数部份可以直接使用实参,不需要在被调函数里进行赋值
out 传递的实参在主调函数部份不需要对它进行赋值,调用函数时实参前面上out关键字
在被调函数部份不能直接使用实参,需要在被调函数里先赋值,才能进行访问
还有更复杂的引用传递引用类型,很少能用到,多余了,大多还是面试题会问.
C#中的函数(三)参数传递及返回值的更多相关文章
- JavaScript学习系列博客_17_JavaScript中的函数的参数、返回值
数的形参(形式参数) - 定义函数时,可以在()中定义一个或多个形参,形参之间使用英文逗号隔开:定义形参就相当于在函数内声明了对应的变量但是并不赋值,形参会在调用时才赋值. 函数的实参(实际参数) - ...
- perl 函数参数传递与返回值(一)
perl 函数参数传递与返回值(一) http://www.cnblogs.com/tobecrazy/archive/2013/06/11/3131887.html
- 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)
这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...
- 【Go入门教程5】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)
这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...
- 总结day7 ---- 函数的内容 ,初识,返回值,进阶(一)
内容大纲: 一: 函数识别 二: 函数的结构 三: 函数的返回值, 四: 函数的参数 五: 动态参数 六: 形参的顺序 七: 名称空间 八: 作用域 九: 加载顺序和取值顺序 十: 内置函数 十一: ...
- Python——变量的引用和函数的参数和返回值的传递方式
变量的引用 在python中,所有的变量都是指向地址,变量本身不保存数据,而是保存数据在内存中的地址.我们用下面的程序来理解: a = 10 print(id(a)) a = 11 print(id( ...
- c&c++函数的参数和返回值的传递终结版
c++函数的参数和返回值的传递方式有三种:值传递.指针传递和引用传递. 在这之前先看几个例子: 一, int a=10; int b=a; b+=10; 此时b是a的一个拷贝,改变b的值,a并不会受到 ...
- JAVA中执行JavaScript代码并获取返回值
JAVA中执行JavaScript代码并获取返回值 场景描述 实现思路 技术要点 代码实现 测试方法 运行结果 改进空间 场景描述 今天在CSDN上偶然看到一个帖子对于一段字符串 “var p=‘xx ...
- 探寻main函数的“标准”写法,以及获取main函数的参数、返回值
main函数表示法 很多同学在初学C或者C++时,都见过各种各样的main函数表示法: main(){/*...*/} void main(){/*...*/} int main(){/ ...
随机推荐
- LoadRunner名词解释
Transactions(用户事务分析):用户事务分析是站在用户角度进行的基础性能分析. 1.Transation Sunmmary(事务综述) 对事务进行综合分析是性能分析的第一步,通过分析测试时间 ...
- 使用IDEA+vue.js+easyUI的demo
最近,随便写了几个开发的小例子,自己总结下,留个纪念. 例子1:使用EasyUI做了一个简单界面,服务器和客户端在一起. @Controller @RequestMapping("/demo ...
- Oracle RAC 修改SPFILE路径 文件查看
在spfile场景下创建pfile: SQL> create pfile='/opt/oracle/init_pfile.ora'; 创建新spfile: SQL> create spfi ...
- Java一个简单的重试工具包
在接口调用中由于各种原因,可能会重置失败的任务,使用Guava-Retrying可以方便的实现重试功能. 首先,需要引用Guava-Retrying的包 <dependency> < ...
- 深入理解C语言 - 指针使用的常见错误
在C语言中,指针的重要性不言而喻,但在很多时候指针又被认为是一把双刃剑.一方面,指针是构建数据结构和操作内存的精确而高效的工具.另一方面,它们又很容易误用,从而产生不可预知的软件bug.下面总结一下指 ...
- fiddler抓包-1-安装与快速上手
前言 fiddler作为一个中间商协议代理,众所周知,有请求就会有响应,那没有响应呢?那就是哪个环节出现问题了.通过代理就可以查看到所有请求信息.与响应信息.举个例子,以前上学时有没有写过情书?或者给 ...
- unity工具开发
1.EditorWindow通过拖拽获取文件夹或者文件路径 #region 拖拽相关 Rect rect4 = EditorGUILayout.GetControlRect(); //将上面的框作为文 ...
- VSCode打字特效Power Mode插件
由于最近比较频繁使用VSCode这个软件写代码,然后里面有一个非常炫酷的打字特效插件,平时写代码的时候不会感觉太枯燥(其实就是装一下逼吧)! 安装很简单,但是容易忘,所以这里整理一下具体的部署步骤. ...
- Java : JavaWeb和Tomcat相关
部署:1.直接把项目移动到webapps文件夹下, 用文件夹名访问(如果ROOT文件夹可以直接访问)2.也可以把war包放到webapps文件夹下, tomcat自动解压,但是删除war包必须要停止t ...
- HTML+CSS学习笔记整理二
盒子模型CSS(重点) 边框border 边框通常使用连写border:1px(边框大小) solid(实线或其他) red(颜色) border-collapse:collapse (合并 ...