C#值参数和引用参数

一、值参数

未用ref或out修饰符声明的参数为值参数。

使用值参数,通过将实参的值复制到形参的方式,把数据传递到方法。方法被调用时,系统做如下操作。

  • 在栈中为形参分配空间。
  • 复制实参到形参。

值参数的实参不一定是变量。它可以是任何能计算成相应数据类型的表达式。

看一个例子:

float func1(float val)    //声明方法
{
  float j=2.6F;
  float k=5.1F;
  ....
}

下面来调用方法

float fValue1=func1(k);        //实参是float类型的变量

float fValue2=func1((k+j)/3);  //实参可以计算成float表达式

在把变量作用于实参之前,变量必须赋值(除非是out参数)。对于引用类型,变量可以被设置为一个实际的引用或null。

下面的代码展示了一个名为MyMethod的方法,它有两个参数,一个是MyClass型变量和一个int。

 1  class MyClass
 2     {
 3         public int Val = 20;
 4     }
 5     class Program
 6     {
 7
 8         static void MyMethod(MyClass f1, int f2)
 9         {
10             f1.Val = f1.Val + 5;
11             f2 = f2 + 5;
12             Console.WriteLine("f1.Val: {0}, f2: {1}", f1.Val, f2);
13         }
14         static void Main(string[] args)
15         {
16             MyClass a1 = new MyClass();
17             int a2 = 10;
18
19             MyMethod(a1, a2);
20
21             Console.WriteLine("f1.Val: {0}, f2: {1}", a1.Val, a2);
22          }
23     }

我们用图来表示实参和形参在方法执行的不同阶段的值。

  • 在方法被调用前,用作实参的a2已经在栈里了。
  • 在方法开始前,系统在栈中为形参分配空间,并从实参复制值。
  • 因为a1是引用类型,所以引用被复制,结果实参和形参都引用堆中的同一对象。
  • 因为a2是值类型,所以值被复制,产生了一个独立的数据项。
  • 在方法的结尾,f2和对象f1的字段都被加上了5。
  • 方法执行后,形参从栈中弹出。
  • a2,值类型,它的值不受方法行为的影响。
  • a1,引用类型,但它的值被方法的行为改变了。

二、引用参数

使用引用参数时,必须在方法的申明和调用中都使用关键字ref修饰符。

实参必须是变量,在用作实参前必须被赋值。如果是引用类型的变量,可以赋值为一个引用或者null值。

下面的代码阐明了引用参数的声明和调用的语法:

  void MyMethod(ref int val)  //方法声明包含ref修饰符
  {
     //your code
   }
  int y = 1;
  MyMethod(ref y);   //方法调用

  MyMethod(ref 3+5);  //错误,形参必须是变量

在第一小节的内容中我们知道,对于值参数,系统在栈上为形参分配内存,相反对于引用参数:

  • 不会为形参在栈上分配内存。
  • 实际情况是,形参的参数名将作为实参变量的别名,指向相同的内存位置

由于形参名和实参名的行为,就好象指向相同的内存位置,所以在方法的执行过程中,对形参作的任何改变,在方法完成后依然有效(表现在实参变量上)。

在方法的声明和调用上都使用关键字ref.

下面的代码再次展示了方法MyMethod,但这一次参数是引用参数而不是值参数。

 1  class MyClass
 2     {
 3         public int Val = 20;
 4     }
 5     class Program
 6     {
 7
 8         static void MyMethod(ref  MyClass f1,ref int f2)
 9         {
10             f1.Val = f1.Val + 5;
11             f2 = f2 + 5;
12             Console.WriteLine("f1.Val: {0}, f2: {1}", f1.Val, f2);
13         }
14         static void Main(string[] args)
15         {
16             MyClass a1 = new MyClass();
17             int a2 = 10;
18
19             MyMethod(ref a1, ref a2);
20
21             Console.WriteLine("f1.Val: {0}, f2: {1}", a1.Val, a2);
22
23         }
24     }

同样,还是用图来阐明方法执行的不同阶段实参和形参的值。

  • 在方法被调用前,用作实参的a1,a2已经在栈里了。
  • 在方法的开始,形参名被设置为实参的别名。变量a1和f1引用相同的内存位置,a2和f2引用相同的内存位置。
  • 在方法的结束位置,f2和对象f1的字段都被加上了5。
  • 方法执行之后,形参的名称已经失效,但是值类型a2和引用类型a1所指向的对象的值都被方法内的行为改变了。

