当前位置: 首页 > news >正文

C#|.net core 基础 - 值传递 vs 引用传递

不知道你在开发过程中有没有遇到过这样的困惑:这个变量怎么值被改?这个值怎么没变?

今天就来和大家分享可能导致这个问题的根本原因值传递 vs 引用传递。

在此之前我们先回顾两组基本概念:

值类型** vs 引用类型**

**值类型:**直接存储数据,数据存储在栈上;

**引用类型:**存储数据对象的引用,数据实际存储在堆上。

形参 vs 实参

**形参:**即形式参数,表示调用方法时,方法需要你传递的值。方法声明定义了其形参。也就是说在定义方法时,紧跟在方法名后面括号中的参数列表就是形参。

**实参:**即实际参数,表示调用方法时,你传递给方法形参的值。调用代码在调用过程时提供实参。也就是说在调用方法时,紧跟在方法名后面括号中的参数列表就是实参。

再来回顾一下值类型和引用类型在内存中是怎么存储的呢?

对于值类型变量的值直接存储在栈中,如下图的int a=10,10就直接存在栈空间中,而其栈空间对应的内存地址为0x66666668;对于引用类型变量本身存储的是实例对象的引用,即实例对象在堆中的实际内存地址,因此引用类型变量是存储其实例对象的引用于栈上,如下图中变量Test a在栈中实际存储的是实例对象Test a在堆中的内存地址0x88888880,而栈空间对应的内存地址为0x66666668。

在这里插入图片描述

栈也是有内存地址的,这一点很重要,无论栈空间上存储的是值还是引用地址,这个栈空间本身也有自己对应的内存地址。

什么是值传递?什么是引用传递?

值传递:如果变量按值传递给方法,则会把变量的副本传递给方法。对于值类型则把变量的副本传递给方法,对于引用类型则把变量的引用的副本传递给方法。因此被调用方法参数会创建一个新的内存地址用于接收存储变量,因此在方法内部对变量修改并不会影响原来的值。

引用传递:如果变量按引用传递给方法,则会把变量的引用传递给方法,对于值类型则把变量的栈空间地址传递给方法,对于引用类型则把变量的引用的栈空间地址传递给方法。因此被调用方法参数不会创建一个新的内存地址用于接收存储变量,意味着形参与实参共同指向相同的内存地址,因此在方法内部修对变量修改会影响原来的值。

上面的描述可能有点拗口,下面我们在基于值类型、引用类型、值传递、引用传递各种组合进行一个详细说明。

01值类型按值传递

当值类型按值传递时,调用者会把值类型变量的副本传递给方法,因此被调用方法参数会创建一个新的内存地址用于接收存储变量,因此当在方法内部对参数进行修改时并不会影响调用者调用处的值类型变量。

传递值类型变量的副本就是相当于在栈上,又复制了一个同样的值,而且内存地址还不一样,所以互不影响。如下图把a赋值给b,则b直接新开辟了一个栈空间,虽然a和b都是10,但是它们在不同的地址空间中,因此如果他们各自被修改了,也互不影响。

在这里插入图片描述

下面我们写个例子演示一下,这个例子就是定义个变量a并赋值,然后调用一个方法此方法内对传进来的参数a进行加1,具体代码如下:

public static void ValueByValueRun()
{var a = 10;Console.WriteLine($"调用者-调用方法前 a 值:{a}");ChangeValueByValue(a);Console.WriteLine($"调用者-调用方法后 a 值:{a}");
}
public static void ChangeValueByValue(int a)
{Console.WriteLine($"    被调用方法-接收到 a 值:{a}");a = a + 1;Console.WriteLine($"    被调用方法-修改后 a 值:{a}");
}

运行结果如下:

在这里插入图片描述

通过代码执行结果可以发现,方法内对变量的修改已经生效,但是不没有影响到调用者调用处的变量值。

02引用类型按值传递

当引用类型按值传递时,调用者会把引用类型变量的引用副本传递给方法,因此被调用方法参数会创建一个新的内存地址用于接收存储变量,而对于一个引用类型变量来说其本身存储的就是引用类型实例对象的引用副本,而方法接收到的也是此变量引用的副本,所以调用者参数和被调用方法参数是引用了同一个实例对象的两个引用副本。如下图Test a可以理解为调用者传的实参,Test b可以理解为被调用方法定义的形参,这两个参数都只是指向堆中Test a的引用副本。

