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(){/ ...
随机推荐
- CentOS7 SUDO 笔记--没配置sudoer,为什么有的账号能用sudo命令,有的不能用
原来: 一.安装linux 创建的用户(管理员打钩)默认在 wheel组里. 1. 使用 cat /etc/passwd 查看用户所在组.中间那个数字是 groupid 不太好看 2.使用 cat / ...
- lock、tryLock和lockInterruptibly的差別
lock():若lock被thread A取得,thread B会进入block状态,直到取得lock:tryLock():若当下不能取得lock,thread就会放弃,可以设置一个超时时间参数,等待 ...
- [转帖]tcpdump详细教程
tcpdump详细教程 https://www.jianshu.com/p/d9162722f189 tcpdump tcpdump - dump traffic on a network tcpdu ...
- Scala Types 2
存在类型 形式: forSome { type ... } 或 forSome { val ... } 主要为了兼容 Java 的通配符 示例 Array[_] // 等价于 Array[T] for ...
- 2019-11-29-C#-反射调用私有事件
原文:2019-11-29-C#-反射调用私有事件 title author date CreateTime categories C# 反射调用私有事件 lindexi 2019-11-29 08: ...
- C++初探
//string1.cpp #include <iostream> int main() { using namespace std; ]={'d','o','g'}//这是char数组, ...
- 排序算法Java代码实现(四)—— 归并排序
本篇内容: 归并排序 归并排序 算法思想: 将两个或两个以上的有序表合并成一个新的有序表, 即把待排序序列分成若干个子序列,每个子序列是有序的,然后在把有序子序列合并为整体有序序列. 此算法分为两步: ...
- 排序算法Java代码实现(三)—— 插入排序 和 希尔排序
因为希尔排序的核心思想是插入排序,所以本篇将两篇排序一起记录 本篇内容: 插入排序 希尔排序 (一)插入排序 算法思想: 把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只有一个元素,无序 ...
- MVC+Ninject+三层架构+代码生成 -- 总结(四、數據層)
1.數據層使用了SqlSugar 庫類 . 數據層使用了SqlSugar 庫類 ,有興趣的 可以學習 http://www.codeisbug.com/Doc/8/1133,個人覺得比EF 簡單,容 ...
- Java内功心法,创建型设计模式包括哪些
1. 单例(Singleton) Intent 确保一个类只有一个实例,并提供该实例的全局访问点. Class Diagram 使用一个私有构造函数.一个私有静态变量以及一个公有静态函数来实现. 私有 ...