三、引用类型作为值参数和引用参数

对于一个引用类型对象,不管是将其作为值参数传递还是作为引用参数传递,我们都可以在方法成员内部修改它的成员。不过,我们并没有在方法内部设置形参本身。

下面我们就来看看在方法内部设置形参本身时会发生什么。

1、将引用类型对象作为值参数传递

 1  class MyClass
 2     {
 3         public int Val = 20;
 4     }
 5     class Program
 6     {
 7
 8         static void RefAsParameter(MyClass f1)
 9         {
10             f1.Val = 50;
11             Console.WriteLine("After member assignment:   {0}", f1.Val);
12             f1 = new MyClass();
13             Console.WriteLine("After new object creation: {0}", f1.Val);
14         }
15         static void Main(string[] args)
16         {
17
18             MyClass a1 = new MyClass();
19             Console.WriteLine("Before method  call:       {0}", a1.Val);
20             RefAsParameter(a1);
21             Console.WriteLine("After method  call:        {0}", a1.Val);
22         }
23     }

这段代码的输出如下:

Before method  call:       20
After member assignment:   50
After new object creation: 20
After method  call:        50

同样,还是用图来阐明以下几点。

  • 在方法开始时,实参和形参都指向堆中相同的对象。
  • 在为对象的成员赋值之后,他们仍指向堆中相同的对象。
  • 当方法分配新的对象并赋值给形参时,方法外部的实参仍指向原始对象,而形参指向的是新对象。
  • 在方法调用之后,实参指向原始对象,形参和新对象都会消失。

2、将引用类型对象作为引用参数传递

除了在方法声明和方法调用时使用ref关键字之外,与上面的代码完全一样。

 1   class MyClass
 2     {
 3         public int Val = 20;
 4     }
 5     class Program
 6     {
 7
 8         static void RefAsParameter(ref MyClass f1)
 9         {
10             f1.Val = 50;
11             Console.WriteLine("After member assignment:   {0}", f1.Val);
12             f1 = new MyClass();
13             Console.WriteLine("After new object creation: {0}", f1.Val);
14         }
15         static void Main(string[] args)
16         {
17
18             MyClass a1 = new MyClass();
19             Console.WriteLine("Before method  call:       {0}", a1.Val);
20             RefAsParameter(ref a1);
21             Console.WriteLine("After method  call:        {0}", a1.Val);
22         }
23     }

这段代码的输出如下:

Before method  call:       20
After member assignment:   50
After new object creation: 20
After method  call:        20

我们开始说过,引用参数的行为就是将实参作为形参的别名。

  • 在方法开始时,实参和形参都指向堆中相同的对象。
  • 在为对象的成员赋值之后,他们仍指向堆中相同的对象。
  • 当方法分配新的对象并赋值给形参时,形参和实参都指向新对象。
  • 在方法调用之后,实参指向方法内创建的新对象

四、写在最后

这些都是老生常谈的问题,为什么还要写?

一是因为今天看书看到了与此相关的内容,回去翻了翻书,然后记录下来

二是供自己以后查阅,毕竟看博客比翻书来的快。

最后,祝大家周末愉快,玩的开心。

时间: 2024-08-02 13:42:04

C#值参数和引用参数的相关文章

值参数和引用参数

