要學會各種參數,重點是理解參數參數傳遞的內存原理。理解了內存,各種參數使用的作用、效果等便可以自己分析出來,而不用死記硬背。
1.按值傳遞參數(值參數)
值參數是最常見的一種參數,也很好判斷。在方法聲明與調用時,參數前不加ref或out關鍵字的參數,便是按值傳遞。
值參數是將實參變量在“棧”中存儲的值復制一份副本,將副本傳遞給方法的形參。
按值傳遞參數又分為兩種不同情況:傳遞值類型,與傳遞引用類型。
(注意這里的值類型、引用類型是數據類型,而值參數、引用參數是參數傳遞方式。二者不是同一概念,不可混淆。)
(1)傳遞值類型參數
示例:
1 class Program 2 { 3 static void Main(String[] args) 4 { 5 int a = 4; 6 int b = 5; 7 Add(a, b); 8 Console.WriteLine($"a={a},b={b}"); 9 } 10 public static void Add(int x, int y) 11 { 12 x += 1; 13 y += 1; 14 } 15 }
輸出為:
a=4,b=5
內存原理:在Add方法中對形參x與y的操作,並不會對實參a與b的值產生影響,因為在Add方法中操作的是a與b的副本。
(2)傳遞引用類型參數
示例1:
1 class Program 2 { 3 static void Main(String[] args) 4 { 5 Animal a = new Animal() { Age = 1 }; 6 Add(a); 7 Console.WriteLine($"a.Age={a.Age}"); 8 } 9 public static void Add(Animal animal) 10 { 11 animal.Age += 1; 12 } 13 } 14 class Animal 15 { 16 public int Age { get; set; } 17 }
輸出為:
a.Age=2
內存原理:
在Add方法中形參animal拿到了實參a在棧中的副本,副本中存儲着對象的地址。所以,animal與a指向了同一個對象。
這時操作的不是副本,而是副本所指的對象,即操作a所指對象。
示例2:
1 class Program 2 { 3 static void Main(String[] args) 4 { 5 Animal a = new Animal() { Age = 1 }; 6 Add(a); 7 Console.WriteLine($"a.Age={a.Age}"); 8 } 9 public static void Add(Animal animal) 10 { 11 animal = new Animal() { Age = 10 }; 12 animal.Age += 1; 13 } 14 } 15 class Animal 16 { 17 public int Age { get; set; } 18 }
輸出為:
a.Age=1
內存原理:
與示例1不同的是,在示例2中Add方法中改變了animal所指向的對象,指向一個new的Animal對象,此時a仍指向原對象,這時再對animal進行操作,
便不再會影響實參a所指的對象。
2.按引用傳遞參數(引用參數)
形參與實參之前,都要加ref關鍵字。
需要注意的是,實參在使用之前必須賦值,否則編譯器會報錯。(如下圖)
此時不再復制實參在棧中的副本,而是將實參在棧中的地址傳給形參,也就是實參與形參共用棧中的值。此時在方法中對形參所做的任何操作,都會影響實參。
按引用傳遞也分為值類型和引用類型兩種情況。
(1)傳遞值類型參數
示例:
1 class Program 2 { 3 static void Main(String[] args) 4 { 5 int a = 4; 6 int b = 5; 7 Add(ref a, ref b); 8 Console.WriteLine($"a={a},b={b}"); 9 } 10 public static void Add(ref int x, ref int y) 11 { 12 x += 1; 13 y += 1; 14 } 15 }
輸出為:
a=5,b=6
內存原理:
由於a與x,b與y指向了棧中相同的地址,在Add方法中對形參x與y的操作,會影響實參a與b的值。
(2)傳遞引用類型參數
示例1:
1 class Program 2 { 3 static void Main(String[] args) 4 { 5 Animal a = new Animal() { Age = 1 }; 6 Add(ref a); 7 Console.WriteLine($"a.Age={a.Age}"); 8 } 9 public static void Add(ref Animal animal) 10 { 11 animal.Age += 1; 12 } 13 } 14 class Animal 15 { 16 public int Age { get; set; } 17 }
輸出為:
a.Age=2
內存原理:
這時得到的效果,與按值傳遞參數是一樣的,但內存原理卻不相同。
這時Add方法中形參animal指向的不再是實參a在棧中的副本的地址,而是與a指向了棧中相同的位置。
示例2:
1 class Program 2 { 3 static void Main(String[] args) 4 { 5 Animal a = new Animal() { Age = 1 }; 6 Add(ref a); 7 Console.WriteLine($"a.Age={a.Age}"); 8 } 9 public static void Add(ref Animal animal) 10 { 11 animal = new Animal() { Age = 10 }; 12 animal.Age += 1; 13 } 14 } 15 class Animal 16 { 17 public int Age { get; set; } 18 }
輸出為:
a.Age=11
內存原理:
這個示例便可以看出與按值傳遞時示例2的不同。
Add方法中改變了animal所指向的對象,指向一個new的Animal對象,此時a所指向的對象也隨之改變。這是由於形參animal
與實參a指向的是棧中相同的地址(這個地址中存儲的是對象在堆中的地址)。當棧中存儲的堆地址改變時,animal與a會同時指向新的對象。
3.輸出參數
用法:
(1)形參與實參之前,都要加out關鍵字。
(2)輸出參數主要是用於函數需要有多個返回值時,作為返回值使用。
與引用變量相同的是:
(1)輸出參數也不復制實參在棧中的副本,而是將實參在棧中的地址傳給形參。在這點上,輸出參數與引用參數相同。
(2)實參必須是可以賦值的變量,而不能是常亮。
與引用參數不同的是:
(1)實參在使用之前不必賦值。
(2)事實上,在使用之前對實參的賦值沒有任何意義,因為在調用方法的最開始,便會將其值抹去,使其成為未賦值的變量。
(3)在方法返回之前,必須對out參數進行賦值。
由以上特點所決定的是,輸出參數無法向方法中傳遞信息,其唯一作用便是,當一個參數需要返回多個值時,作為返回值返回。
示例1:
1 class Program 2 { 3 static void Main(String[] args) 4 { 5 int a; 6 Add(1, 2, out a); 7 Console.WriteLine(a); 8 } 9 public static bool Add(int x, int y, out int result) 10 { 11 result = x + y; 12 return true; 13 } 14 }
輸出為:
3
引用類型的輸出參數同理,請自行編寫示例代碼。
請繼續閱讀參數學習的下篇:C#基礎之參數(二) 數組參數、可選參數與命名參數
