- C#入門經典(第7版):C# 6.0 & Visual Studio 2015(.NET開發經典名著)
- (美)Beijamin Perkins Jacob Vibe Hammer Jon D. Reid
- 2857字
- 2021-04-02 21:18:38
6.2 變量的作用域
在上一節中,讀者可能想知道為什么需要利用函數交換數據。原因是C#中的變量僅能從代碼的本地作用域訪問。給定的變量有一個作用域,在這個作用域外是不能訪問該變量的。
變量的作用域是一個重要主題,最好用一個示例加以說明。下面的示例將演示在一個作用域中定義變量,但試圖在另一個作用域中使用該變量的情形。
試一試:變量的作用域:Ch06Ex01\Program.cs
(1)對Ch06Ex01中的Program.cs進行如下修改:
class Program
{
static void Write()
{
WriteLine($"myString = {myString}");
}
static void Main(string[] args)
{
string myString = "String defined in Main()";
Write();
ReadKey();
}
}
(2)編譯代碼,注意顯示在任務列表中的錯誤和警告:
The name 'myString' does not exist in the current context The variable 'myString' is assigned but its value is never used
示例說明
什么地方出錯了?不能在Write()函數中訪問在應用程序主體(Main()函數)中定義的變量myString。
原因在于變量是有作用域的,在相應作用域中,變量才是有效的。這個作用域包括定義變量的代碼塊和直接嵌套在其中的代碼塊。函數中的代碼塊與調用它們的代碼塊是不同的。在Write()中,沒有定義myString,在Main()中定義的myString則超出了作用域—— 它只能在Main()中使用。
實際上,在Write()中可以有一個完全獨立的變量myString。修改代碼,如下所示:
class Program { static void Write() { string myString = "String defined in Write()"; WriteLine("Now in Write()"); WriteLine($"myString = {myString}"); } static void Main(string[] args) { string myString = "String defined in Main()"; Write(); WriteLine("\nNow in Main()"); WriteLine($"myString = {myString}"); ReadKey(); } }
這段代碼就可以編譯,輸出結果如圖6-4所示。

圖6-4
這段代碼執行的操作如下:
● Main()定義和初始化字符串變量myString。
● Main()把控制權傳送給Write()。
● Write()定義和初始化字符串變量myString,它與Main()中定義的myString變量完全不同。
● Write()把一個字符串輸出到控制臺,該字符串包含在Write()中定義的myString的值。
● Write()把控制權傳送回Main()。
● Main()把一個字符串輸出到控制臺,該字符串包含在Main()中定義的myString的值。
其作用域以這種方式覆蓋一個函數的變量稱為局部變量。還有一種全局變量,其作用域可覆蓋多個函數。修改代碼,如下所示:
class Program { static string myString; static void Write() { string myString = "String defined in Write()"; WriteLine("Now in Write()"); WriteLine($"Local myString = {myString}"); WriteLine($"Global myString = {Program.myString}"); } static void Main(string[] args) { string myString = "String defined in Main()"; Program.myString = "Global string"; Write(); WriteLine("\nNow in Main()"); WriteLine($"Local myString = {myString}"); WriteLine($"Global myString = {Program.myString}"); ReadKey(); } }
執行結果如圖6-5所示。

圖6-5
這里添加了另一個變量myString,這次進一步加深了代碼中的名稱層次。這個變量定義如下:
static string myString;
注意,這里也需要static關鍵字。在此類控制臺應用程序中,必須使用static或const關鍵字來定義這種形式的全局變量。如果要修改全局變量的值,就需要使用static,因為const禁止修改變量的值。
為區分這個變量和Main()與Write()中的同名局部變量,必須用一個完整限定的名稱為變量名分類,參見第3章。這里把全局變量稱為Program.myString。注意,只有在全局變量和局部變量同名時,才需要這么做。如果沒有局部myString變量,就可以使用myString表示全局變量,而不需要使用Program.myString。如果局部變量和全局變量同名,會屏蔽全局變量。
全局變量的值在Main()中設置如下:
Program.myString = "Global string";
全局變量在Write()中可以通過如下語句訪問:
WriteLine($"Global myString = {Program.myString}");
為什么不能使用這個技術通過函數交換數據,而要使用前面介紹的參數來交換數據?有時,這確實是一種交換數據的首選方式,例如編寫一個對象,用作插件,或者在較大項目中使用的短腳本。但許多情況下不應使用這種方式。使用全局變量的最常見問題與并發性的管理相關。例如,可以編寫一個全局變量來讀取一個類的眾多方法或讀取不同的線程。如果大量的線程和方法可以寫入全局變量,能確定全局變量中的值是有效數據嗎?沒有額外的同步代碼,就不能確定。此外,全局變量的真正意圖可能被遺忘,以后因為其他原因再次使用它。因此,是否使用全局變量取決于函數的用途。
使用全局變量的問題在于,它們通常不適合于“常規用途”的函數—— 這些函數能處理我們所提供的任意數據,而不僅限于處理特定全局變量中的數據。詳見本章后面的內容。
6.2.1 其他結構中變量的作用域
上一節的一個要點不是只與函數之間的變量作用域有關:變量的作用域包含定義它們的代碼塊和直接嵌套在其中的代碼塊。接下來要討論的代碼可在本章下載文件的VariableScopeInLoops\Program.cs中找到。這一點也適用于其他代碼塊,例如分支和循環結構的代碼塊。考慮下面的代碼:
int i; for (i = 0; i < 10; i++) { string text = "Line " + Convert.ToString(i); WriteLine($"{text}"); } WriteLine($"Last text output in loop: {text}");
字符串變量text是for循環的局部變量,這段代碼不能編譯,因為在該循環外部調用的WriteLine()試圖使用該字符串變量,但是在循環外部該字符串變量會超出作用域。修改代碼,如下所示:
int i;
string text;
for (i = 0; i < 10; i++)
{
text = "Line " + Convert.ToString(i);
WriteLine($"{text}");
}
WriteLine($"Last text output in loop: {text}");
這段代碼也會失敗,原因是必須在使用變量前對其進行聲明和初始化,但text只在for循環中初始化。由于沒有在循環外進行初始化,賦給text的值在循環塊退出時就丟失了。但可以進行如下修改:
int i; string text = ""; for (i = 0; i < 10; i++) { text = "Line " + Convert.ToString(i); WriteLine($"{text}"); } WriteLine($"Last text output in loop: {text}");
這次text是在循環外部初始化的,可以訪問它的值。這段簡單代碼的執行結果如圖6-6所示。

