恰好我之前寫了一系列介紹 C 語言的文章,介紹了什么是指針,以及為什么要使用指針,下面摘錄一部分,感興趣的話,可以點我了解更多。
什么是 C語言指針?
不同的數據類型的主要區別在于占用的存儲空間不同。我們知道,C 程序是運行在計算機的內存中的,因此 C 程序的變量也是存在于內存中的。C 標準規定 char 類型占用一個字節的存儲空間,對其他整型卻沒有做規定,現在為了解釋的方便,我們假設 int 類型的數據占用內存 4 個字節。
假設我們如下定義了兩個變量:signed char i = 3;int j = 8;
那么,i 占用了 1 字節的內存空間,j 占用了 4 字節的內存空間,請看下圖。
方框表示內存空間,內部表示存儲的值。我們把內存逐字節編號,方框外部的數字表示方框的編號(這樣的內存“編號”即所謂的“內存地址”)。修改變量 i 的值,實際上就是修改地址為 4000 的內存空間里的值。那反過來呢?如果我修改了地址為 4000 的內存空間里的值,i 的值會相應改變嗎?答案是肯定的,請繼續往下看。
上圖中的內存地址“4000”是我為了解釋方便隨意取的。那么,在實際應用中,變量 i 的地址如何獲取呢?C 語言提供了“&”運算符,就是獲取變量地址的。請看下面的例子:#include int main()
{
signed char i = 3;
int j = 8;
long p1 = (long)&i;
printf('p1: %ld ', p1);
return 0;
}
我們取出了 i 的地址,把它強制轉換為 long 型(關于強制類型轉換,可參考上一節),傳遞給 p1 了。編譯執行,發現變量 i 的地址被打印出來了。這說明,C 程序變量的地址也是一個整數。
按照上面的說法,修改 i 的值除了直接對 i 賦值以外,還可以通過修改 p1 地址處的內存空間里的數值。那,怎樣才能“通過修改 p1 地址處的內存空間里的數值”修改 i 的值呢?
上面的代碼實例中,我們使用了 long 型變量 p1 存儲了 i 的地址。事實上,C 語言有專門的數據類型存儲地址,定義方式也很簡單,就是:“類型描述符 * ”,例如,可以定義以下變量存儲地址:signed char *p1 = &i;int *p2 = &j;
p1 和 p2 就是 C 語言中所謂的指針類型,因為 i 是 signed char 類型的,所以定義了 signed char * 類型的指針存儲 i 的地址。j 是 int 類型的,所以定義了 int * 類型的指針存儲 j 的地址。另外,C 語言提供了“&”運算符取變量地址,與之對應的,還提供了“ * ”運算符從相應地址內存里取出數值。
好了,了解了 C 語言的指針類型和“ * ”運算符,現在來看看如何“通過修改 p1 地址處的內存空間里的數值”修改 i 的值。請看如下代碼:signed char *p1 = &i;
*p1 = 5;
printf('i=%d ', i);
編譯運行,發現程序輸出“i=5”,這樣我們就實現了“通過修改 p1 地址處的內存空間里的數值”修改 i 的值。在定義變量時,” * “放在變量符號前,可以定義指針變量。在定義完指針變量后,“ * ”放在變量前,就表示從地址取值的運算符了。另外,“ * ”還可以表示乘法運算符,讀者自己思考什么情況下,“ * ”表示乘法運算符。
以上的操作,實際上就是 C 語言的指針操作,可以看出它一點也不神秘,接下來幾節,我們將繼續討論 C 語言的指針,比如為什么 int 類型的變量 j 的地址要使用 int* p2; 定義,而不能使用 signed char* p2; 定義,使用指針為何能寫出緊湊、高效的 C 程序等等。
為什么要使用指針?
在開始討論為什么使用 C 語言指針之前,先介紹一下復雜點的指針,這是新知識,也是鋪墊。不想看鋪墊可以往后翻一翻。
前面幾節介紹了 C 語言中指針,也討論了數組指針和指針數組的區別,但歸根結底,至今我們說的都是基礎數據類型定義的指針,C 語言有復合數據類型,那么它有復合數據類型的指針嗎?答案是肯定的,事實上,在 C 語言中復合類型指針的使用相當廣泛。
先來看看結構體指針。還是從實例出發,我們定義一個結構體類型,它有兩個成員,分別是 sleep_time(睡覺時間) 和 work_time(工作時間),然后定義這種結構體類型的變量和指針:struct week{
double sleep_time;
double work_time;
};
struct week w;
struct week *pw = &w;
可以通過結構體指針 pw 訪問 week 結構體的成員:(*pw).sleep_time = 7.0;
這樣寫有點麻煩,因此 C 語言非常貼心的提供了“->”運算符,所以我們還可以這樣通過結構體指針訪問成員:pw->sleep_time = 7.0;
為什么要使用 C 語言中的指針
好了,現在我們已經知道 C 語言中的結構體指針怎么使用了,鋪墊完了。但是,明明使用結構體變量 w 就能很好的讀寫 week 結構體啊,為什么要用結構體指針呢?這不是麻煩了嗎?為什么要使用結構體指針,其實可以延伸到“為什么要使用指針”,本節將以結構體指針為例討論一下這個問題。
是的,僅僅訪問 week 的 sleep_time 成員,只使用結構體變量 w 就足夠了,再通過 pw 訪問真的麻煩了。但是工具會不會帶來方便,要看我們怎么使用,不能因為高射炮打蚊子不方便就說高射炮沒用。恰當的使用結構體指針,有利于我們寫出更加緊湊,效率更高的 C 程序。
一周有五天工作日,兩天周末,一般來說,在工作日(weekday),人們的睡覺時間較短,工作時間較長,所以我們定義 weekday 函數來規劃工作日的時間:void weekday(struct week wd)
{
wd.sleep_time = 7.0; // 7 小時
wd.work_time = 8.5; // 8.5 小時
}
而在周末(weekend)則反過來,工作間較短,睡覺時間較長,所以我們定義 weekend 函數來規劃周末的時間:void weekend(struct week we)
{
we.sleep_time = 9.0; // 9 小時
we.work_time = 2.5; // 2.5 小時
}
這兩個函數很好的規劃了一周的睡覺和工作時間,但是卻并不好用。為什么呢?因為它倆只在自己內部規劃了,我們外界看不到啊!想在 main 函數把規劃好的時間打印出來都辦不到,因為它倆在自己內部規劃好以后,就把“規劃書”銷毀了。這里把 weekday 和 weekend 函數的局部變量比作“規劃書”,函數退出后,局部變量就自動銷毀了。可以參考《c語言入門5,一文徹底弄懂函數的形參和實參》一節。
可能你會說,那我可以把“規劃書”返回給 main 函數啊,讓 weekday 和 weekend 函數有返回值就可以了:struct week weekday(struct week wd)
{
wd.sleep_time = 7.0; // 7 小時
wd.work_time = 8.5; // 8.5 小時
return wd;
}
struct week weekend(struct week we)
{
we.sleep_time = 9.0; // 9 小時
we.work_time = 2.5; // 2.5 小時
return we;
}
int main()
{
struct week w;
w = weekday(w);
printf('weekday, sleep time: %0.1f, work time: %0.1f ', w.sleep_time, w.work_time);
weekend(w);
printf('weekend, sleep time: %0.1f, work time: %0.1f ', w.sleep_time, w.work_time);
return 0;
}
是的,這的確是一個解決問題的辦法,main 可以把 weekday 和 weekend 函數的“規劃書”打印出來了。
但是這種解決問題的辦法有一點臃腫,很多程序員把這樣的代碼稱為“不優雅”的代碼。你看,main 現在有一份空的“規劃書”,需要 weekday 和 weekend 函數處理。weekday 和 weekend 函數能處理,但是它們要復制一份“規劃書”回到自己內部做,這種復制就造成了空間浪費。此外,weekday 和 weekend 函數做完了規劃書,還要把“規劃書”再從自己內部取出,return 給 main,這就有時間浪費。
更節約資源,更有效率的做法是:weekday 和 weekend 函數處理這份“規劃書”時,直接處理 main 里的“規劃書”就可以了。不要復制后再處理,完事了還要在從自己內部傳出。那,weekday 和 weekend 函數應該怎么修改呢?請看:void weekday(struct week *wd)
{
wd->sleep_time = 7.0; // 7 小時
wd->work_time = 8.5; // 8.5 小時
}
void weekend(struct week *we)
{ we->sleep_time = 9.0; // 9 小時
we->work_time = 2.5; // 2.5 小時
}
int main()
{
struct week w;
weekday(&w);
printf('weekday, sleep time: %0.1f, work time: %0.1f ', w.sleep_time, w.work_time);
weekend(&w);
printf('weekend, sleep time: %0.1f, work time: %0.1f ', w.sleep_time, w.work_time);
return 0;
}
看到了沒,利用指針,整個 C 代碼簡潔多了。weekday 和 weekend 函數接收到的參數都是 main 里結構體變量 w 的地址,所以它倆都是直接操作 w 的。這樣就不用在自己的棧幀里復制一份 w 再處理了,也不用在處理完畢還要 return 給 main 了。
看到這里,你可能會說,什么嘛,不就是用指針代替了結構體做參數嗎?指針說不定比結構體還要耗空間呢!對嗎?一起來看下:結構體變量 w,它占用內存至少兩個 sizeof(double) 的空間(一個 double 型數據通常占用 8 字節空間)。而一個指針,不管它是什么類型的,在大多數 32 位計算機中,它只占 4 字節空間,在大多數 64 位計算機中,它也僅僅占 8 字節空間。所以使用指針做 weekday 和 weekend 函數的參數,在空間上,絕對是比直接使用 week 結構體節約空間的,何況指針還提升了效率,簡潔了代碼。如果是一個 char 型變量,它只占用 1 字節空間,這時使用指針的確更浪費空間。但是如果是一個非常復雜的結構體,它占用的內存空間甚至達幾千字節,這時使用指針就非常節約空間了。所以說,工具是死的,人是活的。
到這里,相信你已經了解 C 語言指針在節約空間,提升程序效率方面的作用了。事實上,這里我們介紹的僅僅是指針的冰山一角,在以后的文章里,你會愈發覺得 C 語言指針的強大的。
歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。