ref和out的区别

article/2025/9/28 7:33:18

ref 关键字 是作用是把一个变量的引用传入函数,和 C/C++ 中的指针几乎一样,就是传入了这个变量的栈指针。

out 关键字 的作用是当你需要返回多个变量的时候,可以把一个变量加上 out 关键字,并在函数内对它赋值,以实现返回多个变量。

几个简单的演示

上面说了 ref 和 out 的作用,非常简单,但是在具体使用的时候却遇到了很多麻烦,因为 C# 中本身就区分了引用类型和值类型。

我先举几个例子,来看看会出现哪些诡异的情况

代码段一:

static void Main(string[] args)
{int a;Test1(out a);//编译通过int b;Test2(ref b);//编译失败
}static void Test1(out int a)
{a = 1;
}
static void Test2(ref int b)
{b = 1;
}

这两个关键字看起来用法一样,为什么会有合格现象?

网上的答案很简单:out 关键字在传入前可以不赋值,ref 关键字在传入前一定要赋值。

这是什么解释?受之于鱼但并没有授之予渔!这到底是为什么呢?

想知道背后真正原理的呢,就继续看下去吧,后面我讲会讲到这里的区别。

代码二:

static void Main(string[] args)
{object a = new object(), b = new object(), c = new object();Test1(out a);Test2(ref b);Test3(c);//最终 a,b,c 分别是什么?//a,b = null//c 还是 object
}static void Test1(out object a)
{a = null;
}
static void Test2(ref object b)
{b = null;
}
static void Test3(object c)
{c = null;
}

新建三个 object,object是引用类型;三个函数,分别是 out,ref和普通调用;执行了一样的语句;最后的结果为什么是这样呢?

如果你只是从浅层次理解了 out 和 ref 的区别,这个问题你一定回答不上了。(我以前也不知道)

所以,这是为什么呢?继续往下看。

相信很多人晕了,我的目的达到了。(邪恶的笑~~)

那么,下面,我为大家从两个角度来分析一下。

对于值类型来说,加 out、加 ref 和什么都不加有什么共同点和区别?

对于引用类型来说,加 out、加 ref 和什么都不加有什么共同点和区别?

问题一:关于值类型

普通的传递值类型很简单了,传的只是一个值,没难度,平时都是这么用的,很好区分,所以这里就不惨和进去了。

接下来是 ref 和 out 的区别,为什么要了解区别呢?当然是为了了解怎么用它们,简单的来说就是需要了解:什么时候该用哪个。

个人总结有几个原则:

如果你是为了能多返回一个变量,那么就应该用 out:

用 out 关键字有几个好处:可以不关心函数外是否被赋值,并且如果在函数内没有赋值的话就会编译不通过。(提醒你一定要返回)

你可以把它当成是另一种形式的 return 来用,我们来做一个类比:

return 语句的特点:接收 return 的变量事先不需要赋值(当然如果赋值了也没关系),在函数内必须 return。

可以看到 out 关键字的作用、行为、特点 和 return 是完全一样的。因为它当初设计的目的就是为了解决这个问题的。

如果你想要像引用类型那样调用值类型,那你就可以 ref:

传入值类型的引用后,你可以用它,也可以不用它,你也可以重新修改它的各个属性,而函数外也可以随之改变。

我们来把 “传值类型的引用” 和 “传引用类型” 来做一个类比:

static void Main(string[] args)
{int a;Test1(ref a);//错误   1   使用了未赋值的局部变量“a”object b;Test2(b);//错误   2   使用了未赋值的局部变量“b”
}
static void Test1(ref int a) { }static void Test2(object b) { }

传入加了 ref 的值类型 和 传入一个引用类型 的作用、行为、特点都是类似的。

同样,他们同时要遵守一个原则:传入前必须赋值,这个是为什么呢?

如果赋值后,传入两个函数的分别是 int a 的指针 和 object b 的指针。

而不赋值的话,a 和 b 根本还不存在,那它们又怎么会有地址呢?

注意:如果你只写了 object a ,而在后面的代码中没有赋值,它并没有真正地分配内存。

我们可以看一下三个操作的 IL 代码:

private static void Main(string[] args)
{//IL_0000: nopobject a;//没做任何事//IL_0002: ldnull//IL_0003: stloc.1object b = null;//在栈中增加了一个指针,指向 null//IL_0004: newobj instance void [mscorlib]System.Object::.ctor()//IL_0009: stloc.2object c = new object();//在栈中增加了一个指针,指向新建的 object 对象
}

