1. 程式人生 > 實用技巧 >C語言第九章指標

C語言第九章指標

上節回顧

普通變數作為函式引數是按值呼叫的,實參和形參地址不一樣,形參值改變不會影響實參。

陣列作為函式引數是按地址低用的,實參和形引數組共享同一段記憶體空間

從陣列開始,從簡單的資料結構到複雜的資料結構

本章主要內容:

C語言的高效主要就是指標(pointer)的作用

很多“mission impossible”都可以由指標來完成

比如,執行結果和原始碼一模一樣。

強轉和指標,並稱C語言的兩大神器

C語言很多錯誤都是由於陣列和指標引起的非法記憶體訪問導致的

變數的記憶體地址

記憶體中的每個位元組都有唯一的編號(地址)

地址按位元組編號,其字長一般與主機相同

32位機器使用32位地址,的最多支援2^32位元組記憶體(4G)

地址是一個無符號整數,從0開始,依次遞增。在表達和交流時,通常把地址寫成十六進位制數。

例:使用取地址運算子取出變數的地址,然後將其顯示在螢幕上。

%p表輸出變數的地址值

scanf("%d", &a) ----&a 直接定址:按變數的地址直接訪問

以下程式會發生什麼情況?

1 int i;
2 
3 scanf("%d",i);

i的值被當做地址,如i的值為100,則輸出的整數就會從地址100開始寫入記憶體。

1 char c;
2 scanf("%d", &c);

輸入資料以int的二進位制形式寫到c所在的記憶體空間

c所佔記憶體不足以放下一個int,其後所佔的空間也被覆蓋。

間接定址:通過存放變數地址的其他變數訪問該變數。

用什麼型別的變數來存放地址呢?

-指標(pointer)型別

指標變數-具有指標型別的變數

變數的指標←→變數的地址

指標變數的定義和初始化

儲存32位地址值的指標變數佔4個位元組的記憶體,儲存了一個地址,但是隻知道這些是不夠的的,往下讀變數的值佔用幾個位元組不知道

從這個地址開始多少個位元組內的資料是有效的呢?

指標的基型別就是回答這個問題的

例:使用指標變數在螢幕上顯示變數的地址。

指標變數指向的資料型別,稱為基型別。

指標變數使用之前一定要初始化,永遠不要使用沒有初始化的指標變數。普通變數沒有初始化訪問的話可能也就是一串亂碼。

如果開始的時候初始化不知道指向什麼,就將指標變數初始化為NULL

何為空指標?

  值為NULL的指標,即無效指標

既然0(NULL)用來表示空指標,那麼空指標就是地址為0的單元的指標嗎?

  不一定。每個C編譯器都被允許用不同的方式來表示空指標;但並非所有的編譯器都使用0地址。某些編譯器為空指標使用不存在的記憶體地址。

  硬體會檢查出這種試圖通過空指標訪問記憶體的方式

* - 取內容。*p 即取p指向的記憶體中儲存的內容

指標變數只能指向同一基型別的變數

例3 使用指標變數,通過間接定址輸出變數的值

指標的解引用(pointer dereference):引用指標指向的變數的值 *pa

按值呼叫和按地址呼叫

int *p; 基型別

char *pc;

為什麼要通過指標來間接定址?

比如主函式在呼叫另外一個函式的時候,定義一個指標變數來儲存變數的地址。這是指標非常重要的一個應用之一。

用指標變數作為函式引數。

普通變數作函式引數--按值呼叫

  實參的值不隨形參值的改變而改變

形參 <-實參變數的值

指標做函式引數-按地址呼叫

  為了在被調函式中修改其無法直接訪問的實參的值

指標形參 <-實參變數的地址

演示一下有什麼區別?

例9.5:函式傳遞變數的值,形參值的改變不影響對應的實參

例9.6:指標變數作為形參,函式傳遞變數的地址,可以修改變數的值。

演示按值呼叫

通過返回值可以修改變數的值,但是return方式只能返回一個值,多個值只能用形參傳遞方式

例9.6 編寫函式實現兩數的互換。

這個程式無法實現兩數交換的功能,為什麼呢?

用普通變數作引數的時候,實現了形參x和y的交換,但是實參a和b並沒有互換。交換的只是形參的值,而形參的值在函式執行結束後就釋放了。

另外一個用指標變數作為引數,函式是傳地址呼叫,對引數進行解引用,*X是a的值,*Y代表b的值,交換之後,*X變為9,*Y變為5,即a變為9,b變為5.

嘗試一下,呼叫的時候將函式引數改為實參,把取地址符號去掉,會發生什麼情況?

這個時候就會體現出寫函式原型的作用,編譯器會自動檢查實參型別與形參型別是否匹配。

某些編譯器檢查實參和形參的資料型別是否匹配,不匹配則給出警告。但是有的編譯器會直接傳給形參,會導致非法訪問記憶體。

使用陣列完成兩數的交換

這種使用陣列的方式必須保證這兩個形引數據型別要相同。

下面這種方式編譯的時候就會報錯。指標pTemp未初始化,指標pTemp指向哪裡未知,對未知單元寫操作是很危險的,不能借助一個未初始化的指標變數進行兩數交換。

下面這種方式會出錯嗎?這種方式交換的是地址值,並沒有實現a和b的值。這個也是錯誤的。

例9.7 計算並輸出最高分及相應學生的學號

第一種方式:使用陣列的方式,返回陣列的下標,從而獲取陣列元素

