C 語言初學教材 - 第六章 雙重指標

重新看一次這張圖




這是說,若我們想在函數 addfriend() 中修改儲存在記憶體位址 address_a 的變數值,我們就得把 address_a 作為參數值傳遞給 addfriend() 。


可是傳遞記憶體位址需要指標變數, startPtr 不就已經是指標變數了嗎?的確, startPtr 就已經是指標變數,我們也需要 startPtr 這個指標變數來記錄資料結構的起點,不然加入新資料後,我們不知道能夠從哪裡找到資料。


因此 startPtr 為鏈結串列的入口,若是要用其他函數如 addfriend() 建立新節點,然後將新節點拿來當作 startPtr 的初值,那就必須傳遞 startPtr 的記憶體位址給函數。 startPtr 的記憶體仍是指標,不過是指向指標的指標,也就是雙重指標,宣告時需要連用兩個星號
void addfriend(LinkedListNode **startPtr);
void printList(LinkedListNode *currentPtr);


我們將 addfriend() 的參數宣告為雙重指標,函數定義如下
void addfriend(LinkedListNode **startPtr)
{
    LinkedListNode *newPtr, *currentPtr;
    char fname[NAME_SIZE];
    int fage, fsex, frelation;

    // 向作業系統要求新的記憶體空間
    newPtr = malloc(sizeof(LinkedListNode));
        
    // 依序輸入好友資料
    printf("\n好友暱稱: ");
    scanf("%s", fname);
    printf("好友年齡: ");
    scanf("%d", &fage);
    printf("好友性別 - 0.女 1.男: ");
    scanf("%d", &fsex);
    printf("好友關係 - 0.家人 1.同學 2.朋友: ");
    scanf("%d", &frelation); 
        
    // 將好友資料拷貝到剛才取得的記憶體空間之中
    strcpy(newPtr->data.name, fname);
    newPtr->data.age = fage;
    newPtr->data.sex = fsex;
    newPtr->data.relation = frelation;
    newPtr->nextPtr = NULL;
        
    // 將資料加入鏈結串列
    if (*startPtr == NULL) {
        *startPtr = newPtr;
    }
    else {
        currentPtr = *startPtr;
           
        while (currentPtr != NULL) {
           if (currentPtr->nextPtr == NULL) {
               currentPtr->nextPtr = newPtr;
               break;
           }
                       
           currentPtr = currentPtr->nextPtr;
        }
    }
}


注意,凡是使用到 startPtr 的地方,全部都需要加上反參考運算子,也就是 *startPtr 的形式,這樣才能修改原先在函數 main() 中的值



至於 printList() 則不需要用到雙重指標,因為直接傳遞 startPtr 的值即可,若是 startPtr 為 NULL ,表示空的鏈結串列,若 startPtr 儲存任何記憶體位址,就表示鏈結串列中有資料,才需要做印出的動作,其定義如下
void printList2(LinkedListNode *currentPtr)
{
    if (currentPtr == NULL) {
        printf("\n\n還沒有建立任何好友資料唷...\n\n");
    }
    else {
        // 依序由鏈結串列取出資料,然後印在螢幕上
        printf("\n\n以下依好友名錄的儲存順序印出好友資料\n");
        printf("  好友暱稱 - 年 齡 - 性 別 - 關 係\n");
        while (currentPtr != NULL) {
            printf("%10s - ", currentPtr->data.name);
            printf("%5d - ", currentPtr->data.age);
            printf("%5s  - ", currentPtr->data.sex ? "男" : "女");
            printf("%s \n", currentPtr->data.relation ? "同學或朋友" : "家人");
        
            currentPtr = currentPtr->nextPtr;
        }
    }
        
}


完整的範例程式碼及編譯執行,請參考


一般講 C 語言有傳址呼叫,多半是說以指標當參數傳遞給被呼叫函數,然而這樣的講法並不十分確實。如果我們的程式需要某個資料結構,用一個 startPtr 的指標變數作為資料結構的入口,利用函數增加或減少資料,由於可能會更動 startPtr 的值,也就是建立第一個節點,或是刪除第一個節點的時候,這時候就必須以雙重指標當成參數,也就是指向指標的指標。


嚴謹的教科書並不會說 C 語言有傳址呼叫,多半只會講 C 語言是利用傳值呼叫的方式傳遞參數給函數。說到底,「傳址呼叫」的說法是一種好分類、好記住的方式,就指標初步的使用來講,大部分情況也都符合。若是學習 C 語言一開始就牢記「傳址呼叫」,起初雖然運用起來彷彿觀念很清楚,到雙重指標的部份就可能需要兜一大個圈來理解實際程式的運作情況。


兜怎麼樣的圈呢?就是說傳址呼叫只適用在利用取址運算子 & 的情況,而非所有指標變數都適用。然而實際上函數傳遞參數的方法仍是依賴傳值,取址運算子 & 取得記憶體位址,然後把記憶體位址當成值傳遞給被呼叫函數,在被呼叫函數中再利用反參考運算子 * ,便可修改該指標變數的值。


我們建議用傳值呼叫理解 C 程式函數呼叫的運作,至於「傳址呼叫」對 C 語言似乎是種流行的講法,甚至許多考試都把「傳址呼叫」當考題,所以某種程度上也需要大概知道「傳址呼叫」是說什麼。


問題與討論
  1. 說明雙重指標的運作方式。
  2. 為什麼說 C 語言有傳址呼叫並不十分確實?




沒有留言: