C#值传递、引用传递、输出传递详解
- 1、值传递
- 2、引用传递
- 3、输出传递
- 4、ref 和 out
导读:
1,值传递时,为什么被调用的方法中的形参值的改变不会影响到相应的实参?
答:因为按值传递时,系统首先为被调用的方法的形参分配内存空间,然后把实参中的值按位置一一对应“复制”给形参。形参中存储的值只是一份实参的拷贝,因此被调用方法中形参值的任何改变都不会影响到相应的形参。
2,值传递和引用传递有什么不同,什么是值参数,它以什么方式传递?
答:值传递时,系统首先为被调用方法的形参分配内存空间,并将实参的值按位置一一对应地复制给形参,此后,被调用方法中形参值得任何改变都不会影响到相应的实参; 而引用传递时,系统不是将实参本身的值复制后传递给形参,而是将其引用值(即地址值)传递给形参,因此,形参所引用的该地址上的变量与传递的实参相同,方法体内相应形参值得任何改变都将影响到作为引用传递的实参。
3,什么是形参,什么是实参?
答:形参:在定义函数中指定的参数就是形参,在未出现函数调用时,他们并不占内存中的存储单元,只有在发生函数调用时,函数中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。
实参:实参可以是常量、变量和表达式,但要求有确定的值。在调用时将实参的值赋给形参。在内存中,实参单元和形参单元是不同的单元。在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保留原值。
理解:
实参就是送进去方法中的东西~~行参就是把送进来的东西在方法中进行拷贝加工,加工完后方法就返回一个东西--返回值。
参数的传递分为:1.值方式参数传递,2.引用方式参数传递。
我们先来介绍一下形式参数(形参)和实际参数(实参)这两个概念:
形式参数:在定义函数阶段参数列表中定义的参数称之为形式参数,简称形参,可以将它看作变量的名称,它没有具体的值,只是用来接收函数调用时传递过来的数据;
实际参数:在函数被调用时传递给函数的参数称之为实际参数,简称实参,可以将它看作变量的值,用来为形参赋值。
参数可以通过三种方式传递给函数,如下表所示:
方式 | 描述 |
---|---|
值传递 | 值传递会复制参数的实际值并赋值给函数的形式参数,实参和形参使用的是两个不同内存位置中的值,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全 |
引用传递 | 引用传递会复制参数的内存位置并传递给形式参数,当形参的值发生改变时,同时也会改变实参的值 |
输出传递 | 输出传递可以一次返回多个值 |
1、值传递
在 C# 中,值传递是将参数传递给函数的默认方式,值传递的本质就是将实参的副本(将实参的值复制一份)传递给函数的形参。当调用函数时,将使用实参为每个形参赋值,并为每个形参创建一个新的存储位置,由于形参和实参指向不同的内存位置,所以无论是修改实参的值还是修改形参的值都不会对彼此造成影响。
下面通过示例来演示一下如何使用值传递的方式将参数传递给函数:
using System;
namespace c.biancheng.net
{
class Demo
{
static void Main(string[] args){
int val = 10;
Demo Obj = new Demo();
Console.WriteLine("调用函数之前:{0}", val);
Obj.Func(val);
Console.WriteLine("调用函数之后:{0}", val);
}
public void Func(int val){
val *= val;
Console.WriteLine("函数内部的值:{0}", val);
}
}
}
结果:
调用函数之前:10
函数内部的值:100
调用函数之后:10
通过运行结果可以看出,尽管我们在函数内部对形参 val 的值进行的修改,但是并不会影响函数外面实参 val 的值。
2、引用传递
引用传递是对变量内存位置的引用。与值传递不同,使用引用传递的形式传递参数时,并不会为形参创建新的内存地址,而是与实参共同指向相同的内存地址。正因为如此,当修改形参的值时,实参的值也会被修改。
在 C# 中,需要使用 ref 关键字来使用引用传递,下面通过示例来演示一下:
using System;
namespace c.biancheng.net
{
class Demo
{
static void Main(string[] args){
int val = 10;
Demo Obj = new Demo();
Console.WriteLine("调用函数之前:{0}", val);
Obj.Func(ref val);
Console.WriteLine("调用函数之后:{0}", val);
}
public void Func(ref int val){
val *= val;
Console.WriteLine("函数内部的值:{0}", val);
}
}
}
结果:
调用函数之前:10
函数内部的值:100
调用函数之后:100
总结:
1)按值传递(不能改变实参)
实参是变量,表达式等数值。
函数调用的时候,实参和形参存在于内存中2快不同的区域,实参先自己复制一份拷贝,再把拷贝传给形参。由于是传递的是拷贝,所以实参不会受形参的影响,实参值不会被改变。
2)按地址传递(可以改变实参)
实参是指针/引用。
函数调用的时候,指针传给你,形参和实参指针都一样,对形参的任何操作就等于对实参的操做。实参的值就可以被改变。
3、输出传递
使用 return 语句可以从函数中返回一个值,但是使用输出传递则可以从函数中一次性返回多个值。输出传递与引用传递相似,不同之处在于输出传递是将数据从函数中传输出来而不是传输到函数中。
在 C# 中,需要使用 out 关键字来使用输出传递,下面通过示例来演示一下:
using System;
namespace c.biancheng.net
{
class Demo
{
static void Main(string[] args){
int val = 33;
Demo Obj = new Demo();
Console.WriteLine("调用函数之前 val 的值:{0}", val);
Obj.getValue(out val);
Console.WriteLine("调用函数之后 val 的值:{0}", val);
}
public void getValue(out int x){
int temp = 11;
x = temp;
x *= x;
}
}
}
结果:
调用函数之前 val 的值:33
调用函数之后 val 的值:121
在使用输出传递时,也可以不为实参赋值,如下例所示:
using System;
namespace c.biancheng.net
{
class Demo
{
static void Main(string[] args){
int a, b;
Demo Obj = new Demo();
Obj.getValues(out a, out b);
Console.WriteLine("调用函数之后 a 的值:{0}", a);
Console.WriteLine("调用函数之后 b 的值:{0}", b);
}
public void getValues(out int x, out int y){
Console.WriteLine("请输入第一个值: ");
x = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("请输入第二个值: ");
y = Convert.ToInt32(Console.ReadLine());
}
}
}
运行结果如下:
请输入第一个值:
123
请输入第二个值:
321
调用函数之后 a 的值:123
调用函数之后 b 的值:321
4、ref 和 out
在C#中通过使用方法来获取返回值时,通常只能得到一个返回值。因此,当一个方法需要返回多个值的时候,就需要用到ref和out,那么这两个方法区别在哪儿呢?
MSDN:
ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。
out 关键字会导致参数通过引用来传递。这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化。若要使用 out 参数,方法定义和调用方法都必须显式使用 out 关键字。
案例:
定义一个方法,求一个整数数组中的最大值,最小值,和,平均数。如果是一个方法只能有一个返回值,那只能每一个都得定义一个方法来实现,不过有了ref和out这实现起来就方便多了。
ref:
static int GetIntResult(int[] arry, ref float avg, ref int max, ref int min)
{
int sum = 0;
max = arry[0];
min = arry[0];
for (int i = 0; i < arry.Length; i++)
{
sum += arry[i];
if (max < arry[i])
{
max = arry[i];
}
if (min > arry[i])
{
min = arry[i];
}
}
avg = sum / arry.Length;
return sum;
}
然后在控制台中试着调用该方法:
static void Main(string[] args)
{
int[] arr = { 1,2,3,4,5,6,7,8,9};
float avg;
int max;
int min;
int sum = GetIntResult(arr, ref avg, ref max, ref min);
}
此时编译器就会提示画红线,错误:使用了未赋值的avg,max,min
static void Main(string[] args)
{
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
float avg = 0;
int max = 0;
int min = 0;
int sum = GetIntResult(arr, ref avg, ref max, ref min);
Console.WriteLine("和:{0}\t平均值:{1}\t最大值:{2}\t最小值:{3}", sum, avg, max, min);
Console.Read();
}
运行结果:
总结:
ref这个关键字告诉c#编译器被传递的参数值指向与调用代码中变量相同的内存。这样,如果被调用的方法修改了这些值然后返回的话,调用代码的变量也就被修改了。ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中(avg,max,min的初始值为0,调用方法后值改变)。若要使用
ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。
out:
换成out之后,上面的方法不再适用,报错,错误 : 控制离开当前方法之前必须对 out 参数“min”和"max"赋值。你会发现这里max和min在循环外并未初始化。所以才会出错。
修改后代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wolfy.RefAndOut
{
class Program
{
static void Main(string[] args)
{
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
float avg;//在使用out关键字时,不需要在此处初始化,初始化也不会影响到方法内部的值,所以你初始化没用
int max;
int min;
int sum = GetIntResult(arr, out avg, out max, out min);
Console.WriteLine("和:{0}\t平均值:{1}\t最大值:{2}\t最小值:{3}", sum, avg, max, min);
Console.Read();
}
static int GetIntResult(int[] arry, out float avg, out int max, out int min)
{
int sum = 0;
max = arry[0];
min = arry[0];//使用out关键字时,必须在离开方法前对out关键字修饰的参数初始化
for (int i = 0; i < arry.Length; i++)
{
sum += arry[i];
if (max < arry[i])
{
max = arry[i];
}
if (min > arry[i])
{
min = arry[i];
}
}
avg = sum / arry.Length;
return sum;
}
}
}
结果和上面一样。
总结:
out 关键字会导致参数通过引用来传递。这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化。若要使用 out 参数,方法定义和调用方法都必须显式使用 out 关键字。结论:
关键字“ref“和”out”之间的唯一区别就是关键字”out“不要求调用代码初始化要传递的参数值。