C#關鍵字區別之ref和out
問題:爲什麼c#中要有ref和out?(而java中沒有)
需求假設:現需要通過一個叫Swap的方法交換a,b兩個變量的值。交換前a=1,b=2,斷言:交換後a=2,b=1。
現編碼如下:
2 {
3 static void Main(string[] args)
4 {
5 int a = 1;
6 int b = 2;
7 Console.WriteLine("交換前/ta={0}/tb={1}/t",a,b);
8 Swap(a,b);
9 Console.WriteLine("交換後/ta={0}/tb={1}/t",a,b);
10 Console.Read();
11 }
12 //交換a,b兩個變量的值
13 private static void Swap(int a,int b)
14 {
15 int temp = a;
16 a = b;
17 b = temp;
18 Console.WriteLine("方法內/ta={0}/tb={1}/t",a,b);
19 }
20 }
運行結果:
交換前 a = 1 b = 2
方法內 a = 2 b = 1
交換後 a = 1 b = 2
斷言失敗,並未達到我們的需求!
原因分析:int類型爲值類型,它存在於線程的堆棧中。當調用Swap(a,b)方法時,相當於把a,b的值(即1,2)拷貝一份,然後在方法內交換這兩個值。交換完後,a還是原來的a,b還是原來的b。這就是C#中按值傳遞的原理,傳遞的是變量所對應數據的一個拷貝,而非引用。
解決方案:因此,C#中提出了ref 和out兩個關鍵字。
修改代碼如下即可實現我們想要的結果:
2 {
3 static void Main(string[] args)
4 {
5 int a = 1;
6 int b = 2;
7 Console.WriteLine("交換前/ta={0}/tb={1}/t",a,b);
8 Swap(ref a,ref b);
9 Console.WriteLine("交換後/ta={0}/tb={1}/t",a,b);
10 Console.Read();
11 }
12 //交換a,b兩個變量的值
13 private static void Swap(ref int a, ref int b)
14 {
15 int temp = a;
16 a = b;
17 b = temp;
18 Console.WriteLine("方法內/ta={0}/tb={1}/t",a,b);
19 }
20 }
同理用out同樣可以實現我們的需求。
下面談談ref和out到底有什麼區別:
1 關於重載
原則:有out|ref關鍵字的方法可以與無out和ref關鍵字的方法構成重載;但如想在out和ref間重載,編譯器將提示:不能定義僅在ref和out的上的方法重載
2 關於調用前初始值
原則:ref作爲參數的函數在調用前,實參必須賦初始值。否則編譯器將提示:使用了未賦值的局部變量;
out作爲參數的函數在調用前,實參可以不賦初始值。
3 關於在函數內,引入的參數初始值問題
原則:在被調用函數內,out引入的參數在返回前至少賦值一次,否則編譯器將提示:使用了未賦值的out參數;
在被調用函數內,ref引入的參數在返回前不必爲其賦初值。
總結:C#中的ref和out提供了值類型按引用進行傳遞的解決方案,當然引用類型也可以用ref和out修飾,但這樣已經失去了意義。因爲引用數據類型本來就是傳遞的引用本身而非值的拷貝。ref和out關鍵字將告訴編譯器,現在傳遞的是參數的地址而不是參數本身,這和引用類型默認的傳遞方式是一樣的。同時,編譯器不允許out和ref之間構成重載,又充分說明out和ref的區別僅是編譯器角度的,他們生成的IL代碼是一樣的。有人或許疑問,和我剛開始學習的時候一樣的疑惑:值類型在託管堆中不會分配內存,爲什麼可以按地址進行傳遞呢?值類型雖然活在線程的堆棧中,它本身代表的就是數據本身(而區別於引用數據類型本身不代表數據而是指向一個內存引用),但是值類型也有它自己的地址,即指針,現在用ref和out修飾後,傳遞的就是這個指針,所以可以實現修改後a,b的值真正的交換。這就是ref和out給我們帶來的好處。
out的基本用法:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ref_out
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
int a = 1;
int b = 2;
this.textBox1.Text = a.ToString();
this.textBox2.Text = b.ToString();
//swap(ref a,ref b);
//this.textBox5.Text = a.ToString();
//this.textBox6.Text = b.ToString();
swap(out a, out b);
this.textBox5.Text = a.ToString();
this.textBox6.Text = b.ToString();
}
//public void swap(ref int a,ref int b)
//{
// int temp = a;
// a = b;
// b = temp;
// this.textBox3.Text = a.ToString();
// this.textBox4.Text = b.ToString();
//}
public void swap(out int a, out int b)
{
a = Convert.ToInt32(this.textBox1.Text);
b =Convert.ToInt32(this.textBox2.Text);
int temp = a;
a = b;
b = temp;
this.textBox3.Text = a.ToString();
this.textBox4.Text = b.ToString();
}
}
}