在这里插入图片描述

因此可以得出两个结论:

1、变量a和b都是指向实例对象Test a的引用,所以无论变量a或b,只要有一个更新了实例成员则另一个变量也会同步发生变化。

2、虽然变量a和b都是指向实例对象Test a的引用,但是他们存储在栈上的内存地址却不同,因此如果他们各种重新分配实例也就是new一个新对象,则另一个变量无法感知到还是保持原因状态不变。

我们先用代码说明第一个结论:

public static void ChangeReferenceByValueRun()
{var a = new Test{Age = 10};Console.WriteLine($"调用者-调用方法前 a.Age 值:{a.Age}");ChangeReferenceByValue(a);Console.WriteLine($"调用者-调用方法后 a.Age 值:{a.Age}");
}
public static void ChangeReferenceByValue(Test a)
{Console.WriteLine($"    被调用方法-接收到 a.Age 值:{a.Age}");a.Age = a.Age + 1;Console.WriteLine($"    被调用方法-修改后 a.Age 值:{a.Age}");
}

运行结果如下:

在这里插入图片描述

可以看到被调用方法中a实例对象的Age属性发生变化后,调用者中变量也同步发生了变化。

对于第二个结论我们这样论证,在方法中直接对参数new一个新对象,看看原变量是否发生变化,代码如下:

public static void NewReferenceByValueRun()
{var a = new Test{Age = 10};Console.WriteLine($"调用者-调用方法前 a.Age 值:{a.Age}");NewReferenceByValue(a);Console.WriteLine($"调用者-调用方法后 a.Age 值:{a.Age}");
}
public static void NewReferenceByValue(Test a)
{Console.WriteLine($"    被调用方法-接收到 a.Age 值:{a.Age}");a = new Test{Age = 100};Console.WriteLine($"    被调用方法-new后 a.Age 值:{a.Age}");
}

执行结果如下:

在这里插入图片描述

可以发现当在方法中对变量执行new操作后,调用者处的变量并没有发生变化。

为什么会这样呢?因为对于引用类型来说,形参和实参是对引用类型的实例对象引用的两个副本,而这两个副本存储在栈上又分别在不同的内存地址空间上,而new主要就是重新分配内存,这就导致形参变量a=new后,栈上形参变量a指向了Test新的实例对象的引用,而实参变量a还是保持原有实例对象引用不变。

如下图所示。

在这里插入图片描述

03值类型按引用传递

当值类型按引用传递时,调用者会把值类型变量对应的栈空间地址传递给方法,因此被调用方法参数不会创建一个新的内存地址用于接收存储变量,因此当在方法内部对参数进行修改时并同样会影响调用者调用处的值类型变量。

在这里插入图片描述

传递值类型变量对应的栈空间地址就意味着形参与实参共同指向相同的内存地址,所以才导致对形参修改时,实参也会同步发生变化。

我们用一个小例子演示一下:

public static void ValueByReferenceRun()
{Console.WriteLine($"值类型按引用传递");var a = 10;Console.WriteLine($"调用者-调用方法前 a 值:{a}");ChangeValueByReference(ref a);Console.WriteLine($"调用者-调用方法后 a 值:{a}");
}
public static void ChangeValueByReference(ref int a)
{Console.WriteLine($"    被调用方法-接收到 a 值:{a}");a = a + 1;Console.WriteLine($"    被调用方法-修改后 a 值:{a}");
}

执行结果如下:

在这里插入图片描述

可以发现调用者处的值类型变量已经发生改变。

04引用类型按引用传递

当引用类型按引用传递时,调用者会把引用类型变量对应的栈空间地址传递给方法,因此被调用方法参数不会创建一个新的内存地址用于接收存储变量,因此当在方法内部对参数进行修改时并同样会影响调用者调用处的引用类型变量。

在这里插入图片描述