传入引用类型的目的是把一个已经存在的对象的地址传过去,而如果你只是进行了 object a 声明,并没做复制,这行代码跟没做任何事!

所以,除非你使用了 out 关键字,在不用关键字和用 ref 关键字的情况下,你都必须事先复制。 out 只是一种特殊的 return

总结:

现在你是否明白,当变量什么情况下该用什么关键字了吗?其实有时候 ref 和 out 都可以达到目的,你需要根据你的初衷,和它们的特点,来衡量一下到底使用哪个了!

另外,我们来看看两个同样的函数,用 out 和 ref 时候的 IL 代码

原函数:

private static void Test1(out int a)
{a = 1;
}
private static void Test2(ref int a)
{a = 1;
}

IL代码:

.method private hidebysig staticvoid Test1 ([out] int32& a) cil managed
{// Method begins at RVA 0x2053// Code size 5 (0x5).maxstack 8IL_0000: nopIL_0001: ldarg.0IL_0002: ldc.i4.1IL_0003: stind.i4IL_0004: ret
} // end of method Program::Test1.method private hidebysig staticvoid Test2 (int32& a) cil managed
{// Method begins at RVA 0x2059// Code size 5 (0x5).maxstack 8IL_0000: nopIL_0001: ldarg.0IL_0002: ldc.i4.1IL_0003: stind.i4IL_0004: ret
} // end of method Program::Test2

发现了吗? 它们在函数内部完全是一样的!因为他们的原理都是传入了这个变量的引用。只是 out 关键字前面出现了一个标记 [out]

它们在原理上的区别主要在于编译器对它们进行了一定的限制。

最上面“代码段一”中的问题你现在明白了吗?

问题二:关于引用类型

对于值类型来说,最难区别的是 ref 和 out,而对于引用类型来说就不同了。

首先,引用类型传的是引用,加了 ref 以后也是引用,所以它们是一样的?暂时我们就这么认为吧~ 我们暂时认为它们是一样的,并统称为:传引用。

所以,对于引用类型来说,out 和 传引用 的区别跟对于值类型传 ref 和 out 的区别类似,具体适用场景也和值类型类似,所以就不多加阐述了。

虽然我们说直接传和加 ref 都可以统称为传引用,但是它们还是有区别的!而且是一个很隐蔽的区别。

我们再来看一下最上面的代码段二:

static void Main(string[] args)
{object a = new object(), b = new object(), c = new object();Test1(out a);Test2(ref b);Test3(c);//最终 a,b,c 分别是什么?//a,b = null//c 还是 object
}static void Test1(out object a)
{a = null;
}
static void Test2(ref object b)
{b = null;
}
static void Test3(object c)
{c = null;
}

out 关键字就相当于 return ,所以内部赋值为 null ,就相当于 return 了 null

可是,为什么引用类型还要加 ref 呢?它本身部已经是引用了吗?为什么加了以后就会有天大的区别呢?!

用一句话概括就是:不加 ref 的引用是堆引用,而加了 ref 后就是栈引用! @_@ 好搞啊。。什么跟什么?让我们一步步说清楚吧!

正常的传递引用类型:

 

加了 ref 的传递引用类型:

 

这两张图对于上面那句话的解释很清楚了吧?

如果直接传,只是分配了一个新的栈空间,存放着同一个地址,指向同一个对象。

内外指向的都是同一个对象,所以对 对象内部的操作 都是同步的。

但是,如果把函数内部的 obj2 赋值了 null,只是修改了 obj2 的引用,而 obj1 依然是引用了原来的对象。

所以上面的例子中,外部的变量并没有收到影响。

同样,如果内部的对象作了 obj2 = new object() 操作以后,也不会对外部的对象产生任何影响!

而加了 ref 后,传入的不是 object 地址,传入的是 object 地址的地址!

所以,当你对 obj2 赋 null 值的时候,其实是修改了 obj1 的地址,而自身的地址没变,还是引用到了 obj1

虽然在函数内部的语句是一样的,其实内部机制完全不同。我们可以看一下IL代码,一看就知道了!

