值参数(Value Parameters)

值参数(Value Parameter):C# 默认的参数传递方式。调用方法时,把实参的值复制一份,传给形参

注意和我们前面学习的值类型要区分,是两个不同的概念。

  • 值类型(value type):数据类别(int、float、struct 等)
  • 值参数(value parameter):参数传递机制,二者完全不是一回事!

方法调用时系统执行步骤

  1. 在栈上为形参分配内存空间;
  2. 实参的值复制到形参空间。

关键点:形参和实参是两块独立栈内存,默认互不影响;但要区分实参是【值类型】还是【引用类型】。

实参可以是什么

实参不一定必须是变量,任何能算出对应类型结果的表达式都可以

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

值参数 + 值类型 / 引用类型行为差异

统一规则:值参数永远复制「实参存储在栈上的值」

  1. 实参是值类型(int、double、struct) 栈上存的是数据本身 → 复制数据副本;方法内修改形参,不会影响外部实参变量
  2. 实参是引用类型(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)

现象解释

  1. int 是值类型:n1 是 n0副本,方法内修改 n1 ,外部 n0 不变,依旧是 10;
  2. Product 是引用类型:复制引用地址,p1 和 p0 指向堆同一个对象;修改对象内部 Numbers,外部可以看到变成 25。

内存执行流程

  1. 调用前 栈:p0(引用地址)、n0(数值 10);堆存在 Product 对象,Numbers=20
  2. 方法刚进入 栈新增形参 p1n1;复制值:
  • n1 = n0(拷贝数值)
  • p1 = p0 相同的对象引用(拷贝地址)
  1. 方法执行末尾
  • n1 修改为 15;
  • 通过 p1 修改堆对象 Number=25
  1. 方法执行完毕 形参 p1 、n1 从栈销毁; 外部 n 0仍然 = 10;p1 指向堆对象已经被修改,Numbers=25

n1 和n0比较好理解,它们其实就是两个不同的变量,在MyMethod的外面和里面是两个不同的变量,里面的变量做任何操作不会影响外面的变量,函数执行完毕后,里面的变量就会出栈,然后就等于什么都没了。

但是引用类型就不一样,因为调用函数的时候,把p0赋值给p1,其实赋值的是地址,相当于两个人拿到同一把钥匙,有了钥匙,函数内部,通过这个钥匙(地址)打开的是同一个门,然后操作的是同一个东西,外面的p0,通过这个钥匙,访问这个门的东西和里面p1是一样的。

上图是,在方法内部的时候,内存中的结构是这样的。

Previous:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注