CS2L7——ref和out

学习 ref 和 out 的原因

学习 ref​ 和 out​ 的原因,它们可以解决 在函数内部传入的内容 里面变了 外面没变 的问题

首先需要知道两个概念:形参和实参

  • 形式参数(形参):在函数定义中声明的参数
  • 实际参数(实参):在函数调用时传递给函数的具体值或表达式
1
2
3
4
5
6
7
8
9
10
11
12
static void ChangeValue(int value)
{
//这里的value是形参
value = 3;
}

static void Main(string[] args)
{
int a = 1;
//这里的a是实参
ChangeValue(a);
}

形参在函数定义中充当占位符,不占用内存空间,表示函数在执行时将接受的值
形参只在函数内部可见,其作用域局限在函数内部,
在函数调用时,这些形参会申请内存空间,它们申请的内存空间用来存储实参传递的数值
形参在函数执行结束后申请的内存空间就会被释放,形参释放内存空间不会影响实参

实参作为函数调用的一部分,提供了函数在执行时所需的数据。
在函数调用时,实参的数据就会拷贝到形参申请的内存空间内,得到数据的形参再参与到函数体的逻辑执行里

因此,在函数内部修改传入的形参变量,是不能改变外部的实参的,因为两个变量不是一个存储空间

1
2
3
4
5
6
7
8
9
10
11
12
static void ChangeValue(int value)
{
//这里的value是形参
value = 3;
}

static void Main(string[] args)
{
int a = 1;
ChangeValue(a);
Console.WriteLine(a);
}

输出:

1
1

可见不用 ref​ 和 out​ 并不能直接在改变函数内改变值类型的变量
原理是 值类型的变量在传入函数时,相当于参数 = 传入变量,
在栈空间里新开了房间并将数据拷贝给参数,因此只在函数里修改变量并不能影响原来传入的变量

注意!以上的情况都是出现在值类型变量的,因为值类型变量在函数传递时拷贝的是变量的值。
由于引用类型变量在函数传递时,拷贝的实际上是指向堆上对象的地址值,
因此,在函数内修改引用类型形参变量内部数据(注意,这里只说内部数据),是可以影响到外部的实参变量的。

1
2
3
4
5
6
7
8
9
10
11
static void ChangeArrayValue(int[] arr)
{
arr[0] = 99;
}

static void Main(string[] args)
{
int[] arr2 = { 1, 2, 3 };
ChangeArrayValue(arr2);
Console.WriteLine(arr2[0]);
}

输出:

1
99

可见在这里,函数内将引用类型的变量改变了
原理是,引用类型的变量传入函数时,将栈空间内的地址拷贝到参数里,指向了堆空间内同一个数据,
因此在函数内修改了参数,同样会修改堆空间内的数据,因此函数外的变量也会改变

不过,虽然引用类型变量在函数传递时,函数内部修改引用类型内部数据会影响到外部变量
但是形参和实参本质上是两块内存空间存储相同的地址
因此,如果形参引用的对象被修改了(例如为形参赋值一个新的实例化一个对象),这个改动是影响不到外部实参的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void ChangeArrayValue(int[] arr)
{
arr[0] = 99;
}

static void ChangeArray(int[] arr)
{
//相当于重新申明一个数组
arr = new int[] { 10, 20, 30 };
}

static void Main(string[] args)
{
int[] arr2 = { 1, 2, 3 };
ChangeArrayValue(arr2);
Console.WriteLine(arr2[0]);
ChangeArray(arr2);
Console.WriteLine(arr2[0]);
}

输出:

1
99

可见,这里外面的值并没有改变,
原理是在函数内参数在堆空间新开了一个数据区,此时参数的地址就与改变了,
与传入参数无关,因此修改参数数据不再能函数外的变量。

ref 和 out 的使用

它们是函数参数的修饰符,当传入的形参在函数内部修改时,或者引用类型参数在内部重新申明时,外部的实参值会发生变化

对于 ref​ 和 out​ 修饰的参数,外部调用函数传入值时,前面也需要加上 ref​ 和 out​,以 ref​ 为例:

1
2
3
4
5
6
7
8
9
10
11
static void ChangeValueRef(ref int value)
{
value = 3;
}

static void Main(string[] args)
{
int a = 1;
ChangeValueRef(ref a); //注意,这里的第一个参数也需要加上ref修饰
Console.WriteLine(a); //a出现了变化
}

输出:

1
3

可见,函数内部修改的形参的值,外部的实参也会变化

对于引用类型变量,效果也是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
static void ChangeArrayRef(ref int[] arr)
{
//相当于重新申明一个数组
arr = new int[] { 10, 20, 30 };
}

static void Main(string[] args)
{
int[] arr2 = { 1, 2, 3 };
ChangeArrayRef(ref arr2);
Console.WriteLine(arr2[0]);
}

输出:

1
10

可见即便是 new​(让内部形参指向新的对象),外部的实参变量依然会被改变

out​ 的作用和 ref​ 几乎是一致的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void ChangeValueOut(out int value)
{
value = 3;
}

static void ChangeArrayOut(out int[] arr)
{
//相当于重新申明一个数组
arr = new int[] { 10, 20, 30 };
}

static void Main(string[] args)
{
int a = 1;
int[] arr2 = new int[] { 1, 2, 3 };

ChangeValueOut(out a); //注意,一个参数前如果使用了out,则填入该参数的变量前必须加out
Console.WriteLine(a);
ChangeArrayOut(out arr2);
Console.WriteLine(arr2[0]);
//效果与ref差不多
//ref和out类似与将传入变量直接进入函数而不赋值给临时变量,因此在函数内修改变量对外部变量有效
}

输出:

1
2
3
10

ref​ 和 out​ 类似与将传入变量直接进入函数而不赋值给临时变量,因此在函数内修改变量对外部变量有效

ref 和 out 的区别

  • ref​ 传入的变量必须初始化,out​ 不需要
  • out​ 传入的变量必须在函数内部赋值,ref​ 不需要

总之 ref​ 和 out​ 的变量都会被赋值过

  • ref​ 传入的变量必须初始化 但是在内部可改可不改
  • out​ 传入的变量不用初始化 但是在内部必须修改该值或者赋值(因为 out​ 的参数默认没有初始化)