.method private hidebysig staticvoid Test1 (object a) cil managed
{// Method begins at RVA 0x2053// Code size 5 (0x5).maxstack 8IL_0000: nopIL_0001: ldnullIL_0002: starg.s aIL_0004: ret
} // end of method Program::Test1.method private hidebysig staticvoid Test2 (object& a) cil managed
{// Method begins at RVA 0x2059// Code size 5 (0x5).maxstack 8IL_0000: nopIL_0001: ldarg.0//多了这行代码IL_0002: ldnullIL_0003: stind.refIL_0004: ret
} // end of method Program::Test2

上面是直接传入,并赋 null 值的

下面是加 ref 的

我们可以发现仅仅是多了一行代码:IL_0001: ldarg.0

其实,这样代码的作用就是讲参数0加载到堆栈上,也就是先根据引用,找到了外部的变量,然后再根据外部的变量,找到了最终的对象!

那现在你知道什么时候该加 ref,什么时候不用加 ref 了吗?

再看了一个例子:

private static void Test1(List<int> list)
{list.Clear();
}
private static void Test2(ref List<int> list)
{list = new List<int>();
}

同样是清空一个 List,如果没加 ref ,只能用 clear。

而加了 ref 后可以直接 new 一个新的~

如果你没加 ref 就直接 new 一个新的了,抱歉,外部根本不知道有这个东西,你们操作的将不是同一个 List

所以,你一定要了解这点,并注意一下几件事:

1、一般情况下不要用 ref

2、如果你没加 ref,千万别直接给它赋值,因为外面会接收不到…

现在你全部明白了吗?_


http://chatgpt.dhexx.cn/article/Qp9kig84.shtml

相关文章

vue ref介绍

基本用法 ref 有三种用法&#xff1a; 1、ref 加在普通的元素上&#xff0c;用this.ref.name 获取到的是dom元素 2、ref 加在子组件上&#xff0c;用this.ref.name 获取到的是组件实例&#xff0c;可以使用组件的所有方法。 3、如何利用 v-for 和 ref 获取一组数组或者dom …

vue3中ref的使用

