值類型和引用類型:
C#數據類型分為兩大類:值類型和引用類型。
值類型數據主要有:結構體struct,枚舉體enum,布爾型bool,浮點型,整型。
引用類型數據主要有:數組,字符串,接口,委托,類。
值類型和引用類型的區別:
引用類型繼承自System.Object,值類型繼承自System.ValueType。
引用類型保存到內存的堆heap中,值類型保存在內存的堆棧stack中。在.net中,棧的內存是自動釋放的,而堆會有垃圾回收器GC來釋放。
引用類型可以派生出新的類型,而值類型不可以。引用類型可以包含null值,而值類型不可以。
引用類型變量賦值只復制對象的引用,不復制對象本身。而將一個值類型變量賦給另一個值類型變量時,將復制包含的值。
下面一個小例子可以簡單說明值類型和引用類型:
class PointR { public int x, y; } struct PointV { public int a, b; } class Program { static void Main(string[] args) { //給一個引用類型賦值將復制到一個對象的引用,而給一個值類型賦值將復制一個對象的值 PointR r; PointV v; r = new PointR(); v = new PointV(); r.x = 7; v.a = 7; PointR pr = r; PointV pv = v; pr.x = 9; pv.a = 9; Console.WriteLine(r.x);//9 Console.WriteLine(v.a);//7 Console.Read(); } }
C#參數傳遞:
.net默認的是通過值傳送變量,但是也可以迫使值參數通過引用傳送給方法。C#要求對傳遞給方法的參數進行初始化。在傳遞給方法之前,無論時按值傳遞,還是按引用傳遞,變量都必須初始化。 ref關鍵字:可以迫使值參數通過引用傳送給方法。即:在.net中,如果把一個參數傳遞給方法,且這個方法的輸入參數前帶有ref關鍵字,則該方法對變量所做的任何改變都會影響原來對象的值。下面一個小例子說明按值傳遞和ref參數傳遞:
class Program { //默認值,C#的參數是按值傳遞的,這也是最常見的情況 static void Method1(int p) { ++p; } //為了按引用傳遞,C#提供了參數修飾字ref,。ref修飾字要求變量在傳遞給方法之前必須賦值 static void Method2(ref int p) { ++p; } static void Main(string[] args) { int x = 9; int y = 9; Method1(x); Method2(ref y); Console.WriteLine(x);//9 Console.WriteLine(y);//10 Console.Read(); } }
在方法的輸入參數前面加上out關鍵字時,傳遞給該方法的變量可以不初始化。該變量通過引用傳遞,所以在從被調用的方法中返回時,方法對該變量進行的任何改變都會保留下來。在調用該方法時,還需要使用out關鍵字,這與在定義該方法時一樣。並且out修飾字要求變量在從方法返回時必須賦值。舉例說明:
class Program { //out修飾字要求變量在從方法返回時必須賦值 static void Split(string name, out string firstName, out string lastName) { int i = name.LastIndexOf(' '); firstName = name.Substring(0, i); lastName = name.Substring(i + 1); } static void Main(string[] args) { string a, b; Split("zhou enlai", out a, out b); Console.WriteLine("{0}+ {1}", a, b);//zhou+ erlai Console.Read(); } }
還有一個params修飾傳遞參數,不多說,直接舉例:
class Program { //params 修飾字可以使用在方法的最后一個參數上,這樣方法就可以接受任意數目的某種類型的參數 static int Add(params int[] arr) { int sum = 0; foreach (int i in arr) { sum += i; } return sum; } static void Main(string[] args) { int i = Add(1, 1, 2, 3, 4, 5, 6); Console.WriteLine(i);//22 Console.Read(); } }
補充:
起因是網上看到一句話:java里面的參數傳遞都是按值傳遞的。不理解,於是查詢資料:
java數據類型分類兩大類:基本類型和對象類型,相應的,變量也分為基本類型和引用類型。
基本類型數據有:byte\short\int\long\float\double\char\boolean
整數類型:byte是8位字節,范圍是-128-127,short是16位的,int是32位,long是64位
浮點數:float是32位,1位符號位,7位指數,23位有效尾數;double是64位,1位符號位,11位指數,52位有效尾數;
字符:char是16位字節
布爾類型:boolean。
基本類型的變量保存本身的數值,“引用值”則保存內存空間地址,代表了對某個對象的引用,而不是對象本身。
基本類型的變量在聲明時則由系統自動分配內存空間,不能包含null;而引用類型的變量在聲明時僅僅是對變量分配了引用空間,而不分配數據空間,可以賦予null值。
值傳遞:
值傳遞在進行方法調用時,實際參數把它的值傳遞給對應的形參,函數接收的形參數組其實是實參數值的拷貝,對形參進行修改不會影響實際參數的數值。
引用傳遞:
引用傳遞在進行方法調用時,實際參數把它對對象的引用傳遞給形參變量,而不是將對象傳遞給形參變量,在進行方法調用過程中,實參變量和形參變量具有相同的數值,都是指向同一塊內存地址,對形參數值進行改變,改變的是內存地址中的實際對象,形參變量本身的引用數值不會得到改變。
此處需要考慮String、Integer、Double等基本基本類型包裝類,它們都是immutable類型,因為它們都沒有自身修改操作的函數,所以對它們的每次操作都是重新創建一個對象,因此可以將它們當作基本類型使用,在參數傳遞時可以按照值傳遞來考慮。
綜合,上文說的java都是按值傳遞的,可以這么理解,這個值傳遞其實是實參地址的拷貝,得到這個拷貝地址后,你可以通過它修改這個地址的內容,因為此時這個內容的地址和原地址是同一個地址,但是你不能修改這個地址本身使其重新引用其它的對象,所以也可以說成是指傳遞。但同時因為形參的引用地址和實參的引用地址是相同的,因此對形參的修改也會影響實參變量。
說明:不管是對象、基本類型還是對象數組、基本類型數組,在函數中都不能改變其實際地址但能改變其中的內容。
這里加入一張網上拷貝的圖片便於理解:
關於值傳遞和引用傳遞,網上還有這么一種說法:其實都是對“=”的理解
1、= 是賦值操作(任何包含=的如+=、-=、 /=等等,都內含了賦值操作)。不再是你以前理解的數學含義了,而+ - * /和 = 在java中更不是一個級別,換句話說, = 是一個動作,一個可以改變內存狀態的操作,一個可以改變變量的符號,而+ - * /卻不會。這里的賦值操作其實是包含了兩個意思:1、放棄了原有的值或引用;2得到了 = 右側變量的值或引用。Java中對 = 的理解很重要啊!!可惜好多人忽略了,或者理解了卻沒深思過。
2、對於基本數據類型變量,= 操作是完整地復制了變量的值。換句話說,“=之后,你我已無關聯”;至於基本數據類型,就不在這科普了。基本類型:Byte\Short\int\long\float\double\char\boolean.
3、對於非基本數據類型變量,= 操作是復制了變量的引用。換句話說,“嘿,= 左側的變量,你丫別給我瞎動!咱倆現在是一根繩上的螞蚱,除非你再被 = 一次放棄現有的引用!!上面說了 = 是一個動作,所以我把 = 當作動詞用啦!”。而非基本數據類型變量你基本上可以
4、參數本身是變量,參數傳遞本質就是一種 = 操作。參數是變量,所有我們對變量的操作、變量能有的行為,參數都有。所以把C語言里參數是傳值啊、傳指針啊的那套理論全忘掉,參數傳遞就是 = 操作。