第 24 講:指針(一):指針的概念
指針(Pointer),名字很奇怪,不過很容易理解:指針本身存儲的并非數(shù)值,而是某個變量的內(nèi)存地址。換句話說,這個變量存儲的是你家門牌號,幾棟幾單元等等信息,而不是真正意義的可提供計算的數(shù)據(jù)。
為了引入指針的概念,我們需要先介紹一個 C# 里新的概念:不安全代碼塊。
Part 1 不安全代碼塊
在 C# 里,因為指針本身會引起很復(fù)雜且很嚴(yán)重的內(nèi)存錯誤的問題,因此 C# 團隊為了避免你直接使用指針,發(fā)明了一種新的機制:你需要先允許項目可以使用指針,才可以添加指針的相關(guān)代碼。在 C# 里,使用的那些指針相關(guān)代碼,叫做不安全代碼(Unsafe Code)。
下面我們來說一下,如何讓項目啟用不安全代碼的相關(guān)配置。
和之前配置“啟用算術(shù)溢出”的操作類似,我們也是在解決方案資源管理器里選擇項目,然后點擊右鍵選擇“Properties”(屬性),進(jìn)入項目配置頁面。
然后,找到“Build”(生成)選項卡,找到啟用不安全代碼的選項“Allow unsafe code”(啟用不安全代碼),打勾即可。

就可以了。
如果我們在代碼里使用指針,我們需要用到兩個運算符:*v
和 &v
。
為了表明 p
是指針變量,我們使用 int*
類型表達(dá)“它是用來存儲 int
變量的地址用的”。p
此時是一個 int
類型的指針變量,存儲的是 a
變量的內(nèi)存地址,也就是內(nèi)存里,“a
的數(shù)值到底放哪里”的信息。
間接地址運算符(簡稱間址運算符),寫成 *p
的方式來獲取“這個存儲的門牌號對應(yīng)的那么門戶里的數(shù)值”:
p
變量來取得 30 的結(jié)果。這就是指針變量的用法。需要注意的是,指針變量的類型“int*
”,前面的這個 int
表明了指針本身存儲的變量的數(shù)據(jù)類型;換句話說,你不能拿一個 float*
的指針變量存儲一個 int
變量的地址。
另外請你注意,你在寫代碼的時候,需要把指針的代碼嵌入到 unsafe
包裹起來的大括號里。和 unchecked
和 checked
用法是差不多的。只是 unsafe
是表示一個代碼塊,因此它不能用于表達(dá)式,只能用在代碼塊上:
我們使用這樣的格式來允許程序使用不安全的代碼。首先,int a = 30;
是正常的賦值語句,只有 int *p = &a;
和 Console.WriteLine(*p);
里使用到了指針,因此我們只需要把它們兩個語句框起來,用 unsafe
當(dāng)然,為了代碼的靈活性,也不是不可以把 int a = 30;
放進(jìn) unsafe
代碼塊里。這看你習(xí)慣。
另外,為了避免你寫代碼的時候?qū)哟翁?,你依然可以在寫代碼的時候,把 unsafe
寫到 Main
的簽名上:
Part 3 指針作參數(shù)
如果參數(shù)是帶指針的變量呢?我們可以使用指針來模擬 ref
和 out
參數(shù)。
然后調(diào)用方:
指針的用法就是傳入地址的形式,以在 Swap
方法里使用到 Main
里的 a
和 b
。這個語法雖然不同,但從語義上講,它就和 ref
out
參數(shù)也是一樣的道理。
調(diào)用方:
ref
和 out
參數(shù)我們都能完成。但是我們?yōu)槭裁催€需要用 ref
和 out
來代替指針呢?
因為 ref
和 out
的語義更強,編譯器知道這里的參數(shù)是傳入引用和輸出用的,因此編譯器就知道怎么去處理和幫助你寫代碼:比如 ref
參數(shù),因為在調(diào)用方和方法里使用相同的變量,因此我們無法避免方法里使用變量的數(shù)值信息;因此編譯器必須讓你為 ref
參數(shù)給定了初始賦值才能傳入;另外一方面,因為你用 out
修飾參數(shù),因此編譯器知道你這里的參數(shù)是輸出用的,因此在方法沒有為參數(shù)賦值的時候,編譯器就會產(chǎn)生錯誤信息告訴你,必須給這個變量賦值才能繼續(xù)使用方法。這兩點,指針本身是無法確定具體用途的,因此編譯器本身也不知道怎么幫助你寫代碼。
Part 4 無類型指針
C# 還允許無類型指針。無類型指針指的是我們?yōu)榱思嫒萑魏晤愋偷闹羔槻女a(chǎn)生的寫法。我們使用 void*
表示這個類型。
比如這個寫法下,pa
、pb
、pc
和 pd
四個變量都是 void*