值参数 方法被调用时系统做如下操作: 1.在栈中为形参分配空间: 2.将实参的值复制给形参: 在变量用作实参前,变量必须要被赋值(除非是输出参数),对于引用类型, 变量可以被设置为一个实际的引用或null. namespace ConsoleApplication1 { class A { public int val=10; } class Program { static void method(A obj2,int f1) { obj2.val = obj2.val + 5; f1 = f

C#值参数和引用参数,方法的重载,foreach,数组,以及ref和out的用法

1.方法的传输传递 值参数:传递的是副本 引用参数:自身 保留自定义的方法中对值的改变 形参影响实参ref:对应的形参和实参都用ref修饰 输出参数:实参不用赋值,但是自定义方法内必须对此参数赋值!!! 把自定义方法产生的结果带回调用处out:对应的形参和实参都用out修饰 必须在自定义方法中赋值 注:如果需要返回一个参数 使用return 如果需要反回多个参数 使用ref 或者 out TryParse用法:自行判断转换是否成功 转换成功反回true 转换失败返回false 例: bool f

C#值参数、引用参数和输出参数

一.值参数:在使用参数时,是把一个值传递给函数使用的一个变量.对函数中此变量的任何修改都不会影响函数调用中指定的参数.(由于函数只有一个返回值,不能用作参数的多个变量值). 二.引用参数:即函数处理的变量与函数调用中使用的变量相同,而不仅仅是值相同的变量.因此,对这个变量的任何改变都会影响用作参数的变量值.需用ref关键字指定参数.用作ref参数的变量有两个限制,由于函数可能会改变引用参数的值,所有必须在函数调用中使用“非常量”变量.其次,必须使用初始化过的变量. 三.输出参数:out关键字,指

(一)Python入门-5函数:06参数类型-位置参数-默认值参数-命名参数-可变参数-强制命名参数

参数的几种类型: 位置参数: 函数调用时,实参默认按位置顺序传递,需要个数和形参匹配.按位置传递的参数,称为: “位置参数” 默认值参数: 我们可以为某些参数设置默认值,这样这些参数在传递时就是可选的.称为“默认值参数”. 默认值参数放到位置参数后面. 命名参数: 我们也可以按照形参的名称传递参数,称为“命名参数”,也称“关键字参数”. 可变参数: 可变参数指的是“可变数量的参数”.分两种情况: 1. *param(一个星号),将多个参数收集到一个“元组”对象中. 2. **param(两个星号

python-在定义函数时,不定长参数中,默认值参数不能放在必选参数前面

如果一个函数的参数中含有默认参数,则这个默认参数后的所有参数都必须是默认参数,否则会报错:SyntaxError: non-default argument follows default argument 例如: #-*- coding:utf-8 -*-def test(a=11,b,c): #默认值参数不能放在必选参数前面    print(a)    print(b)    print(c)    test(11,22,33) 改正: #-*- coding:utf-8 -*- def

C#方法参数--值参数,引用参数,输出参数

值参数: 使用值参数,通过复制实参的值到形参的方式,把数据传递到方法,方法被调用的时候,系统做如下操作: 在栈中为形参分配空间: 复制实参到形参. 注意:一个值参数的实参不一定是变量,它可以是任何能够计算成相应数据类型的表达式. 在把变量用作实参之前,变量必须被赋值(除非是输出参数,这个稍后介绍),对于引用类型,变量可以设置成实际引用或者Null. 引用参数: 使用引用参数时,必须在方法的申明和调用中都使用关键字ref修饰符. 实参必须是变量,在用作实参前必须被赋值.如果是引用类型的变量,可以赋

C++ const引用、临时变量 引用参数

C++引用-临时变量.引用参数和const引用 如果实参与引用参数不匹配,C++将生成临时变量.如果引用参数是const,则编译器在下面两种情况下生成临时变量: 实参类型是正确的,但不是左值 实参类型不正确,但可以转换为正确的类型 左值参数是可被引用的数据对象,例如,变量.数组元素.结构成员.引用和被解除引用的指针都是左值,非左值包括字面常量和包含多项式的表达式.定义一个函数 Double refcube(const double& ra) { Returnra*ra*ra; } double

Caffe学习系列(16):各层权值参数可视化

通过前面的学习,我们已经能够正常训练各种model了.我们训练cifar10数据,迭代10000次,然后将训练好的 model保存起来,名称为my_iter_10000.caffemodel,然后使用jupyter notebook 来进行可视化. 首先,导入必要的库 In [1]: import numpy as np import matplotlib.pyplot as plt import os,sys,caffe %matplotlib inline In [2]: caffe_roo

JSON数据源提供多值参数的实现

一.应用场景 (1)报表的数据内容需要根据某个参数进行过滤. (2)该参数是一个多值参数,即从一个下拉列表中选择一个或多个项目. (3)报表需要自动运行,因此参数必须有默认值. (4)参数默认值无法在设计报表时就确定下来. 二.实现方案 问题的核心是:报表自动运行是无人值守的,没有人机交互的机会,因此"必需性(Required)"报表参数必须有默认值,但是某些报表的参数取值是与业务系统有关的,无法在设计报表模板时就确定好参数的默认值. 解决这一问题的思路是:编写一个Web Servic