一.定义一个数据的响应式 <template><h2>{{count}}</h2><hr><button click"update">更新</button> </template><script> import {ref } from vue export default {setup () {// 定义响应式数据 ref对象const count …

ref和reactive

一&#xff0c;前言 1.ref和reactive是vue3基于组合式api模式下&#xff0c;在setup中用于声明的具有响应式的数据的方法。 二&#xff0c;ref 1.ref通常用于声明基础类型响应式数据。 import { ref } from vue const age ref(10) //声明响应式数据2.ref返回的是被包装过的…

Vue中ref的用法

1、ref 加在普通的元素上&#xff0c;用this.$refs.&#xff08;ref值&#xff09; 获取到的是dom元素 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><div id"app"…

c#引用(ref)的用法和应用实例

无论是浅拷贝与深拷贝&#xff0c;C#都将源对象中的所有字段复制到新的对象中。不过&#xff0c;对于值类型字段&#xff0c;引用类型字段以及字符串类型字段的处理&#xff0c;两种拷贝方式存在一定的区别&#xff08;见下表&#xff09;。 1. 一般对C#中传值调用和传引用调用…

什么时候用ref,怎么用ref

文章目录 用refref是一个字符串ref是一个内联函数ref是一个回调函数ref通过调用React.createRef()生成 不用ref 用ref “打在我身&#xff0c;疼在她心”时&#xff0c;用ref。比如&#xff0c;点击事件绑定在某个标签上&#xff0c;事件处理函数中却要访问另一个完全不相关的…

[Vue]ref属性

前言 系列文章目录&#xff1a; [Vue]目录 老师的课件笔记&#xff0c;不含视频 https://www.aliyundrive.com/s/B8sDe5u56BU 笔记在线版&#xff1a; https://note.youdao.com/s/5vP46EPC 视频&#xff1a;尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 文章目录 前言1. ref…

什么是 ref 引用

1. 什么是 ref 引用 ref 用来辅助开发者在 不依赖于 jQuery 的情况下 &#xff0c;获取 DOM 元素或组件的引用。 每个 vue 的组件实例上&#xff0c;都包含一个 $refs 对象 &#xff0c;里面存储着对应的 DOM 元素或组件的引用。默认情况下&#xff0c; 组件的 $refs 指向一…

关于 Ref 你需要知道的知识点

Intro 在 React 项目中&#xff0c;有很多场景需要用到 Ref。例如使用 ref 属性获取 DOM 节点&#xff0c;获取 ClassComponent 对象实例&#xff1b;用 useRef Hook 创建一个 Ref 对象&#xff0c;以便解决像 setInterval 获取不到最新的 state 的问题&#xff1b;你也可以调用…

学习Vue3 第六章(认识Ref全家桶)

视频教程小满Vue3&#xff08;第六章 Ref 全家桶 & 源码解析&#xff09;_哔哩哔哩_bilibili ref 接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property&#xff0c;指向该内部值。 案例 我们这样操作是无法改变message 的值 应为mess…

Vue中ref和$refs的介绍及使用

在JavaScript中需要通过document.querySelector("#demo")来获取dom节点&#xff0c;然后再获取这个节点的值。在Vue中&#xff0c;我们不用获取dom节点&#xff0c;元素绑定ref之后&#xff0c;直接通过this.$refs即可调用&#xff0c;这样可以减少获取dom节点的消耗…

C++实现身份证号码过滤与排序

1.描述 警察办案里检索到一批(n个)身份证号码&#xff0c;希望按出生日期对它们进行从大到小排序&#xff0c;如果有相同日期&#xff0c;则按身份证号码大小进行排序&#xff0c;如果是错误的身份证号&#xff0c;则从排序列表中删除(仅需判断前两位省级地区编码是否在下面的…

基于MATLAB的身份证号码识别系统

一、课题目标 本文主要介绍了一种采用基于matlab数字图像处理的图像识别技术&#xff0c;对身份证原始图像中的序列号标示进行图像识别的方法。该系统通过图像预处理、图像定位、图像校正并最终输出结果。在系统调试阶段&#xff0c;根据遇到的错误即时对原系统进行调整&#x…

python爬虫--根据身份证号码获取户籍地、出生年月等信息

一、背景 工作中偶尔会遇到这样的情况&#xff0c;给你一堆客户身份证号码&#xff0c;然后要你把对应的性别、生日、户籍地等信息弄出来。 最常用的方法就是用excel表套公式&#xff0c;这个方式如果用来取性别、生日这些信息的话问题不大&#xff0c;毕竟这些规则还好梳理&…

BP算法的身份证号码识别

摘 要:基于反向传播算法的多层前馈网络(简称BP神经网络)在图像处理方面应用较为广泛。目前&#xff0c;身份证号码识别技术 在图像识别领域迅猛发展&#xff0c;为提高识别身份证号码的速度及准确性&#xff0c;本文研究一种基于BP神经网络的身份证号码识别系统。首 先&#xf…

Matlab实现身份证号码快速识别

用Matlab 实现身份证号码快速识别 摘 要&#xff1a; 探讨身份证号码的快速识别。 首先从身份证图像中获取 0&#xff5e;9 共 10 个号码数字的样本图像&#xff0c; 从中提取其空间分布特征和结构特征&#xff1b; 再从待识别的身份证图像中提取各号码数字的空间分布特征和结构…

身份证号码的编码规则及校验

前言 本文内容适用于二代身份证&#xff0c;如无特殊说明&#xff0c;本文中所说的身份证皆指二代身份证。 笔者目前使用的jdk版本是1.6.0_29&#xff0c;Eclipse版本是Juno Release&#xff0c;Build id 20120614-1722。如无特殊说明&#xff0c;本文所有的Java代码都是基于…

基于Python实现身份证号码验证

基于Python实现身份证号码验证 摘要: 该设计主要使用python语言来实现身份证号验证程序。它运用 tkinter模块生成GUI界面。当用户输入身份证号&#xff0c;按下检查按钮&#xff0c;即可判断身份证号是否正确。该程序简洁&#xff0c;灵活&#xff0c;执行效率高。 关键词: 身…

java验证身份证号码的合格性

中国居民身份证校验码算法步骤如下: 将身份证号码前面的17位数分别乘以不同的系数。从第一位到第十七位的系数分别为: 7&#xff0d;9&#xff0d;10&#xff0d;5&#xff0d;8&#xff0d;4&#xff0d;2&#xff0d;1&#xff0d;6&#xff0d;3&#xff0d;7&#xff0d;9&…

Python+Opencv身份证号码区域提取及识别

前端时间智能信息处理实训&#xff0c;我选择的课题为身份证号码识别&#xff0c;对中华人民共和国公民身份证进行识别&#xff0c;提取并识别其中的身份证号码&#xff0c;将身份证号码识别为字符串的形式输出。现在实训结束了将代码发布出来供大家参考&#xff0c;识别的方式…