传递引用类型变量对应的栈空间地址就意味着形参与实参共同指向相同的内存地址,因此对形参修改时,实参也会同步发生变化,而且这个里的修改不单单指修改实例成员,还包括new一个新实例对象。

下面我们看一个修改实例成员的例子:

public static void ChangeReferenceByReferenceRun()
{Console.WriteLine($"引用类型按引用传递 - 修改实例成员");var a = new Test{Age = 10};Console.WriteLine($"调用者-调用方法前 a.Age 值:{a.Age}");ChangeReferenceByReference(ref a);Console.WriteLine($"调用者-调用方法后 a.Age 值:{a.Age}");
}
public static void ChangeReferenceByReference(ref Test a)
{Console.WriteLine($"    被调用方法-接收到 a.Age 值:{a.Age}");a.Age = a.Age + 1;Console.WriteLine($"    被调用方法-修改后 a.Age 值:{a.Age}");
}

执行结果如下:

在这里插入图片描述

再看看new一个新对象的例子:

public static void NewReferenceByReferenceRun()
{Console.WriteLine($"引用类型按引用传递 - new 新实例");var a = new Test{Age = 10};Console.WriteLine($"调用者-调用方法前 a.Age 值:{a.Age}");NewReferenceByReference(ref a);Console.WriteLine($"调用者-调用方法后 a.Age 值:{a.Age}");
}
public static void NewReferenceByReference(ref Test a)
{Console.WriteLine($"    被调用方法-接收到 a.Age 值:{a.Age}");a = new Test{Age = 100};Console.WriteLine($"    被调用方法-new后 a.Age 值:{a.Age}");
}

执行结果如下:

在这里插入图片描述

另外string是一个特殊的引用类型,string类型变量的按值传递和按引用传递和值类型是一致的,也就是要把string类型当值类型一样看待就行。string类型的特殊性我们后面会单独具体介绍。

在C#中以下修饰符可应用与参数声明,并且会使得参数按引用传递:ref、out、readonly ref、in。对于每个修饰符具体怎么使用就不再这里细说了。

相信到这里你应该就可以回答我之前在《LeetCode题集-2 - 两数相加》最后提的问题了。

:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner

相关文章:

C#|.net core 基础 - 值传递 vs 引用传递

不知道你在开发过程中有没有遇到过这样的困惑:这个变量怎么值被改?这个值怎么没变? 今天就来和大家分享可能导致这个问题的根本原因值传递 vs 引用传递。 在此之前我们先回顾两组基本概念: 值类型** vs 引用类型** **值类型&a…...

axure的下载,激活,汉化全过程,多图

1.前言 下载地址:https://pan.baidu.com/s/12xo1mJer2hmBK7QrYM5v-Q?pwd0107#list/path%2Fcsdn%E5%85%B1%E4%BA%AB%E6%96%87%E4%BB%B6 源文章:https://blog.csdn.net/iwanttostudyc/article/details/123773796?ops_request_misc%257B%2522request%25…...

LCR 026

