C#學習( ref和out、堆和棧、列舉)
一、值型別和引用型別
1、值型別直接儲存其值,而引用型別儲存對其值的引用。
引用型別:基類為Objcet
值型別:均隱式派生自System.ValueType
2、值和引用型別儲存
- 值型別變數聲明後,不管是否已經賦值,編譯器為其分配記憶體。
- 引用型別當宣告一個類時,只在棧中分配一小片記憶體用於容納一個地址,而此時並沒有為其分配堆上的記憶體空間。當使用 new 建立一個類的例項時,分配堆上的空間,並把堆上空間的地址儲存到棧上分配的小片空間中。
- 值型別的例項通常是線上程棧上分配的(靜態分配),但是在某些情形下可以儲存在堆中。
- 引用型別的物件總是在程序堆中分配(動態分配)。
3、引用型別
陣列(派生於System.Array)
類:class(派生於System.Object);
4、值型別
C#的所有值型別均隱式派生自System.ValueType:結構體:struct(直接派生於System.ValueType)
5、裝箱和拆箱
1、裝箱:裝箱就是將值型別轉化為引用型別的過程。
裝箱過程:
- 在託管堆中分配好記憶體,分配的記憶體量是值型別的各個欄位需要的記憶體量加上託管堆上所以物件的兩個額外成員(型別物件指標,同步塊索引)需要的記憶體量
- 值型別的欄位複製到新分配的堆記憶體中
- 返回物件的地址,這個地址就是這個物件的引用
2、拆箱:將已裝箱的值型別例項轉化成值型別的過程。
二、ref和out
通常向方法傳遞一個實參時,對應的引數(形參)會用實參的一個副本來初始化。
1、ref
如果為一個引數(形參)附加一個ref關鍵字作為字首,該引數就會成為一個實參的一個別名(或對實參的一個引用),而不再是一個實參一個副本。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { int a = 6; int b = 66; Fun(ref a,ref b); Console.WriteLine("a:{0},b:{1}", a, b);//輸出:72和6說明傳入Fun方法是a和b的引用 } static void Fun(ref int a, ref int b) { a = a+b; //72,說明Main方法的a和b的值傳進來了 b = 6; } } }
通過以上例子,形參的操作改變了實參的值。 ref修飾的是引用型別,而不是值型別,也就是說所儲存的是地址。
2、out
out與ref的用法類似,不過out可以向方法傳遞一個未賦初值的實參。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int a=100;
int b;
Fun(out a, out b);
Console.WriteLine("a:{0},b:{1}", a, b);//輸出:3和1說明out引數傳遞進去的是a和b的引用,輸出3說明a的引數值沒有傳入Fun方法中
}
static void Fun(out int a, out int b)
{
a = 1+2;
b = 1;
}
}
}
3、兩者區別:ref有進有出,out無進有出
三、堆和棧
計算機使用記憶體來容納要執行的程式以及這些程式要使用的資料。作業系統和執行時通常將用於容納資料的記憶體劃分為兩個獨立的區域,每個區域都採用不同的方式進行管理。這兩個區域通常稱為堆和棧。
參考:https://blog.csdn.net/taoerit/article/details/53420684
1、呼叫一個方法時,它的引數以及它的區域性變數需要記憶體總是從棧中獲取。方法結束後,為引數和區域性變數分配的記憶體將自動歸還棧。
使用new關鍵字來建立的一個物件,構造物件所需要的記憶體總是從堆中獲取。
2、棧記憶體就像是一系列堆疊越高的箱子,呼叫方法時,它的每個引數(或區域性變數)都被放進一個箱子,並將這個箱子放到棧的最頂部。方法結束之後,方法的所有箱子都會從棧中移除。
堆記憶體則像散佈在房間裡的一大堆箱子。每個箱子都有一個標籤,它標記了這個箱子是否正在使用。建立一個新物件時,“執行時”會查詢一個空箱子,並把它分配給物件。當最後一個引用消失,執行時就將箱子標記為“未使用”。將來某個時候,會清除箱子裡的東西,使之能被真正重用。
3、棧和堆中主要放置了四種類型的資料:值型別(Value Type),引用型別(Reference Type),指標(Pointer),指令(Instruction)。
(1)值型別:
bool 、byte 、char 、decimal 、double 、enum 、float 、int 、long 、sbyte 、short 、struct 、uint 、ulong 、ushort
(2)引用型別
class 、 interface 、delegate 、object 、string
(3)指標
(4)指令
四、列舉
列舉是由一組特定常量構成的一組資料結構,是值型別的一種特殊形式,當需要一個由指定常量集合組成的資料型別時,使用列舉型別。列舉宣告可以顯式地宣告 byte、sbyte、short、ushort、int、uint、long 或 ulong 型別作為對應的基礎型別。沒有顯式地宣告基礎型別的列舉宣告意味著所對應的基礎型別是 int。
1、格式
enum <enum_name>{ enumeration list };
enum_name指定列舉的型別名稱,enumeration list是一個用逗號分隔的識別符號列表。列舉列表中的每個符號代表一個整數值,一個比它前面的符號大的整數值。預設情況下,第一個列舉符號的值是 0.
列舉的取值型別:byte、sbyte、short、ushort、int、uint、long 、ulong
2、注意
列舉型別只針對字串,對於索引,無意義。
常量的集合,這些常量只能取值,不能賦值。
用常量表示所引用的字串,這樣可以省去重複寫入長字串 。
3、舉例
例如,假設你需要定義一個變數,其值表示一週內的某一天。 該變數只會儲存七個有意義的值。 若要定義這些值,可以使用列舉型別,該型別是使用 enum 關鍵字宣告的。
宣告:
enum Day { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };
enum Month : byte { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec };
使用列舉
Day today = Day.Monday;
int dayNumber =(int)today;
Console.WriteLine("{0} is day number #{1}.", today, dayNumber);
Month thisMonth = Month.Dec;
byte monthNumber = (byte)thisMonth;
Console.WriteLine("{0} is month number #{1}.", thisMonth, monthNumber);
// Output:
// Monday is day number #1.
// Dec is month number #11.
如果未為列舉器列表中的元素指定值,則值將自動按 1 遞增。在上例中,Day.Sunday
的值為 0,Day.Monday
的值為 1,依此類推。