今天在看一段C#代碼的時候,發現對ref和out兩個關鍵不瞭解,所以就查閱了C#語言相關的教程,深入的學習了一下。
ref關鍵字的作用是使參數按參考型別傳遞,這樣控制權傳遞給調用方法時,在方法中對參數所做的任何更改都將反映在該變數中。
這裡提到了參考型別,其實在C#中共有兩種類型,一種是實值型別(結構體、數實值型別、bool型、枚舉、可空類型),另一種是參考型別(自訂類、介面、委託、數組、字串類型、object)。實值型別和參考型別的區別在於以下幾點:
1、實值型別直接儲存其值,變數本身就包含了執行個體資料,而參考型別儲存的只是執行個體資料的記憶體引用。因此,一個實值型別變數就永遠不會影響到其他的實值型別變數,而兩個參考型別變數則很有可能指向同一地址,從而發生相互影響。
2、從記憶體配置上來看,實值型別通常分配線上程的堆棧上,範圍結束時,所佔空間自行釋放,效率高,無需進行地址轉換,而參考型別通常分配在託管堆上,由GC來控制其回收,需要進行地址轉換,效率降低,這也正是c#需要定義兩種資料類型的原因之一。
3、實值型別均隱式派生自System.ValueType,而System.ValueType又直接派生於 System.Object,每種實值型別均有一個隱式的預設建構函式來初始化該類型的預設值,注意所有的實值型別都是密封(sealed)的,所以無法派生 出新的實值型別。而且System.ValueType本身是一個類類型,而不是實值型別,因為它重寫了object的Equals()方法,所以對實值型別將 按照執行個體的值來比較,而不是比較引用地址。
4、C# 的統一類型系統,使得實值型別可以轉化為對象來處理,這就是常說的裝箱和拆箱。由於裝拆箱需要裝建全新對象或做強制類型轉換,這些操作所需時間和運算要遠遠大於賦值操作,因此不提倡使用它,同時也要盡量避免隱式裝拆箱的發生。
註:棧是作業系統分配的一個連續的記憶體地區,用於快速存取資料。因為實值型別的容量是已知的,因此它可儲存在棧上。而託管堆是CLR在應用程式啟動時為應用程式預留的一塊連續記憶體區,是用於動態記憶體分配的記憶體區,參考型別的容量只有到運行時才能確定,所有用堆來儲存參考型別。
理解了C#中實值型別與參考型別的區別再來看ref關鍵字就會非常簡單了。下面通過一個小例子來說明ref關鍵字的作用。
下面這段代碼的功能是實現兩個整數按由小到大的順序進行排序。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ref1
{
class Program
{
/// <summary>
/// 從小到大進行排序
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
public static void sawp(int a, int b)
{
int c = 0;
if (a > b)
{
c = a;
a = b;
b = c;
}
Console.WriteLine("a的值為:{0},b的值為:{1}", a, b);
}
static void Main(string[] args)
{
int x = 6;
int y = 4;
sawp(x, y);
Console.WriteLine("x的值為:{0},y的值為:{1}", x, y);
Console.ReadLine();
}
}
}
運行結果如下:
程式啟動並執行結果出人預料,仔細分析後就會發現,swap函數的參數是通過值傳遞的,它只是將x、y的副本值進行了交換,而記憶體中的值是沒有改變的。現在我們在參數的前面加上ref關鍵字。
View Code using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ref1
{
class Program
{
/// <summary>
/// 從小到大進行排序
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
public static void sawp(ref int a,ref int b)
{
int c = 0;
if (a > b)
{
c = a;
a = b;
b = c;
}
}
static void Main(string[] args)
{
int x = 6;
int y = 4;
sawp(ref x,ref y);
Console.WriteLine("x的值為:{0},y的值為:{1}", x, y);
Console.ReadLine();
}
}
運行結果如下:
可以看到,程式給出了正確的結果。
下面來說說out關鍵字,out關鍵字的功能跟ref關鍵字的功能很相似,out關鍵字的作用是導致參數通過應用來傳遞。它們的區別就是ref關鍵要求變數必須在傳遞之前進行初始化,而out不需要。
下面通過一個小例子來學習一下out關鍵字。
View Code using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ref1
{
class Program
{
static int MaxIndex(int[] m_Array, out int m_Index)
{
m_Index = 0;
int m_Max = m_Array[0];
for (int i = 0; i < m_Array.Length; i++)
{
if (m_Array[i] > m_Max)
{
m_Max = m_Array[i];
m_Index = i;
}
}
return m_Max;
}
static void Main(string[] args)
{
int[] Array = new int[5] { 12, 22, 55, 44, 11 };
int maxIndex;//使用out關鍵字,此處不用初始化
Console.WriteLine("數組中最大的值是:{0}", MaxIndex(Array, out maxIndex));
Console.WriteLine("最大數的索引號是:{0}", maxIndex + 1);
Console.ReadLine();
}
}
}
運行結果如下:
其實上面的程式也可以使用ref關鍵字來寫:
View Code using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ref1
{
class Program
{
/// <summary>
/// 返回數組的最大值及最大值的索引值
/// </summary>
/// <param name="m_Array">數組</param>
/// <param name="m_Index">最大值的索引值</param>
/// <returns>數組中的最大值</returns>
static int MaxIndex(int[] m_Array, ref int m_Index)
{
m_Index = 0;
int m_Max = m_Array[0];
for (int i = 0; i < m_Array.Length; i++)
{
if (m_Array[i] > m_Max)
{
m_Max = m_Array[i];
m_Index = i;
}
}
return m_Max;
}
static void Main(string[] args)
{
int[] Array = new int[5] { 12, 22, 55, 44, 11 };
int maxIndex=0;//使用ref關鍵字,此處需要初始化
Console.WriteLine("數組中最大的值是:{0}", MaxIndex(Array, ref maxIndex));
Console.WriteLine("最大數的索引號是:{0}", maxIndex + 1);
Console.ReadLine();
}
}
}
運行結果如下: