值参数(Value Parameter):C# 默认的参数传递方式。调用方法时,把实参的值复制一份,传给形参
注意和我们前面学习的值类型要区分,是两个不同的概念。
- 值类型(value type):数据类别(int、float、struct 等)
- 值参数(value parameter):参数传递机制,二者完全不是一回事!
方法调用时系统执行步骤
- 在栈上为形参分配内存空间;
- 将实参的值复制到形参空间。
关键点:形参和实参是两块独立栈内存,默认互不影响;但要区分实参是【值类型】还是【引用类型】。
实参可以是什么
实参不一定必须是变量,任何能算出对应类型结果的表达式都可以。
float funName(float val)
{
// function body
}
//准备两个变量
float bookPrice = 2.6F;
float eggPrice = 5.1F;
// 调用
float fValue1 = funName(bookPrice); // 实参:变量
float fValue2 = funName((bookPrice + eggPrice ) / 2); // 实参:表达式
float fValue2 = funName(1.36F); // 实参:float字面值Code language: C# (cs)
前置要求:变量作为实参前必须赋值(
out参数除外);引用类型变量可以赋值对象引用或null。
值参数 + 值类型 / 引用类型行为差异
统一规则:值参数永远复制「实参存储在栈上的值」
- 实参是值类型(int、double、struct) 栈上存的是数据本身 → 复制数据副本;方法内修改形参,不会影响外部实参变量。
- 实参是引用类型(class、数组) 栈上存的是堆对象的引用地址 → 复制引用地址; 形参、实参指向堆里同一个对象: 在方法内通过引用修改堆中对象的成员 → 外部能看见变化 ,在方法内直接给形参变量赋新对象(
f1 = new MyClass()),不会改变外部实参.
using System;
class Product
{
public int Numbers = 20;
}
class Program
{
static void MyMethod(Product p1, int n1)
{
p1.Numbers = p1.Numbers + 5; // 修改堆内对象字段
n1 = n1 + 5; // 修改值类型副本
Console.WriteLine($"p1.Numbers: {p1.Numbers}, n1: {n1}");
}
static void Main()
{
Product p0 = new Product();
int n0 = 10;
MyMethod(p0, n0);
Console.WriteLine($"p0.Numbers: {p0.Numbers}, n0: {n0}");
}
}
Code language: C# (cs)
输出结果
p1.Numbers: 25, n1: 15
p0.Numbers: 25, n0: 10
Code language: CSS (css)
现象解释
int是值类型:n1 是 n0副本,方法内修改n1,外部 n0 不变,依旧是 10;- Product 是引用类型:复制引用地址,p1 和 p0 指向堆同一个对象;修改对象内部
Numbers,外部可以看到变成 25。
内存执行流程
- 调用前 栈:
p0(引用地址)、n0(数值 10);堆存在 Product 对象,Numbers=20 - 方法刚进入 栈新增形参
p1、n1;复制值:
n1 = n0(拷贝数值)- p1 = p0 相同的对象引用(拷贝地址)
- 方法执行末尾
- n1 修改为 15;
- 通过 p1 修改堆对象
Number=25;
- 方法执行完毕 形参
p1、n1 从栈销毁; 外部 n 0仍然 = 10;p1 指向堆对象已经被修改,。Numbers=25
n1 和n0比较好理解,它们其实就是两个不同的变量,在MyMethod的外面和里面是两个不同的变量,里面的变量做任何操作不会影响外面的变量,函数执行完毕后,里面的变量就会出栈,然后就等于什么都没了。
但是引用类型就不一样,因为调用函数的时候,把p0赋值给p1,其实赋值的是地址,相当于两个人拿到同一把钥匙,有了钥匙,函数内部,通过这个钥匙(地址)打开的是同一个门,然后操作的是同一个东西,外面的p0,通过这个钥匙,访问这个门的东西和里面p1是一样的。
上图是,在方法内部的时候,内存中的结构是这样的。

Previous: 参数