第二種方式:直接返回對應的陣列元素

該程式使用普通引數形參來返回得分和學號的值,但是形參的值改變不會影響實參,雖然在函式中可以得到最高分和學號,呼叫完最後不會傳給主函式

直接呼叫函式,在VC上報出警告:區域性變數maxScore和maxNum沒有被初始化就呼叫。

maxScore和maxNum被傳遞給形參的時候必須是被賦值的,在沒有呼叫函式的時候,這兩個值是隨機的,隨機的值傳給形參,雖然在呼叫的函式中計算完,但是沒有反向傳給maxScore和maxNum。所以檢測出來這兩個變數沒有被初始化,並不是直接指出這個錯誤所在。真正原因:普通變數作函式引數按值呼叫,不能在被調函式中改變相應實參的值。

怎麼修改呢?將形參改為指標變數型別,呼叫的時候,取變數的地址傳給形參來間接訪問變數值,在函式呼叫時解引用。

下面介紹指標變數的另外一個應用,就是將指標變數指向一個函式,即所謂的函式指標。

上節回顧:剛才我們講了一個指標的重要應用,即指標變數作為函式引數。

什麼情況下使用指標變數?就是在被調函式中沒辦法訪問修改主調函式的值,那隻能是間接的修改它 ,即通過將要修改的主調函式引數的地址值作為形參,被調函式得到引數的地址值後就可以通過指標的解引用間接的訪問修改主調函式中的變數值。

指標另外一個應用:編寫通用的函式。需要通過函式指標來實現。

函式指標(Function Pointer):指向函式的指標變數

  資料型別 (*指標變數名)(形參列表);

例 int (*f)(int a, int b); 表示函式指標f指向的函式原型為 int 函式名(int a, int b);

假設存在Fun函式

int Fun(int a, int b);

f = Fun; // Fun代表編譯器為函式分配的入口地址。

令f = Fun,就是讓f指向函式fun()

編譯器將不帶()的函式名解釋為改函式的入口地址

函式指標變數儲存的是函式在記憶體中的入口地址。

常見錯誤:
  1、忘了寫前一個() 即: int *f(int a, int b); 表示聲明瞭一個函式名為f,返回值為整型指標型別的函式。

2、忘了寫後一個()即:int (*f); 表示定義了一個整型指標變數

  3、定義是的引數型別(實型)與指向的函式引數型別(整型)不匹配,int (*f)(float a, float b);

例:函式指標的應用,求最大值與最小值兩數之和。

函式指標的應用是編寫通用性很強的函式:一是計算定積分,二是編寫通用的排序函式

1、計算定積分

當需要計算函式為f1時:

當需要計算的函式為f2....時

可以發現,以上方法不同函式的定積分計算需要分別定義不同的函式。要想通過一個函式來解決不同函式的定積分,那就要用到函式指標。

增加一個函式指標作為形參,解引用方式通過函式指標呼叫它所指向的函式,呼叫同一個函式實現了不同函式定積分的計算,只是第一個實參發生了變化。

2、編寫通用排序

例9.8:修改例8.8中的排序函式,使其能夠實現對學生成績的升序排序,又能實現對學生成績的降序排序

不使用函式指標程式設計
只能通過編寫兩個排序函式(升序和降序)

主函式:

採用選擇法排序:這兩個函式基本上都相同,就是大於號小於號的區別

最後兩個函式

使用函式指標程式設計

通用的交換法排序函式:

可以使用剛剛的兩數交換函式實現,即Swap(&a[i], &a[k]);一定要加上取地址運算子。

本章

一種特殊型別的變數,指標是地址是一種不嚴謹的說法,指標是一種資料型別。

指標變數:指標型別的變數,指標型別的變數儲存特殊的資料,即地址。

指標變數與普通變數的共性:

1、都是在記憶體中佔據一定大小的記憶體單元,對於指標變數而言,在32位機器中,佔用4個位元組的記憶體,按基型別理解地址單元中的資料,從這個地址開始讀幾個位元組看它的基型別。

2、都需要先定義後使用,尤其對於指標變數而言,一定要先初始化,如果不確定指向 哪裡,先讓它指向NULL

指標變數的特殊性

1、指標變數中儲存的內容只能是地址(變數或者函式的地址)

2、只能指向同一基型別的變數

3、可參與的運算型別有限,加減整數,自增自減,關係運算、賦值運算

定義指標變數的基型別目的:明確了指標指向的記憶體單元可以存放的資料型別,從這個地址開始幾個位元組的單元是有效的,可以存放什麼型別資料。

初始化的目的:明確指標變數具體指向了哪裡。

使用指標變數的基本原則:

1、明確指向哪裡 -- 初始化

2、明確指向單元的內容 基型別

3、永遠不要使用未初始化--的指標變數

4、一個(xx型)的指標指向(xx型別)的變數

指標的一個重要應用就是作為函式引數,向函式換地變數或函式的地址。一種是指標變數作為函式引數,一種是函式指標對位函式引數。

指向變數的指標,作函式引數

  -按地址呼叫,傳遞變數在記憶體中的地址(用取地址運算子)

  -被調函式根據改地址讀寫它不能直接訪問的變數的值(用間接定址運算子,指標的解引用)

函式指標作為引數,可以編寫一個通用的函式,傳遞的是函式在記憶體中的入口地址,可以通過解引用來呼叫函式,實現不同的函式地址呼叫不同的函式。

指標還有其他方面的應用,後面章節介紹。