探究C#中的ref和out按引用传递和按值传递以及指针类型的地址
按引用传递参数
在微软官网上给出的介绍是
ref 关键字指示按引用传递的值。
它用在四种不同的上下文中:
在方法签名和方法调用中,按引用将参数传递给方法。 有关详细信息,请参阅按引用传递参数。
在方法签名中,按引用将值返回给调用方。 有关详细信息,请参阅引用返回值。
在成员正文中,指示引用返回值是否作为调用方欲修改的引用被存储在本地,或在一般情况下,局部变量按引用访问另一个值。 有关详细信息,请参阅 Ref 局部变量。
在 struct 声明中声明 ref struct 或 ref readonly struct。 有关详细信息,请参阅 ref 结构类型。
按引用传递参数
在方法的参数列表中使用 ref 关键字时,它指示参数按引用传递,而非按值传递。 ref 关键字让形参成为实参的别名,这必须是变量。 换而言之,对形参执行的任何操作都是对实参执行的。 例如,如果调用方传递本地变量表达式或数组元素访问表达式,所调用方法会替换 ref 参数引用的对象,然后,当该方法返回时,调用方的本地变量或数组元素将开始引用新对象。
参考地址:ref(C# 参考)
按引用传递参数
在方法的参数列表中使用 ref 关键字时,它指示参数按引用传递,而非按值传递。 ref 关键字让形参成为实参的别名,这必须是变量。 换而言之,对形参执行的任何操作都是对实参执行的。 例如,如果调用方传递本地变量表达式或数组元素访问表达式,所调用方法会替换 ref 参数引用的对象,然后,当该方法返回时,调用方的本地变量或数组元素将开始引用新对象。
参考地址:按引用传递参数
传递值类型参数(C# 编程指南)
一个值类型变量包含它直接与引用类型变量相对的数据,其中包含对其数据的引用。 按值将值-类型变量传递给方法,意味着将变量副本传递给该方法。 在方法内发生的对该实参进行的任何更改不会影响存储在形参变量中的原始数据。 若要使用已调用的方法来更改参数值,必须使用 ref 或 out 关键字通过引用传递它。 还可以使用 in 关键字来按引用传递值参数,以避免复制并同时保证不更改值。
参考地址:传递值类型参数
in 参数修饰符
C# 指南
in 关键字会导致按引用传递参数,但确保未修改参数。
测试
由于C#不推荐指针类型,为安全起见一般不用,实用指针类型的时候需要添加unsafe关键字,编译器开启unsafe模式,在方法里使用 int* p = &a; 的时候会提示错误 'you can only take the address of an unfixed expression inside of a fixed",翻译过来是“只能在fixed中获取未固定表达式的地址”,需要添加fixed关键字,代码如下:
//报错
static unsafe void test()
{
int* iadd = &i;
Console.WriteLine(“静态定义i的地址:” + (int) iadd); //输出i的地址
Add(i); //按值传递
Add(ref i); //按引用传递
Addout(out i); //按引用传递
Add(iadd); //取地址i
}
//修改之后的代码
static unsafe void test()
{
fixed (int* iadd = &i)//添加fixed
{
Console.WriteLine("静态定义i的地址:" + (int) iadd); //输出i的地址
Add(i); //按值传递
Add(ref i); //按引用传递
Addout(out i); //按引用传递
Add(iadd); //取地址i
}
}
/*
*********************************************************************
*Copyright (C) 2019 Unity1903
*File Name: RefOutTest.cs
*Author: lvzhijie
*CreateTime: 2019年8月2日 10:31:02
*Describe:
* 按值传递:
* 新创建一个空间,存放传递的值;
* 按引用传递,引用(即传递变量的地址):
* 不创建新空间,让形参成为实参的别名,对形参执行的任何操作都是对实参执行的;
* 按地址传递,传递一个指针类型:
* 不创建新空间,直接在该地址上进行操作,但是C#中不建议使用指针,指针不安全,需要在方法里添加unsafe,编译器开启unsafe模式;
**********************************************************************
*/
using System;
using System.Runtime.InteropServices;
namespace ConsoleApplication2
{
public class RefOutTest
{
static int i = 1;
public static void Main(string[] args)
{
test();
}
static unsafe void test()
{
fixed (int* iadd = &i)
{
Console.WriteLine("静态定义i的地址:" + (int) iadd); //输出i的地址
Add(i); //按值传递
Add(ref i); //按引用传递,使用ref的时候需要在使用之前给i赋值,不然报错
Addout(out i); //按引用传递,使用out不需要提前给i赋值,但是方法里必须对i赋值之后再使用
Add(iadd); //取地址i
}
}
//按值传递
static unsafe void Add(int a)
{
int* p = &a;
Console.WriteLine("add int 的i地址:" + (int) p);
}
//按引用传递
static unsafe void Add(ref int a)
{
fixed (int* p = &a)
{
Console.WriteLine("add ref int 的i的地址:" + (int) p);
}
}
//按引用传递
static unsafe void Addout(out int a)
{
a = 1;
fixed (int* p = &a)
{
Console.WriteLine("add out int 的i的地址:" + (int) p);
}
}
//用指针传递
static unsafe void Add(int* p)
{
Console.WriteLine("add int* 的i的地址" + (int) p);
}
}
}
运行结果
静态定义i的地址:16729260
add int 的i地址:11529624
add ref int 的i的地址:16729260
add out int 的i的地址:16729260
add int* 的i的地址16729260
##
总结
- 按值传递:
-
新创建一个空间,存放传递的值;
- 按引用传递,引用(即传递变量的地址):
-
不创建新空间,让形参成为实参的别名,对形参执行的任何操作都是对实参执行的;
- 按地址传递,传递一个指针类型:
-
不创建新空间,直接在该地址上进行操作,但是C#中不建议使用指针,指针不安全,需要在方法里添加unsafe,编译器开启unsafe模式;