2013-03-06 106 views
7

我通常使用python編程。爲了提高我的模擬性能,我正在學習C語言。在將鏈接列表的附加函數實現時,我理解使用指針指針的問題。這是我書中的代碼摘錄(Kanetkar在C中的理解指針)。鏈接列表中指針的指針附加

#include <stdlib.h> 
#include <stdio.h> 

struct node{ 
    int data; 
    struct node *link; 
}; 

int main(){ 
    struct node *p; //pointer to node structure 
    p = NULL; //linked list is empty 

    append(&p,1); 
    return 0; 
} 

append(struct node **q, int num){ 
    struct node *temp, *r; //two pointers to struct node 
    temp = *q; 

    if(*q == NULL){ 
     temp = malloc(sizeof(struct node)); 
     temp -> data = num; 
     temp -> link = NULL; 
     *q = temp; 
    } 
    else{ 
     temp = *q; 
     while(temp -> link != NULL) 
      temp = temp -> link; 
     r = malloc(sizeof(struct node)); 
     r -> data = num; 
     r -> link = NULL; 
     temp -> link = r; 
    } 
} 

在這段代碼中,我將雙指針** q傳遞給append函數。我知道這是地址的地址,即在這種情況下是NULL的地址。

我只是沒有得到爲什麼這樣做是這樣的。從append()函數中的所有內容中移除一個*運算符並將簡單的NULL(即p而不是& p)傳遞給append()函數是否無效?

我已經使用了這個問題。答案要麼太難理解(因爲我只是一個C初學者)或太簡單。我很感激任何提示,評論或鏈接,我可以閱讀這些內容。

回答

16

當您將事物傳遞給C中的函數時,無論它是變量還是指針,它都是原始函數的副本。

簡單的例子:

#include <stdio.h> 
void change(char *in) 
{ 
    // in here is just a copy of the original pointer. 
    // In other words: It's a pointer pointing to "A" in our main case 
    in = "B"; 
    // We made our local copy point to something else, but did _not_ change what the original pointer points to. 
} 
void really_change(char **in) 
{ 
    // We get a pointer-to-a-pointer copy. This one can give us the address to the original pointer. 
    // We now know where the original pointer is, we can make _that one_ point to something else. 
    *in = "B"; 
} 
int main(int argc, char *argv[]) 
{ 
    char *a = "A"; 
    change(a); 
    printf("%s\n", a); /* Will print A */ 
    really_change(&a); 
    printf("%s\n", a); /* Will print B */ 
    return 0; 
} 

所以第一個函數調用change()被傳遞一個指針的一個拷貝到一個地址。當我們做in = "B"時,我們只更改我們通過的指針副本。

在第二個函數調用really_change()中,我們傳遞了一個指針指針的副本。這個指針包含我們原始指針的地址,瞧,我們現在可以引用原始指針並且改變原始指針應該指向的地方。

希望它能多解釋一下:)

+0

ahhhh!這真的很有幫助。謝謝! :-) – seb 2013-03-06 10:27:47

6

先說它不是「地址的地址」。它是一個指針變量的地址。例如:如果你傳遞一個包含零的int變量n的地址,那麼你不會傳遞零地址;你正在傳遞一個變量的地址(在這個例子中是一個int變量,在你的情況下是一個指針變量)。變量在內存中有地址。這種情況下的參數是一個變量的地址,它恰好是一個指針變量,即列表的頭部。

關於爲什麼要這樣做?簡單。 C中的所有變量(數組通過指針衰減不能承受)通過。如果你想通過引用(地址)修改某些東西,那麼你需要傳遞的「值」必須是一個地址,接收它的形式參數必須是一個指針類型。總之,你讓「價值」被傳遞給一個內存地址,而不僅僅是一個基本的定標器值。該函數然後使用這個(通過形式指針參數)來相應地存儲數據。把它想象成「把我想要的東西放在」這個「內存地址」上。「

