程式設計 博碩文化出版發行
第七章 位址與指標 課前指引 位址,就好像是記憶體中的地址一樣。在記憶體中,每一個位元組 (byte) 都有一個記憶體編號,正如同是現實生活中的地址一樣。 指標就是記錄變數位址的工具,可以直接依據其指定之位址來存取變數。指標也可以用於動態配置一維陣列、二維陣列等等,使得空間的運用更加有效。
章節大綱 7-1 認識位址 7-4 指標與字串 7-2 多重指標 7-5 動態配置記憶體 7-3 指標的運算 備註:可依進度點選小節
7-1 認識位址 指標的功用 是一種變數型態,其內容就是記憶體的地址。各位可以把身份證號碼當成變數的位址,當各位有了身份證號碼,自然就可以知道該位人士的個人資料(變數內容)了。 透過指標變數,應用程式可以直接存取該指標變數所指定的位址之內容。基本上,利用指標,可以直接存取記憶體,增加其便利性。另外,如果在撰寫程式的時候並不能預估程式執行時需要多少記憶體、多少變數等等資訊,還可以使用動態配置。透過指標變數記錄系統給定的位址在哪邊,才能完成動態配置的工作。
7-1 認識位址 變數位址的存取 在 C 語言中,為了要針對位址與指標進行運算,特別定義了指標變數的形式與存取變數位址的方式。例如當需要使用某個資料時,就存取哪一個位址的記憶體空間內容即可。如果要了解變數所在記憶體位址,可以透過 &(取址運算子)來求出變數所在的位址,語法格式如下: &變數名稱;
7-1 認識位址 範例CH07_01.c /* %p控制碼之使用 */
7-1 認識位址 執行結果 程式解說 第6~7行分別宣告兩種不同型態的變數,第11、13行則以%p格式來表示16進位的位址,如果要取出變數的位址只要在變數前加上&運算子即可。通常我們並不用直接處理記憶體位址的問題,因為變數就已經包括了記憶體位址的資訊,它會直接告訴程式,應該到記憶體中的何處取出數值。
7-1 認識位址 陣列元素位址的存取 基本上,編譯器對程式所宣告的變數或陣列都會分配記憶體空間,以作為儲存資料使用。因此在程式中需要取得陣列元素的位址時,可以使用&(取址運算子)來取得該陣列元素的位址。
7-1 認識位址 範例CH07_02.c /* 「&」作用於一維陣列元素 */
7-1 認識位址 執行結果 程式解說 從第11、13行的輸出結果可以看出,陣列元素每移動一次索引值,其實是在記憶體位移4個位元組(因為整數型態),才取出陣列的下一筆資料。
7-1 認識位址 範例CH07_03.c /* 「&」作用於二維陣列元素 */
7-1 認識位址 執行結果 程式解說 第6行定義定義整數二維陣列,並直接設定初使值。 第11行我們利用for迴圈輸出陣列元素值及第14行輸出該元素在記憶體中的位址,各位可從位址的數值間發現二維陣列在記憶體中的儲存關係仍是以線性的方式來處理。
7-1 認識位址 指標變數 宣告指標時,首先必須定義指標的資料型態,並於資料型態後加上「*」字號(稱為取值運算子或反參考運算子),再給予指標名稱,即可宣告一個指標變數。「*」的功用可取得指標所指向變數的內容。指標變數宣告方式如下: 資料型態 *指標名稱; 或 資料型態* 指標名稱;
7-1 認識位址 指標變數 指標變數也不能指向不同資料型態的變數。以下是幾個指標變數的宣告方式,當然指標變數宣告時也可設定初值為0或是NULL來增加可讀性: int* x; int *x, *y; int *x=0; int *y=NULL;
7-1 認識位址 指標變數 在指標宣告之後,如果沒有指定其初值,則指標所指向的記憶體位址將是未知的,您不能對未初始化的指標進行存取,因為它可能指向一個正在使用的記憶體位址。要指定指標的值,可以使用&取址運算子將變數所指向的記憶體位址指定給指標,如下所示: 資料型態 *指標變數; 指標變數=&變數名稱; /* 變數名稱已定義或宣告 */
7-1 認識位址 指標變數 如下指令中,將指標變數address1指向一個已宣告的整數變數num1: 此外,也不能直接將指標變數的初始值設定為數值,這樣會造成指標變數指向不合法位址。例如: int num1 = 10; int *address1; address1 = &num1; int* piVal=10; /* 不合法指令 */
7-1 認識位址 範例CH07_04.c
7-1 認識位址 執行結果 程式解說 第11行將a1位址指定給指標變數p1。 第12行將a2位址指定給指標變數p2。 第15~17行輸出p1、p2、p3與*p1、*p2、*p3的值。
7-1 認識位址 範例CH07_05.c
7-1 認識位址 範例CH07_05.c
7-1 認識位址 執行結果 程式解說 第12~14行將p1、p2、p3分別指向整數變數a1、a2與a3 第20行重新設定a1的值為101,第24行中可以看出同樣指向a1的*p1值也會同步改為101。 第21行重新設定*p2的值為103,第25行中可以看出a2的值也會同步改為103。 第22行將p3指向p2,所以*p3的值就是*p2的值,但a3的值仍為71,並未改變。
7-2 多重指標 雙重指標 是指向指標的指標,通常是以兩個 * 表示,也就是 「**」。事實上,雙重指標並不是一個困難的概念。只要想像原本的指標是指向基本資料型態,例如整數、浮點數等等。而現在的雙重指標一樣是一個指標,只是它指向目標是另一個指標。雙重指標的語法格式如下: 資料型態 **指標變數名稱;
7-2 多重指標 雙重指標 以下我們利用一個範例說明,假設整數 a1 設定為 10,指標 ptr1 指向 a,而指標 ptr2 指向 ptr1。則程式碼如下所示: int a1=10; /*設定基本整數值a為10*/ int *ptr1, **ptr2; /*整數指標 ptr1 與雙重指標ptr2*/ ptr1=&a1; /* 將a1位址指定給ptr1 */ ptr2=&ptr1; /* 將ptr1位址指定給雙重指標ptr2 */
7-2 多重指標 雙重指標 至於整數 a1、指標 ptr1,與指標 ptr2 之間的關係,上述的程式碼可以由下圖來加以說明:
7-2 多重指標 範例CH07_06.c
7-2 多重指標 執行結果 程式解說 第9行ptr1指向a1的位址,第10行ptr2指向ptr1的位址。 第12~13行中各位可以發現&a1的位址和ptr是一樣的,而*ptr的值也和a1相同。 第13~14行中&ptr1和ptr2相同,ptr1與*ptr2一樣,*ptr1與**ptr2相同。
7-2 多重指標 多重指標 雙重指標就是指向指標的指標;而三重指標就是指向「雙重指標」的指標,語法格式為: 在此我們仍然延續上一小節的範例,假設整數 a1 設定為 10,指標 ptr1 指向 a,而指標 ptr2 指向 ptr1,而指標 ptr3 指向 ptr2。則程式碼如下所示: 資料型態 ***指標變數名稱; int a1=10; /*設定基本整數值a為10*/ int *ptr1, **ptr2; /*整數指標 ptr1 與雙重指標ptr2*/ int ***ptr3; /*三重指標 ptr3*/ ptr1=&a1; /*將a1位址指定給ptr1*/ ptr2=&ptr1; /*將ptr1位址指定給雙重指標ptr2*/ ptr3=&ptr2; /*將ptr2位址指定給雙重指標ptr3*/
7-2 多重指標 多重指標 使用 ***ptr3 則可存取 a 變數的內容,所以 ***ptr3 之值即為 10。如右圖所示:
7-2 多重指標 範例CH07_07.c
7-2 多重指標 執行結果
7-2 多重指標 程式解說 第10行ptr1是指向a1的指標。 第11行ptr2是指向ptr1的整數型態雙重指標。 第16行ptr2所存放的內容為ptr1的位址(&ptr1),而*ptr2即為ptr1所存放的內容。 各位可將**ptr2看成*(*ptr2),也就是*(ptr1),因此**ptr2=*ptr1=10。 第17行ptr3所存放的內容為ptr2的位址(&ptr2),而*ptr3即為ptr2所存放的內容,另外**ptr3即為*ptr2所存放的內容,至於***ptr2看成*(**ptr2),因此***ptr3=**ptr2=10。
7-3 指標的運算 指標的加法或減法運算,只能針對常數值(如+1或-1)來進行,不可以做指標變數之間的相互運算。 因為指標變數內容只是存放位址,而位址間的運算並沒有任何意義,而且容易讓指標變數指向不合法位址。
7-3 指標的運算 遞增與遞減運算 由於不同的變數型態,在記憶體中所佔空間也不同,所以當指標變數加一或減一時,是以指標變數的宣告型態其所佔記憶體大小為單位,來決定向右或向左移動多少單位。例如以下程式碼表示一個整數指標變數,名稱為piVal,當指標宣告時所取得iVal的位址值為0x2004,之後piVal作遞增(++)運算,其值將改變為0x2008: int iVal=10; int* piVal=&iVal; /* piVal=0x2004 */ piVal++; /* piVal=0x2008 */
7-3 指標的運算 範例 CH07_08.c
7-3 指標的運算 執行結果
7-3 指標的運算 程式解說 第6行宣告整數型態指標。 第7行初始化指標,並給予合法位址。 第10行輸出最初的int_ptr位址。 第11行執行int_ptr++的遞增運算,可以發現第13行中輸出的int_ptr位址向右移動4個位元組。 第14行執行int_ptr--的遞減運算,可以發現第16行中輸出的int_ptr位址又向左移動4個位元組。 第17行執行int_ptr=int_ptr+2的加法運算,可以發現第19行中輸出的int_ptr位址又向右移動4*2個位元組。 第20行執行int_ptr=int_ptr-2的減法運算,可以發現第21行中輸出的int_ptr位址又向左移動4*2個位元組。
7-3 指標的運算 指標常數與陣列 指標運算的應用,也經常使用在陣列存取的操作上。例如在7-1-2節中,我們介紹過了使用&(取址運算子)來取得該陣列元素的位址。假設程式中宣告一個陣列 int array[5],就是要求系統尋覓一塊連續的記憶體區段,能夠儲存 5 個整數型態的資料之連續記憶體空間。 陣列名稱 array 就是指向這塊連續記憶體空間的起始位址,而「索引值」其實就是其它元素相對於第一個元素的記憶體位址之「位移量」(offset)。
7-3 指標的運算 範例CH07_09.c
7-3 指標的運算 執行結果 程式解說 第9行中可以發現指標 array 所指向的位址就是陣列的開頭點,亦即 array[0] 的位址(可以用 &array[0] 表示)。 第11~14行中可發現4個元素的位址都分別和上一個元素相差 4,那是因為一個整數在記憶體中佔了 4 個 byte。
7-3 指標的運算 範例CH07_10.c
7-3 指標的運算 執行結果 程式解說 第9行輸出陣列與兩種指標常數的替代運算,從執行結果中您可以看到,對於int資料型態來說,每加1則位址位移4位元組。 13行則分別以兩種指標常數方式與*運算子來存取陣列內的元素值。
7-3 指標的運算 範例CH07_11.c
7-3 指標的運算 執行結果
7-3 指標的運算 程式解說 第12行中是輸出使用「&」取址運算子取得二維陣列元素位址與指標常數來表示二維陣列元素位址,並可發現要取得元素no[i][j]的記憶體位址,則要使用*(no+i)+j來取得。 第15行利用雙重指標方式來列印arr[i][j]元素值。
7-3 指標的運算 指標變數與陣列 撰寫C程式碼時,各位不但可以把陣列名稱直接當成一種指標常數來運作,也可以將指標變數指到陣列的起始位址,並藉由指標變數來間接存取陣列中的元素值。例如有關指標變數取得一維陣列位址的方式如下: 資料型態 *指標變數=陣列名稱; 或 資料型態 *指標變數=&陣列名稱[0];
7-3 指標的運算 範例CH07_12.c
7-3 指標的運算 執行結果 程式解說 第10行使用指標變數ptr指向陣列常數arr。 第15、17行輸出arr+i的值與ptr+i的值,兩者顯示的位址是相同的。
7-3 指標的運算 範例CH07_13.c
7-3 指標的運算 執行結果 程式解說 第7行宣告並設定二維陣列arr。 第12~17行利用for迴圈及*(ptr+i*3+j)公式來輸出arr[i][j]
7-4 指標與字串 指標變數設定字串 利用指標變數設定字串,則是以字元指標變數指向字串,宣告格式如下: char *指標變數="字串內容"; 例如: char *ptr = "How are you ?";
7-4 指標與字串 範例CH07_14.c
7-4 指標與字串 執行結果 程式解說 第7行以字元陣列宣告,第8行將指標變數指向字串"Please input your name:"。 第10行輸出number的資料值,在此不用加上「*」號。
7-4 指標與字串 範例CH07_15.c
7-4 指標與字串 執行結果 程式解說 第7行以字串指標宣告,並設定其值為"President"。 第9行字串指標的加1運算式,這是指標移動到原來字串的第2個字元。 第10行輸出此字串的第一個字元。第11行輸出執行加1運算後的新字串。
7-4 指標與字串 指標陣列 每個指標陣列中的元素都是一個指標變數,而元素值則為指向其它資料型態變數的位址。以下是一維指標陣列的宣告格式: 一維指標陣列的應用特別在儲存字串上相當實用。例如之前介紹使用二維字元陣列,當一個字串陣列的宣告方式如下所示: 資料型態 *陣列名稱[元素名稱]; /* 陣列名稱前加上* 運算子 */ char name[4][11] = { "apple", "watermelon", "Banana", "orange" };
7-4 指標與字串 範例CH07_16.c
7-4 指標與字串 執行結果 程式解說 第6行宣告了指標陣列name,並將每個元素指向不同長度的字串。 第9~10行利用for迴圈來輸出指標name[i]所指向的字串。
7-4 指標與字串 範例 CH07_17.c
7-4 指標與字串 執行結果 程式解說 第6行宣告一維指標字串陣列。 第8行宣告二維字元陣列。 第14~15行列印指標陣列的name[i]儲存結果與位址。 第20~21行列印二維字元陣列name1[i]儲存結果與位址。
7-5 動態配置記憶體 動態配置一般變數 動態配置變數的方式如下,如果n=1,即表示一個變數: 資料型態* 指標名稱=(資料型態*)malloc(sizeof(資料型態)*n);
7-5 動態配置記憶體 範例CH07_18.c
7-5 動態配置記憶體 執行結果 程式解說 第6行宣告浮點數指標piF,並將指標指向浮點數動態配置記憶空間。 第8行自行輸入piVal的值。 第12行輸出piF所指向的位址內容。第14行將指標piF的空間釋放。
7-5 動態配置記憶體 動態配置一維陣列 通常各位將資料宣告為陣列時,必須於編譯階段即確定陣列長度,但這樣很容易產生記憶體的浪費或無法滿足程式所需。這時就可採用動態配置陣列的方式。例如動態配置一維陣列的方式如下,n=陣列長度: 資料型態* 指標名稱=(資料型態*)malloc(n*sizeof(資料型態));
7-5 動態配置記憶體 動態配置一維陣列 當執行時期動態配置的一維陣列不需要時,可以將其釋放,釋放動態配置的一維陣列的方式如下: 例如依整數資料型態動態配置一個長度為8個元素的連續整數陣列記憶體空間,如下所示: free(指標名稱); int* piArrVal=(int*)malloc(8*sizeof(int)); /*指標變數指向動態配置的記憶空間*/ … free(piArrVal); /*釋放此陣列的記憶體*/
7-5 動態配置記憶體 範例CH07_19.c
7-5 動態配置記憶體 範例CH07_19.c
7-5 動態配置記憶體 執行結果 程式解說 第6行宣告學生成績陣列指標grades。 第12行請輸入欲產生的動態一維陣列個數n。 第13行將整數指標指向動態配置一維陣列記憶空間。 第18~22行利用for迴圈輸入學生成績,並於第21行利用sum變數累加成績。 第31行釋放指標指向的記憶空間。
7-5 動態配置記憶體 範例 CH07_20.c
7-5 動態配置記憶體 執行結果
7-5 動態配置記憶體 程式解說 第7行宣告動態陣列指標。 第10行動態宣告5個整數元素的陣列。 第15行透過 sizeof ()函數來針對靜態陣列取得陣列大小資訊。 第16行透過 sizeof ()函數來針對動態陣列取得陣列大小資訊。 第20行透過dynamic_array++ 的運算,便可以獲得與 dynamic+1 相同的效果,並能針對 dynamic_array 所指向之位址遞增,其遞增量仍是該陣列之單位元素大小,也就是 4 個位元組。
7-5 動態配置記憶體 動態配置字串 字串其實就是字元陣列,若在程式執行時無法得知字串長度,也就是字元陣列元素個數的話,也可以使用動態陣列進行字串的配置。 當以動態指標配置一維整數陣列時,並無法以 sizeof ()函數來求得該陣列之大小。不過對於字串而言,卻可以使用 strlen 函數取得字串之長度。相關格式如下:
7-5 動態配置記憶體 範例CH07_21.c
7-5 動態配置記憶體 執行結果 程式解說 第3行使用到strlen()函數,因此要含括string.h檔。
7-5 動態配置記憶體 動態配置多維陣列 動態配置多維陣列與一維陣列的宣告方式類似,差異點在於多維陣列需要由第一維逐一配置記憶體至第n維。 例如宣告一個n*m的二維陣列動態配置記憶體,就可以利用雙重指標來配置第一維部份的記憶體,方式如下: 資料型態** 指標名稱=(資料型態**)malloc(陣列長度n*sizeof(資料型態*));
7-5 動態配置記憶體 動態配置多維陣列 在配置動態一維整數陣列時,使用的是「指向整數的指標」。現在配置二維陣列時,可以將二維陣列看成有「多個一維整數陣列」,因此,需要一個「指向『整數指標』的指標」來達成。方式如下: 指標名稱[0]=(資料型態*)malloc(m*sizeof(資料型態)); 指標名稱[1]=(資料型態*)malloc(m*sizeof(資料型態)); 指標名稱[2]=(資料型態*)malloc(m*sizeof(資料型態)); … 指標名稱[m-1]=(資料型態*)malloc(m*sizeof(資料型態));
7-5 動態配置記憶體 範例CH07_22.c
7-5 動態配置記憶體 範例CH07_22.c
7-5 動態配置記憶體 執行結果 程式解說 第6行宣告字元雙重指標。 第11、13行輸出此動態配置陣列的列數和欄數。 第16行使用 star 雙重指標配置一個具有 row個元素的一維陣列。 第18行個別產生一個具有col 個元素的一維陣列。 第25~27行輸出此二維陣列的內容。
7-5 動態配置記憶體 範例CH07_23.c
7-5 動態配置記憶體 範例CH07_23.c
7-5 動態配置記憶體 執行結果 程式解說 第10~12行宣告二維字元陣列儲存12個月份的名稱。 第14行以雙重指標動態配置12個字串。 第17行以指標動態配置10個字元。 第21~23行輸出此動態配置字串陣列的所有元素值。
7-5 動態配置記憶體 範例CH07_24.c
7-5 動態配置記憶體 範例CH07_24.c
7-5 動態配置記憶體 執行結果
7-5 動態配置記憶體 程式解說 第3行因為要使用strlen()函數,所以含括string.h標頭檔。 第11~13行宣告二維字元陣列儲存12月份的名稱。 第15行動態配置12個字串。 第21行動態配置該字串長度+1的字元。 第23行要將字串資料指定給另一個字串變數,並須使用strlen()函數才可。
7-5 動態配置記憶體 通用型態指標 在 C 語言中也有通用型態指標,用來指定特定記憶體位址,但不指定資料型態。這樣的指標可以透過轉型(cast)的方式將所指的記憶體位址轉成各種資料型態。通用型態指標的語法如下: void *p;
本章結束 Q&A討論時間