C# - 函数参数的传递
近段时间,有几个刚刚开始学习C#语言的爱好者问我:C#中的函数,其参数的传递,按值传递和按引用传递有什么区别。针对这一问题,我简单写了个示例程序,用以讲解,希望我没有把他们绕晕。因为,常听别人说起:“你不说我还明白,你一说,我就糊涂了”。
好,现在开始吧。
我们知道,在C#中,类型有值类型(例如int)和引用类型(例如string)之分,传递参数有按值传递和按引用传递之分。这样,简单的组合一下,我们可以得到以下几种传递方式:(1)按值传递值类型。(2)按值传递引用类型。(3)按引用传递值类型。(4)按引用传递引用类型。一般来说,除非使用特定的关键字(ref和out)否则参数是按值传递的。也就是说,会传递一个副本。传递副本的一个好处是,可以避免误操作而影响了原始值。原因是在被调用的函数体内,操作的是副本的值,而不是原始值。当然,传递副本也是有副作用的,最为突出的应该是由于复制而产生的性能损耗,这点在大型的值类型身上尤为突出。那么C#的编译器的默认行为为什么不是使用按引用传递参数呢?呵呵,其实我没仔细深入思考过这个问题。我猜测,是因为安全因素吧,就是怕函数误操作了原始值。这点应该和C#的编译器要求显示使用关键字(ref和out)差不多,都是为了清楚地表达使用的意图,以避免误操作。使用ref等关键字,暗示函数调用者知道,在函数体内,也许存在修改原始值的语句,会改变参数的值(或者叫状态)。
用个简单的示例演示一下。
示例代码如下所示:
- using System;
- namespace DonLiang
- {
- class Sample
- {
- 值类型测试函数#region 值类型测试函数
- public static void foo(int x)
- {
- x = 10;
- }
- //*
- public static void foo(ref int x)
- {
- x = 10;
- }
- //*/
- /**//*
- public static void foo(out int x)
- {
- x = 10;
- }
- //*/
- #endregion
- 辅助引用类型#region 辅助引用类型
- public class Point
- {
- private int m_x;
- private int m_y;
- public Point()
- {
- m_x = 0;
- m_y = 0;
- }
- public Point(int x, int y)
- {
- m_x = x;
- m_y = y;
- }
- public void Change(int x, int y)
- {
- m_x = x;
- m_y = y;
- }
- public override string ToString()
- {
- return string.Format("The Point is ({0},{1})", m_x.ToString(), m_y.ToString());
- }
- }
- #endregion
- 引用类型测试函数#region 引用类型测试函数
- public static void foo(Point p)
- {
- p.Change(10, 10);
- }
- public static void foo(ref Point p)
- {
- p.Change(100, 100);
- }
- public static void other(Point p)
- {
- Point tmp = new Point(13, 16);
- p = tmp;
- }
- public static void other(ref Point p)
- {
- Point tmp = new Point(138, 168);
- p = tmp;
- }
- #endregion
- Main#region Main
- static void Main(string[] args)
- {
- int n = 5;
- //call the foo(int x) method and check what happened.
- Console.WriteLine("before call foo(int x) the n = " + n.ToString());
- foo(n);
- Console.WriteLine("after call foo(int x) the n = " + n.ToString());
- Console.WriteLine("--------------------------------------------------------------");
- //call the foo(ref int x) method and check what happened.
- Console.WriteLine("before call foo(ref int x) the n = " + n.ToString());
- foo(ref n);
- //foo(out n);
- Console.WriteLine("after call foo(ref int x) the n = " + n.ToString());
- Console.WriteLine("--------------------------------------------------------------");
- Point p = new Point(5, 5);
- Point q = p;
- //call the foo(Point p) method and check what happened.
- Console.WriteLine("before call foo(Point p) the p = " + p.ToString());
- foo(p);
- Console.WriteLine("after call foo(Point p) the p = " + p.ToString());
- Console.WriteLine("q = " + q.ToString());
- Console.WriteLine("--------------------------------------------------------------");
- //call the foo(ref Point p) method and check what happened.
- Console.WriteLine("before call foo(ref Point p) the n = " + p.ToString());
- foo(ref p);
- Console.WriteLine("after call foo(ref Point p) the n = " + p.ToString());
- Console.WriteLine("q = " + q.ToString());
- Console.WriteLine("--------------------------------------------------------------");
- //call the other(Point p) method and check what happened.
- Console.WriteLine("before call other(Point p) the n = " + p.ToString());
- other(p);
- Console.WriteLine("after call other(Point p) the n = " + p.ToString());
- Console.WriteLine("q = " + q.ToString());
- Console.WriteLine("--------------------------------------------------------------");
- //call the other(ref Point p) method and check what happened.
- Console.WriteLine("before call other(ref Point p) the n = " + p.ToString());
- other(ref p);
- Console.WriteLine("after call other(ref Point p) the n = " + p.ToString());
- Console.WriteLine("q = " + q.ToString());
- Console.ReadLine();
- }
- #endregion
- }
- }
接下来,简单分析一下这个结果:
(1)按值传递值类型
初始值为5,调用函数的时候,弄了个副本给函数折腾,于是,从函数返回后,值还是5。嗯,本来应该弄个堆栈图出来的,可是,图是在太难画,因此,我偷懒,用VS2008的调试监视器看看:
(2)按引用传递值类型
初始值还是5,这次换了个传递参数的方式——按引用传递,这次可不是副本了,而是原始值(的地址),这就类似大牌的武打演员总不能老是使用替身一样,偶尔还是要亲自上阵的。既然是亲自上阵,那么,值被修改为10就不足为奇了。正如结果图所示,n=10了。
(3)按值传递引用类型 和 按引用传递引用类型
之所以把这两个放在一起讲,是因为,如结果图所示,两种传递方式,都成功修改了值——这两个函数都分别调用了一个辅助修改的函数Change,去修改内部状态,即m_x,m_y的值,从5到10。呃,竟然都可以成功修改原始值,那么,为什么会存在两种方式呢?它们有什么区别吗?分别用在什么地方?为了说明他们的区别,我特意写了两个名为other的函数,在函数内new一个Point对象,并使从参数传递过来的引用这个新生成的Point对象。值得提醒的是,这个引用其定义在函数体外。其运行如上图我用方框框起来那个。
可以很清楚地看到,通过值传递方式,可以改变其值,却不能改变其本身所引用的对象;而按引用传递方式可以。
顺便提一下,代码中,有一段注释掉的代码,使用out关键字的。当你尝试将其两者一起写着,然后,编译,C#编译器是会提示错误的(error CS0663: 'foo' cannot define overloaded methods that differ only on ref and out)。其原因是,C#编译器,对ref和out生成的IL代码,是相同的;而在CLR层面,是没有ref和out的区别的。C#中,ref和out的区别,主要是,谁负责初始化这个参数使之能用——ref形式是函数外初始化,而out是函数内初始化。
转自:http://www.cnblogs.com/DonLiang/archive/2008/02/16/1070717.html
C# - 函数参数的传递的更多相关文章
- 你好,C++(26)如何与函数内部进行数据交换?5.1.3 函数参数的传递
5.1.3 函数参数的传递 我们知道,函数是用来完成某个功能的相对独立的一段代码.函数在完成这个功能的时候,往往需要外部数据的支持,这时就需要在调用这个函数时向它传递所需要的数据它才能完成这个功能获 ...
- C语言函数参数的传递详解
一.三道考题 开讲之前,我先请你做三道题目.(嘿嘿,得先把你的头脑搞昏才行--唉呀,谁扔我鸡蛋?)考题一,程序代码如下:void Exchg1(int x, int y){ int tmp; ...
- python 函数参数的传递(参数带星号的说明) 元组传递 字典传递
python中函数参数的传递是通过赋值来传递的.函数参数的使用又有俩个方面值得注意:1.函数参数是如何定义的 2.在调用函数的过程中参数是如何被解析 先看第一个问题,在python中函数参数的定义主要 ...
- python 函数参数的传递(参数带星号的说明)
python中函数参数的传递是通过赋值来传递的.函数参数的使用又有俩个方面值得注意:1.函数参数是如何定义的 2.在调用函数的过程中参数是如何被解析 先看第一个问题,在python中函数参数的定义主要 ...
- python函数参数的传递、带星号参数的传递
python中函数参数的传递是通过赋值来传递的.函数参数的使用又有俩个方面值得注意:1.函数参数是如何定义的 2.在调用函数的过程中参数是如何被解析 先看第一个问题,在python中函数参数的定义主要 ...
- Summary: Java中函数参数的传递
函数调用参数传递类型(java)的用法介绍. java方法中传值和传引用的问题是个基本问题,但是也有很多人一时弄不清. (一)基本数据类型:传值,方法不会改变实参的值. public class Te ...
- go语言基础之结构体做函数参数 值传递和地址传递
1.结构体做函数参数值传递 示例: package main //必须有个main包 import "fmt" //定义一个结构体类型 type Student struct { ...
- python中的函数参数的传递
转载自: http://winterttr.me/2015/10/24/python-passing-arguments-as-value-or-reference/ 我想,这个标题或许是很多初学者的 ...
- C语言中将二维数组作为函数参数来传递
c语言中经常需要通过函数传递二维数组,有三种方法可以实现,如下: 方法一, 形参给出第二维的长度. 例如: #include <stdio.h> void func(int n, char ...
随机推荐
- IOS 多级列表展开控件
项目中实现了一个可以多级展开的列表控件.每次展开都是互斥的,就是说,展开一个cell 就会关闭其他展开的层. 可以呈现的效果如下图.第一个图片是应用中实现的效果.第二个是Demo中的效果.如果有新的需 ...
- HDU 5429 Geometric Progression
题意:给出一个大数数列,问是不是等比数列. 解法:拿java大数搞,注意全是0的情况也是Yes.我把公比用分数表示了,灰常麻烦,题解说只要判a[i - 1] * a[i + 1] == a[i] * ...
- 有效处理java异常的三个原则
Java中异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮.易于调试.异常之所以是一种强大的调试手段,在于其回答了以下三个问题: 什么出了错? 在哪出的错? 为什么出错? ...
- linux-制作linux启动U盘
1. 使用的制作工具 Ø 下载需要制作启动盘的linux的iso文件 Ø 制作启动盘的软件linux usb creater Ø U盘(大小差不多需要4G的空间) 软件可以的下载的地址:http:// ...
- 采集网页数据---Using Java
http://www.cnblogs.com/longwu/archive/2011/12/24/2300110.html 1).学习网页数据采集,首先必不可少的是学习java的正则表达式(Regex ...
- HDU5765 Bonds 最小割极
http://acm.hdu.edu.cn/showproblem.php?pid=5765 题意:无向连通图,问每条边在几个最小割极上 思路:用位压形式,表示边的关系.g[1<<i]=1 ...
- JAVA与数据库开发(JDBC-ODBC、SQL Server、MySQL)
1)配置数据库环境和驱动 2)设计数据库结构并创建数据库 3)对数据库进行增删改查操作...
- leetcode—Plus one
1.题目描述 Given a number represented as an array of digits, plus one to the number. 2.解法分析 不要被常规思路限制住 ...
- IO-同步,异步,阻塞,非阻塞,阅读摘要
http://www.cnblogs.com/Fly-Wind/p/io.html http://blog.csdn.net/historyasamirror/article/details/5778 ...
- 第二百四十天 how can I 坚持
在家待了一天,晚上出去买了个帽子,还有买了点排骨炖着吃了... 玩了好多局游戏. 想搞个直播,不知道能不能玩的起来. 水平太菜了,明天去小米之家玩玩. 睡觉.