作爲一個簡單的例子,假設你想通過一個文件來運行,中的每一個字符追加到節點的向前鏈表。你會而不是使用像你所擁有的追加方法(請參閱The Painter's Algorithm爲什麼)。看看你是否可以遵循這個代碼,它使用指針指針,但沒有函數調用。在那一段時間

typedef struct node 
{ 
    char ch; 
    struct node *next; 
} node; 


node *loadFile(const char *fname) 
{ 
    node *head = NULL, **next = &head; 
    FILE *fp = fopen(fname, "r"); 
    if (fp) 
    { 
     int ch; 
     while ((ch = fgetc(fp)) != EOF) 
     { 
      node *p = malloc(sizeof(*p)); 
      p->ch = ch; 
      *next = p; 
      next = &p->next; 
     } 
     *next = NULL; 
     fclose(fp); 
    } 
    return head; 
} 

凝視,看你是否能明白是怎麼指針到指針next總是用來填充一個連接節點添加到列表中,首先是頭節點。

+0

關於價值/參考的很好的解釋。 – Jite 2013-03-06 10:29:26

+0

好吧,我在這。它會花費我一分多鐘。感謝您提前舉辦的偉大榜樣! – seb 2013-03-06 10:43:19

+0

那麼,曾經有一個。 32分鐘前,我似乎沒有刷新(或者我還記得它,這是相對不太可能;-) – wildplasser 2013-03-06 11:11:22

2

嘿嘿你爲什麼這麼想呢,想想有人在傳遞結構來追加函數,那麼整個結構struct node{int data; struct node *link; };在你的情況下會被拷貝到append function的棧幀上,所以最好傳遞結構指針的地址以便只將4個字節複製到堆棧中。

2

你不需要if/else;在這兩種情況下,您都需要將新節點鏈接到在操作之前爲NULL的指針。這可能是根節點,或鏈中最後一個節點的 - >下一個節點。兩者都是指向結構節點的指針,並且您需要一個指針指向這些指針才能分配給它們。

void append(struct node **q, int num){ 

    while (*q){ q = &(*q)->link; } 

    *q = malloc(sizeof **q); 
    (*q)->data = num; 
    (*q)->link = NULL; 

} 

爲什麼有人會這樣做?基本上,因爲它更短,它只使用一個循環,沒有附加條件,不使用額外的變量,並且可以證明它是正確的。 當然應該爲malloc的結果添加一個測試,這需要一個附加條件。

+0

空指針不保證標準具有全零位表示,IIRC。 (整數和ISO浮點數) – wildplasser 2013-03-06 10:40:14

+0

這是真的,我糾正了(實際上我現在坐着)。 NULL被定義爲0,並且將「正確地」比較爲一個空指針,但它們不是*同義詞(如果你曾經在AS/400上工作過,你知道這是真的。*驚人的*指針結構)。我將放棄我的評論。感謝您的幫助。 – WhozCraig 2013-03-06 10:42:40

+0

不,NULL不能保證全零;在源代碼中,將0常量強制轉換爲指針類型將被解釋爲NULL指針,編譯器將使用該平臺的表示形式指向NULL指針。 (其中*在大多數情況下*全部爲零) – wildplasser 2013-03-06 10:47:31

1

實質上,正如Jite &其他人所說的是正確的。您需要將一個「引用」傳遞給此數據結構,以使更改持久到change()函數完成後繼續執行。這也是Python中發生的事情,除非您明確地創建副本,否則將對象的引用傳遞給對象。在C中,你必須指定你想要做什麼。爲了簡化甚至更多,這是兩種:

型data_struct

變化(data_struct)=>這裏是我data_struct的副本,讓你的改變,但我不會在有關修改調用函數應用

關心這裏

變化(& data_struct)=>是地址我data_struct的(「參考」來),應用更改,應用它後調用函數就會看到這種變化。

現在,根據原始「類型」是什麼,你可能有*或**。儘管如此,請記住,您可以擁有多少「間接指令」,如果有人對我是一個接受者有回答,系統或編譯器確定的天氣是不確定的。我從來沒有超過3個指示。

3

您需要這樣做才能讓函數能夠分配內存。簡化了代碼:

main() 
{ 
    void *p; 
    p = NULL; 
    funcA(&p); 

    int i; 
    i = 0; 
    funcB(&i); 
} 

funcA(void **q) 
{ 
    *q = malloc(sizeof(void*)*10); 
} 

funcB(int *j) 
{ 
    *j = 1; 
} 

此代碼是這樣做的方式這樣子功能funcA可分配p指針。首先,考慮void* p就好像它在哪裏int i。你在做什麼p = NULLint i = 0類似。現在如果您通過&i您不通過0的地址,您通過i的地址。 &p同樣的事情發生在你傳遞指針的地址。

現在在funcA中,你想做分配,所以你用malloc但是如果你想做q = malloc(...而q應該是void* q在主函數中p就不會被分配。爲什麼?想想funcB,j擁有我的地址,如果你想修改我,你會做*j = 1,因爲如果你想做j = 1那麼你會讓j指向另一個內存區域而不是i。與funcA的q相同。認爲它是<type of p>* q它是一個指向void *類型的p的指針,但是在funcB的情況下它是一個int。現在你想要修改p指向的地址,這意味着你不想修改指向的地址q,你想修改q所指向的指向地址,即*qp

如果還不清楚。試着想想盒子。我已經用相關框的funcA繪製了一個快速示例。每個框都有一個名稱(在框內),該框位於任意地址的進程虛擬內存中,並且每個框都包含一個值。在這個視圖中,我們處於已調用funcA(&p)的狀態,並且malloc將完成。

enter image description here

+0

真的很有幫助!謝謝! – seb 2013-03-06 11:51:05

+0

順便說一下:'sizeof(void)'是零或者是一個錯誤。很可能你的意思是'sizeof(void *)'? – wildplasser 2013-03-10 12:07:06

+0

由於我用C語言編寫的,太長了;-)謝謝,我會更新答案。 – Huygens 2013-03-10 13:18:53

0

我認爲原因如下:

結構節點* P; //指向節點結構的指針 p = NULL;

上面的代碼片段寫在主塊中時,意味着指針p的值爲NULL,因此它不指向內存中的任何內容。所以我們傳遞指針p的地址以創建一個新節點,並將新節點的地址賦給指針p的值。

*(& p)== * q == temp;

通過做* q == temp;我們實現了爲最初指向任何地方的指針p分配一些值的目標。