8

私は通常、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() 関数内のすべてのものから 1 つの * 演算子を削除し、単純に NULL のアドレス (つまり、&p ではなく p) を append() 関数に渡すことは有効ではないでしょうか?

この質問をグーグルで検索しました。答えは、理解するのが難しすぎる (私は C の初心者なので) か、単純すぎるかのどちらかです。これについて読むことができるヒント、コメント、またはリンクに感謝します。

4

7 に答える 7

17

変数であろうとポインターであろうと、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"コピーのみを変更します。

2番目の関数呼び出しであるreally_change()、は、ポインターからポインターへのコピーを渡します。このポインターには、元のポインターと出来上がりのアドレスが含まれています。これで、元のポインターを参照して、元のポインターが指すものを変更できます。

うまくいけば、それはそれをもう少し説明します:)

于 2013-03-06T10:22:23.067 に答える
6

まず、「アドレスのアドレス」ではありません。そのポインタ変数のアドレス。例:ゼロを含むint変数のアドレスを渡す場合n、ゼロのアドレスを渡すことはありません。変数(この場合はint変数、この場合はポインター変数)のアドレスを渡します。変数のアドレスはメモリにあります。この場合のパラメーターは、リストの先頭であるポインター変数である変数のアドレスです。

なぜこれをするのですか?単純。Cのすべての変数(ポインタ減衰を介した配列は耐えられません)はによって渡されます。参照(アドレス)によって何かを変更する場合、渡す必要のある「値」はアドレスである必要があり、それを受け取る仮パラメーターはポインター型である必要があります。つまり、基本的なスケーラー値ではなく、「値」にメモリアドレスが渡されるようにします。次に、関数はこれを(正式なポインターパラメーターを介して)使用して、それに応じてデータを格納します。「私が欲しかったものを「この」メモリアドレスに置いてください」と考えてください。

簡単な例として、ファイルを実行し、ノードの順方向リンクリストにその中のすべての文字を追加したいとします。使用しているようなappend-methodは使用しません(理由については、画家のアルゴリズムを参照してくださいポインターからポインターを使用するが、関数呼び出しを使用しないこのコードに従うことができるかどうかを確認してください。

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が、ヘッドノードから始めて、リストに追加される次のリンクされたノードに常に入力する方法を理解できるかどうかを確認してください。

于 2013-03-06T10:22:43.097 に答える
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 = NULLは、ある意味で に似ていint i = 0ます。渡す&i場合は、 のアドレスを渡すのではなく、 のアドレスを0渡しますi&pポインタのアドレスを渡しても同じことが起こります。

funcA では、割り当てを行いたいので、を使用しますmallocq = malloc(...、q がvoid* qメイン関数にあった場合、p は割り当てられませんでした。なんで?を考えてみてくださいfuncB。j は i のアドレスを保持します。i を変更したい場合は、 を実行します。変更すると、*j = 1jj = 1が i ではなく別のメモリ領域を指すようになるからです。funcA の q も同様です。void* である p の型へのポインタだと考えて<type of p>* qください。しかし、funcB の場合は int です。ここで、p が指しているアドレスを変更する必要があります。これは、q が指しているアドレスを変更したくないことを意味し、q が指しているアドレス、別名*qまたはを変更したいということですp

まだはっきりしていない場合。箱を考えてみてください。関連するボックスの funcA を使用した簡単な例を示しました。各ボックスには (ボックス内に) 名前があり、このボックスはプロセスの仮想メモリ内の任意のアドレスにあり、各ボックスには値が含まれています。このビューでは、funcA(&p)が呼び出され、malloc が実行される状態になっています。

ここに画像の説明を入力

于 2013-03-06T10:43:14.903 に答える
2

なぜあなたはそれをこのように考えているのですか、誰かが構造体を渡して関数を追加していると考えてください。その場合struct node{int data; struct node *link; };、あなたの場合の構造体全体がのスタックフレームにコピーされるappend functionので、構造体ポインタのアドレスを渡して4を取得する方が良いですスタックにコピーされたバイト。

于 2013-03-06T10:23:39.560 に答える
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;

}

なぜ誰かがこれをするのでしょうか?基本的には短いため、1 つのループのみを使用し、追加の条件を使用せず、追加の変数を使用せず、正しいことを証明できます。もちろん、追加の条件が必要な malloc の結果に対してテストを追加する必要があります。

于 2013-03-06T10:36:21.770 に答える
1

本質的に、Jite & 他の人が言ったように正しいです。

C のデータ構造に変更を適用する (別の関数によって実行される変更) 場合は常に、このデータ構造に「参照」を渡して、change() 関数が完了した後も変更が持続するようにする必要があります。これは Python でも起こります。明示的にコピーを作成しない限り、オブジェクトへの参照を渡します。C では、何をしたいのかを正確に指定する必要があります。さらに単純化するには、次のいずれかです。

type data_struct

change(data_struct) => これが私の data_struct のコピーです。変更を行いますが、適用する変更について呼び出し元関数では気にしません

また

change(&data_struct) => これが私の data_struct のアドレス (「参照」) です。変更を適用します。呼び出し元の関数は、適用後にこの変更を確認します。

ここで、元の「タイプ」が何であるかに応じて、* または ** を持つ場合があります。それにもかかわらず、私がテイカーであるという答えを誰かが持っている場合、それがシステムまたはコンパイラによって決定されるかどうかはわかりません。私は3つ以上の間接化を経験したことはありません。

于 2013-03-06T10:36:59.783 に答える
0

理由は次のとおりだと思います。

構造体ノード *p; //ノード構造体へのポインタ p = NULL;

上記のスニペットがメイン ブロックに書き込まれた場合、ポインター p の値が NULL であるため、メモリ内の何も指していないことを意味します。したがって、新しいノードを作成し、新しいノードのアドレスをポインター p の値に割り当てるために、ポインター p のアドレスを渡します。

*(&p) == *q == 温度;

*q == temp; を実行することによって。最初はどこも指していなかったポインター p に何らかの値を割り当てるという目標を達成します。

于 2014-12-17T17:40:38.730 に答える