题目:LCR 026 解法一:线性表 将链表中所有元素加入数组中,创建两个指针,分别指向数组的头部和尾部,然后向中间遍历 public void reorderList(ListNode head) {if (head null || head.next null || head.next.next …...

万能小程序运营管理系统 _requestPost 任意文件读取漏洞复现

0x01 产品简介 万能小程序运营管理系统是一种功能全面的系统,旨在帮助开发者和运营人员更好地管理和推广小程序。该系统集成了多种功能模块,覆盖了从小程序开发、部署到运营管理的全链条服务。系统通过提供丰富的功能和工具,帮助用户轻松搭建、管理和优化小程序。该系统支持…...

libyuv之linux编译

文章目录 一、下载源码二、编译源码三、注意事项1、银河麒麟系统(aarch64)(1)解决 armv8-adotprodi8mm 指令集支持问题(2)解决 armv9-asve2 指令集支持问题 一、下载源码 到GitHub网站下载https://github.…...

vue3路由基本使用

在 Vue 3 中,路由指的是应用程序的导航系统,允许你在不同的视图或页面之间进行切换。通过 vue-router 插件,你可以定义路由规则,将 URL 路径映射到 Vue 组件,实现页面间的跳转和状态管理。使用路由,用户可以…...

哪些人适合学习人工智能?

人工智能(AI)的浪潮正席卷全球,它不仅是科技领域的一场革命,更是社会进步的重要推手。随着AI技术的不断成熟和应用领域的不断拓展,越来越多的人开始关注并渴望掌握这一前沿技术。那么,究竟哪些人适合学习人…...

计算机的错误计算(九十七)

摘要 讨论 的计算精度问题。 由计算机的错误计算(九十六)知,IEEE754-2019标准中含有 运算。 另外,似乎没有语言直接编程实现内置了该运算。 例1. 已知 x-0.9999999999076 . 计算 不妨用 Python的 math库与 numpy库中的 …...

Flask-Migrate的使用

组织一个 Flask 项目通常需要遵循一定的结构,以便代码清晰、可维护。下面是一个典型的 Flask 项目结构: my_flask_app/ │ ├── app/ │ ├── __init__.py │ ├── models.py │ ├── views.py │ ├── forms.py │ ├── templat…...

python怎么输入整数

使用input()函数输入一个整数: ainput(“请输入一个整数\n”) 结果却报错TypeError: str object cannot be interpreted as an integer 查阅文档发现input()函数返回值是str型。 我们只需要这样转换: aint(input("请输入一…...

代码随想录打卡Day36

今天做的头皮发麻,除了第一道题是自己AC的,剩下两道题目都是看视频才AC的,主要是看了视频也花了很久时间才想清楚。 1049. 最后一块石头的重量 II 这道题一开始没什么思路,但是看到提示说和昨天的分割子集很像,然后我…...

速盾:凡科建站开cdn了吗?

凡科建站是一家专业的建站平台,提供了多种功能和工具来帮助用户快速搭建自己的网站。随着互联网技术的不断发展,网站的访问速度和稳定性成为了越来越重要的考虑因素。为了优化用户体验,提高网站的加载速度,凡科建站已经开启了CDN&…...

python贪吃蛇游戏项目源码【免费】

使用Pygame库实现的贪吃蛇游戏。Pygame是一个用于创建视频游戏的Python模块集合,它提供了图形和声音库,使游戏开发变得容易。 初始化设置 屏幕大小 (SCREEN_WIDTH, SCREEN_HEIGHT): 定义了游戏窗口的宽度和高度。方格大小 (SIZE): 定义了游戏中每个小方…...

Mycat搭建分库分表

分库分表解决的问题 单表数据量过大带来的性能和存储容量的限制的问题: 索引效率下降读写瓶颈存储容量限制事务性能问题分库分表架构 再搭建一对主从复制节点,3307主节点,3309从节点配置数据源 dw1 , dr1,创建集群c1创建逻辑库 CREATE DATAB…...

Python中的数据结构

1.列表 可以将列表理解为一个购物清单,这个清单里面的的每个元素可以是任何类型,并且可以重复(这里区别了列表与数组)。列表用“[]”表示。 #这里定义了一个列表,列表中的元素的类型不同。 lsit_a [a, b, c, 1, 2] …...

mysql笔记8(多表查询)

文章目录 1. union联合查询可能会用到去重操作 2. inner join 内连接3. left join 左连接4. right join 右连接5. cross join 交叉连接6. natural join 自然连接natural left join 自然左连接natural right join 自然右连接自然连接的两张表没有同名字段怎么办? 7. …...

typescript-tsconfig文件解释

ts.config.json 作用 用于标识 TypeScript 项目的根路径用于配置 TypeScript 编译器用于指定编译的文件 重要字段 files - 设置要编译的文件的名称;include - 设置需要进行编译的文件,支持路径模式匹配;exclude - 设置无需进行编译的文件…...

所有用贪心的算法和所有用动态规划(dp)的算法合集

贪心 纯贪心算法(常在普及组中做暴力题正解使用)DijkstraBFS(最短路径)KruskalPrimSollin(Boruvka)二分三分最值排序LCA(纯贪心版) 动态规划(dp) Floyd线性DP区间DP背…...

论文阅读 | 基于流模型和可逆噪声层的鲁棒水印框架(AAAI 2023)

Flow-based Robust Watermarking with Invertible Noise Layer for Black-box DistortionsAAAI, 2023,新加坡国立大学&中国科学技术大学本论文提出一种基于流的鲁棒数字水印框架,该框架采用了可逆噪声层来抵御黑盒失真。 一、问题 基于深度神经网络…...

上线跨境电商商城的步骤

上线一个跨境电商商城涉及多个步骤,从前期准备到上线后的维护。以下是一些关键步骤: 1. 市场调研与规划 目标市场分析:研究目标市场的需求、竞争对手和消费者行为。法律法规:了解并遵守目标市场的法律法规,包括税收、…...

Python基础(七)——PyEcharts数据分析(面向对象版)

四、使用PyEcharts数据分析案例(面向对象版) 【前言:为了巩固之前的Python基础知识(一)到(五),并为后续使用Python作为数据处理的好帮手,我们一起来用面向对象的思想来理…...

滚雪球学SpringCloud[5.1讲]: Spring Cloud Config详解

全文目录: 前言1. Spring Cloud Config的基本概念1.1 微服务配置管理的挑战1.2 Spring Cloud Config的架构 2. 配置服务端与客户端的配置2.1 配置服务端的搭建2.2 配置客户端的搭建2.3 环境隔离配置 3. 配置中心与Git的集成3.1 Git仓库的目录结构设计3.2 配置的版本…...

Unity常用随机数算法

Unity的Random.Range介绍 有两个重载: 如果参数存在至少一个浮点数那么将会触发public static float Range(float minInclusive, float maxInclusive); 返回一个范围内的浮点数(包含首尾) 如果参数都是整形则触发public static int Range(int minInclusive, int maxExclusiv…...

dial unix /var/run/docker.sock: connect: permission denied

要解决 permission denied 错误并授予当前用户 sunyuhua 访问 Docker 的权限,您可以按照以下步骤操作: 1. 检查 Docker 服务是否在运行 首先,确保 Docker 服务已经启动: sudo systemctl start docker sudo systemctl enable do…...

Prompt提示词技巧

文章目录 🍊 探索AI内容创作核心:提示词Prompt1 什么是提示词工程?1.1 提示词的原理1.2 提示词工程师的前景1.3 提示词工程师的门槛是否较低?1.4 提示词的未来展望 2 提示词编写的基本技巧3 常见的提示词框架3.1 CO-STAR框架3.2 BORKE框架3.…...

滑动窗口(6)_找到字符串中所有字母异位词

个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 滑动窗口(6)_找到字符串中所有字母异位词 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论&#x1f4…...

【无标题】rocket

rocketMQ集群双主双从同步模式(2m-2s-sync)搭建-CSDN博客 集群架构概念 在部署的时候首先要将nameserver启动起来,之后就是将broker启动起来,broker启动起来会将自己的信息注册到nameserver上面。之后再去创建topic,因为发消息的逻辑和收消…...

Maven国内镜像(四种)

配置Maven使用国内镜像是一个常见的做法&#xff0c;因为这样可以显著提高依赖下载的速度并避免网络不稳定带来的问题 在 settings.xml 文件中&#xff0c;需要添加或修改 <mirrors> 标签来指定国内镜像。 以下是几个可用的镜像 1. 阿里云 <mirrors> <mi…...

Linux环境中如何快速修改 JAR 包中的配置文件

在日常的 Java 开发中&#xff0c;我们时常会遇到需要修改 JAR 包中某个配置文件的情况。比如&#xff0c;某些场景下你可能需要调整 application-dev.yml 文件中的配置信息。但解压整个 JAR 包再重新打包会显得比较繁琐。本文将介绍一种快捷的方法&#xff0c;帮助你快速查找并…...

java高频面试题(2024最新)

HashMap使用哪些方法来解决哈希冲突&#xff1f; 使用链地址法&#xff08;使用散列表&#xff09;来链接拥有相同hash值的数据&#xff1b;使用2次扰动函数&#xff08;hash函数&#xff09;来降低哈希冲突的概率&#xff0c;使得数据分布更平均&#xff1b;引入红黑树进一步…...