深刻理解C#的传值调用和传引用调用
来源:互联网 发布:java还可以学什么 编辑:程序博客网 时间:2024/06/05 20:19
深刻理解C#的传值调用和传引用调用
Posted on 2011-05-18 23:11 wang_yb 阅读(5220) 评论(6) 编辑 收藏传值调用和传引用调用是几乎所有主流语言都会涉及到的问题,下面我谈谈我对C#中传值调用和传引用调用的理解。
1. 一般对C#中传值调用和传引用调用的理解
- 如果传递的参数是基元类型(int,float等)或结构体(struct),那么就是传值调用。
- 如果传递的参数是类(class)那么就是传引用调用。
- 如果传递的参数前有ref或者out关键字,那么就是传引用调用。
验证示例的代码如下:
using
System;
public
class
ArgsByRefOrValue
{
public
static
void
Main(
string
[] args)
{
// 实验1. 传值调用--基元类型
int
i = 10;
Console.WriteLine(
"before call ChangeByInt: i = "
+ i.ToString());
ChangeByInt(i);
Console.WriteLine(
"after call ChangeByInt: i = "
+ i.ToString());
Console.WriteLine(
"=============================================="
);
// 实验2. 传值调用--结构体
Person_val p_val =
new
Person_val();
p_val.name =
"old val name"
;
Console.WriteLine(
"before call ChangeByStruct: p_val.name = "
+ p_val.name);
ChangeByStruct(p_val);
Console.WriteLine(
"after call ChangeByStruct: p_val.name = "
+ p_val.name);
Console.WriteLine(
"=============================================="
);
// 实验3. 传引用调用--类
Person_ref p_ref =
new
Person_ref();
p_ref.name =
"old ref name"
;
Console.WriteLine(
"before call ChangeByClass: p_ref.name = "
+ p_ref.name);
ChangeByClass(p_ref);
Console.WriteLine(
"after call ChangeByClass: p_ref.name = "
+ p_ref.name);
Console.WriteLine(
"=============================================="
);
// 实验4. 传引用调用--利用ref
Person_ref p =
new
Person_ref();
p.name =
"old ref name"
;
Console.WriteLine(
"before call ChangeByClassRef: p.name = "
+ p.name);
ChangeByClassRef(
ref
p);
Console.WriteLine(
"after call ChangeByClassRef: p.name = "
+ p.name);
Console.ReadKey(
true
);
}
static
void
ChangeByInt(
int
i)
{
i = i + 10;
Console.WriteLine(
"when calling ChangeByInt: i = "
+ i.ToString());
}
static
void
ChangeByStruct(Person_val p_val)
{
p_val.name =
"new val name"
;
Console.WriteLine(
"when calling ChangeByStruct: p_val.name = "
+ p_val.name);
}
static
void
ChangeByClass(Person_ref p_ref)
{
p_ref.name =
"new ref name"
;
Console.WriteLine(
"when calling ChangeByClass: p_ref.name = "
+ p_ref.name);
}
static
void
ChangeByClassRef(
ref
Person_ref p)
{
p.name =
"new ref name"
;
Console.WriteLine(
"when calling ChangeByClassRef: p.name = "
+ p.name);
}
}
public
struct
Person_val
{
public
string
name;
}
public
class
Person_ref
{
public
string
name;
}
运行结果如下:
看起来似乎上面代码中实验3和实验4是一样的,即对于类(class)来说,不管加不加ref或out,都是传引用调用。
其实,这只是表面的现象,只要稍微改一下代码,结果就不一样了。
修改上面代码,再增加两个实验。
using
System;
public
class
ArgsByRefOrValue
{
public
static
void
Main(
string
[] args)
{
// 实验1. 传值调用--基元类型
int
i = 10;
Console.WriteLine(
"before call ChangeByInt: i = "
+ i.ToString());
ChangeByInt(i);
Console.WriteLine(
"after call ChangeByInt: i = "
+ i.ToString());
Console.WriteLine(
"=============================================="
);
// 实验2. 传值调用--结构体
Person_val p_val =
new
Person_val();
p_val.name =
"old val name"
;
Console.WriteLine(
"before call ChangeByStruct: p_val.name = "
+ p_val.name);
ChangeByStruct(p_val);
Console.WriteLine(
"after call ChangeByStruct: p_val.name = "
+ p_val.name);
Console.WriteLine(
"=============================================="
);
// 实验3. 传引用调用--类
Person_ref p_ref =
new
Person_ref();
p_ref.name =
"old ref name"
;
Console.WriteLine(
"before call ChangeByClass: p_ref.name = "
+ p_ref.name);
ChangeByClass(p_ref);
Console.WriteLine(
"after call ChangeByClass: p_ref.name = "
+ p_ref.name);
Console.WriteLine(
"=============================================="
);
// 实验4. 传引用调用--利用ref
Person_ref p =
new
Person_ref();
p.name =
"old ref name"
;
Console.WriteLine(
"before call ChangeByClassRef: p.name = "
+ p.name);
ChangeByClassRef(
ref
p);
Console.WriteLine(
"after call ChangeByClassRef: p.name = "
+ p.name);
Console.WriteLine(
"=============================================="
);
// 实验5. 传引用调用--类 在调用的函数重新new一个对象
Person_ref p_ref_new =
new
Person_ref();
p_ref_new.name =
"old new ref name"
;
Console.WriteLine(
"before call ChangeByClassNew: p_ref_new.name = "
+ p_ref_new.name);
ChangeByClassNew(p_ref_new);
Console.WriteLine(
"after call ChangeByClassNew: p_ref_new.name = "
+ p_ref_new.name);
Console.WriteLine(
"=============================================="
);
// 实验6. 传引用调用--利用ref 在调用的函数重新new一个对象
Person_ref p_new =
new
Person_ref();
p_new.name =
"old new ref name"
;
Console.WriteLine(
"before call ChangeByClassRefNew: p_new.name = "
+ p_new.name);
ChangeByClassRefNew(
ref
p_new);
Console.WriteLine(
"after call ChangeByClassRefNew: p_new.name = "
+ p_new.name);
Console.ReadKey(
true
);
}
static
void
ChangeByInt(
int
i)
{
i = i + 10;
Console.WriteLine(
"when calling ChangeByInt: i = "
+ i.ToString());
}
static
void
ChangeByStruct(Person_val p_val)
{
p_val.name =
"new val name"
;
Console.WriteLine(
"when calling ChangeByStruct: p_val.name = "
+ p_val.name);
}
static
void
ChangeByClass(Person_ref p_ref)
{
p_ref.name =
"new ref name"
;
Console.WriteLine(
"when calling ChangeByClass: p_ref.name = "
+ p_ref.name);
}
static
void
ChangeByClassRef(
ref
Person_ref p)
{
p.name =
"new ref name"
;
Console.WriteLine(
"when calling ChangeByClassRef: p.name = "
+ p.name);
}
static
void
ChangeByClassNew(Person_ref p_ref_new)
{
p_ref_new =
new
Person_ref();
p_ref_new.name =
"new ref name"
;
Console.WriteLine(
"when calling ChangeByClassNew: p_ref_new.name = "
+ p_ref_new.name);
}
static
void
ChangeByClassRefNew(
ref
Person_ref p_new)
{
p_new =
new
Person_ref();
p_new.name =
"new ref name"
;
Console.WriteLine(
"when calling ChangeByClassRefNew: p_new.name = "
+ p_new.name);
}
}
public
struct
Person_val
{
public
string
name;
}
public
class
Person_ref
{
public
string
name;
}
则运行结果为:
实验5的运行结果似乎说明即使参数是类(class),只要不加ref,也是传值调用。
下面就引出了我的理解。
2. 没有ref时,即使参数为引用类型(class)时,也可算是一种传值调用
参数为引用类型时,传递的是该引用类型的地址的一份拷贝,“该引用类型的地址的一份拷贝”即为传值调用的“值”。
注意这里说传递的是该引用类型的地址的一份拷贝,而不是引用类型的地址。
下面将用图的形式来说明以上实验3,实验5和实验6中内存的情况。
2.1 首先是实验3
实验3的内存图如下,实参是函数ChangeByClass外的Person_ref对象,形参是函数ChangeByClass内的Person_ref对象。
从图中我们可以看出实参new出来之后就在托管堆上分配了内存,并且在栈上保存了对象的指针。
调用函数ChangeByClass后,由于没有ref参数,所以将栈上的实参p_val拷贝了一份作为形参,注意这里p_val(实参)和p_val(形参)是指向托管堆上的同一地址。
所以说没有ref时,即使参数为引用类型(class)时,也可算是一种传值调用,这里的值就是托管堆中对象的地址(0x1000)。
调用函数ChangeByClass后,通过p_val(形参)修改了name属性的值,由于p_val(实参)和p_val(形参)是指向托管堆上的同一地址,所以函数外的p_val(实参)的name属性也被修改了。
2.2 然后是实验5
上面的实验3从执行结果来看似乎是传引用调用,因为形参的改变导致了实参的改变。
下面的实验5就可以看出,p_val(形参)和p_val(实参)并不是同一个变量,而是p_val(实参)的一个拷贝。
从图中可以看出第一步还是和实验3一样,但是在调用函数ChangeByClassNew后,就不一样了。
函数ChangeByClassNew中,对p_val(形参)重新分配了内存(new操作),使其指向了新的地址(0x1100),如下图:
所以p_val(形参)的name属性改了时候,p_val(实参)的name属性还是没变。
2.3 最后是实验6
我觉得实验6是真正的传引用调用。不废话了,直接上第一个图。
参数中加了ref关键字之后,其实传递的不是托管堆中对象的地址(0x1000),而是栈上p_val(实参)的地址(0x0001)。
所以这里实参和形参都是栈上的同一个东西,没有什么区别了。我觉得这才是真正的传引用调用。
然后调用了函数ChangeByClassRefNew,函数中对p_val(形参)重新分配了内存(new操作),使其指向了新的地址(0x1100)。
由于p_val(形参)就是p_val(实参),所以p_val(形参)的name属性改变后,函数ChangeByClassRefNew外的p_val(实参)的name属性也被改变了。
而原先分配的对象(地址0x1000)其实已经没有被引用了,随时会被GC回收。
3. 结论
- 如果调用的函数中对参数重新进行了地址分配(new操作),那么执行结果类似传值调用
- 如果调用的函数中没有对参数重新进行了地址分配,直接就是使用了传递的参数,那么执行结果类似传引用调用
- 深刻理解C#的传值调用和传引用调用
- 深刻理解C#的传值调用和传引用调用
- c# 传值调用和传引用调用
- 传值调用和引用调用
- 传值调用和引用调用
- 引用调用和传值调用
- c#区分传值调用 传引用调用。
- 传值调用&引用调用
- C++函数的传值调用&指针调用&引用调用
- c#中函数调用的按值传递和按引用传值
- 函数的传值调用,传址调用和引用调用分析
- java 实参、形参、传值调用、传引用调用的理解
- C++的值调用和引用调用
- 传地址调用和传引用调用的区别
- 函数的传址调用和引用调用
- 传址调用和引用调用的区别
- 深刻理解引用、const引用、右值引用的本质
- C语言函数调用三种方式:传值调用,引用调用和传地址调用
- word转pdf教程
- Android ViewPagr使用RequestFocus没有效果的解决方法
- 关于带返回值委托函数和委托函数使用引用参数的问题
- 杂记
- ios版本更新
- 深刻理解C#的传值调用和传引用调用
- A星
- Linux下的tar压缩解压缩命令详解
- EditText与TextView共舞setOnKeyListener事件
- UDI-00018 EXPDP RELOAD
- 泛洪法与SMC算法的综合使用
- 专业DBA级的MYSQL 优化
- About hadoop hdfs filesystem rename
- as3与服务端通信