圖6-6
在循環中最后賦給text的值可以在循環外部訪問。可以看出,這個主題的內容需要花一點時間來掌握。在前面的示例中,循環之前將空字符串賦給text,而在循環之后的代碼中,text就不會是空字符串了,其原因可能一下子看不出來。
這種情況的解釋涉及分配給text變量的內存空間,實際上任何變量都是這樣。只聲明一個簡單變量類型,并不會引起其他變化。只有在給變量賦值后,這個值才會被分配一塊內存空間。如果這種分配內存空間的行為在循環中發生,該值實際上定義為一個局部值,在循環外部會超出其作用域。
即使變量本身未局部化到循環上,其包含的值卻會局部化到該循環上。但在循環外部賦值可以確保該值是主體代碼的局部值,在循環內部它仍處于其作用域中。這意味著變量在退出主體代碼塊之前是沒有超出作用域的,所以可在循環外部訪問它的值。
幸好,C#編譯器可檢測變量作用域的問題,根據它生成的錯誤信息修正程序有助于我們理解變量的作用域問題。
6.2.2 參數和返回值與全局數據
本節將詳細介紹如何通過全局數據以及參數和返回值與函數交換數據。首先分析下面的代碼:
class Program { static void ShowDouble(ref int val) { val *= 2; WriteLine($"val doubled = {val}"); } static void Main(string[] args) { int val = 5; WriteLine($"val = {val}"); ShowDouble(ref val); WriteLine($"val = {val}"); } }
注意:這段代碼與本章前面的代碼稍有不同,在前面的示例中,在Main()中使用了變量名myNumber,這說明局部變量可以具有相同的名稱,且不會相互干涉。
和下面的代碼比較:
class Program { static int val; static void ShowDouble() { val *= 2; WriteLine($"val doubled = {val}"); } static void Main(string[] args) { val = 5; WriteLine($"val = {val}"); ShowDouble(); WriteLine($"val = {val}"); } }
這兩個ShowDouble()函數的結果是相同的。
使用哪種方法并沒有什么硬性規定,這兩種方法都十分有效,但需要考慮一些規則。
首先,在第一次討論這個問題時就提到過,使用全局值的ShowDouble()版本只使用全局變量val。為使用這個版本,必須使用這個全局變量。這會對該函數的靈活性有輕微的限制,如果要存儲結果,就必須總是把這個全局變量值復制到其他變量中。另外,全局數據可能在應用程序的其他地方被代碼修改,這會導致預料不到的結果(其值可能會改變,等我們認識到這一點時為時已晚)。
當然,也可以說,這種簡化實際上使代碼更難理解。顯式指定參數可以一眼看出發生了什么改變。例如對于FunctionName(val1, out val2)函數調用,馬上就可以知道val1和val2都是要考慮的重要變量,在函數執行完畢后,會為val2賦予一個新值。反之,如果這個函數不帶參數,就不能對它處理了什么數據做任何假設。
總之,可以自由選擇使用哪種技術來交換數據。一般情況下,最好使用參數,而不使用全局數據,但有時使用全局數據更合適,使用這種技術并沒有錯。
- 解構產品經理:互聯網產品策劃入門寶典
- 高效微控制器C語言編程
- 零基礎學Scratch少兒編程:小學課本中的Scratch創意編程
- Clojure for Domain:specific Languages
- Java加密與解密的藝術(第2版)
- Learning Concurrent Programming in Scala
- 智能手機APP UI設計與應用任務教程
- Access 2010數據庫應用技術實驗指導與習題選解(第2版)
- C++ Application Development with Code:Blocks
- Flink入門與實戰
- Go Systems Programming
- Unity 5 Game Optimization
- Apache Kafka 1.0 Cookbook
- Three.js Essentials
- 高性能MVVM框架的設計與實現:San