Programing

링크된 목록에 노드를 추가할 때 이중 포인터를 사용하는 이유는?

c10106 2022. 5. 23. 21:26
반응형

링크된 목록에 노드를 추가할 때 이중 포인터를 사용하는 이유는?

아래의 두 코드 예는 모두 링크된 목록의 맨 위에 노드를 추가한다.그러나 첫 번째 코드 예는 이중 포인터를 사용하는 반면 두 번째 코드 예는 단일 포인터를 사용한다.

코드 예제 1:

struct node* push(struct node **head, int data)
{
        struct node* newnode = malloc(sizeof(struct node));
        newnode->data = data;
        newnode->next = *head;
        return newnode;
}

push(&head,1);

코드 예제 2:

struct node* push(struct node *head, int data)
{
        struct node* newnode = malloc(sizeof(struct node));
        newnode->data = data;
        newnode->next = head;
        return newnode;
}

push(head,1)

두 가지 전략이 모두 통한다.그러나 링크된 목록을 사용하는 많은 프로그램은 새 노드를 추가하기 위해 이중 포인터를 사용한다.나는 이중 포인터가 무엇인지 안다.그러나 단일 포인터가 새 노드를 추가하는 데 충분하다면 왜 많은 구현이 이중 포인터에 의존하는가?

한 개의 포인터가 작동하지 않아서 이중 포인터를 사용해야 하는 경우가 있는가?

일부 구현에서는 포인터를 포인터 매개 변수에 전달하여 새 헤드 포인터를 반환하는 대신 헤드 포인터를 직접 변경할 수 있다.따라서 다음과 같이 쓸 수 있다.

// note that there's no return value: it's not needed
void push(struct node** head, int data)
{
    struct node* newnode = malloc(sizeof(struct node));
    newnode->data=data;
    newnode->next=*head;
    *head = newnode; // *head stores the newnode in the head
}

// and call like this:
push(&head,1);

머리 포인터로 포인터를 가져가지 않는 구현은 새 헤드를 반환해야 하며, 호출자는 헤드를 직접 업데이트할 책임이 있다.

struct node* push(struct node* head, int data)
{
    struct node* newnode = malloc(sizeof(struct node));
    newnode->data=data;
    newnode->next=head;
    return newnode;
}

// note the assignment of the result to the head pointer
head = push(head,1);

이 기능을 호출할 때 이 할당을 하지 않으면 malloc로 할당한 노드가 누출되고 헤드 포인터가 항상 같은 노드를 가리킨다.

이점은 이제 명확해야 한다. 두 번째와 함께 발신자가 반환된 노드를 헤드 포인터에 할당하는 것을 잊어버리면 나쁜 일이 발생할 것이다.

편집:

포인터에 대한 포인터(이중 포인터)도 동일한 프로그램 내에서 여러 사용자 정의 데이터 유형을 생성할 수 있도록 허용(예: 링크된 목록 2개 작성)

이중 포인터의 복잡성을 피하기 위해 우리는 항상 구조를 활용할 수 있다.

다음과 같은 방법으로 목록을 정의할 수 있다.

typedef struct list {
    struct node* root;    
} List;

List* create() {
    List* templ = malloc(sizeof(List));
    templ->root = NULL;
    return templ;
}

링크 목록 함수의 경우 위의 목록을 다음과 같은 방법으로 사용한다. (Push 기능 예)

void Push(List* l, int x) {         
    struct node* n = malloc(sizeof(struct node));
    n->data = x;
    n->link = NULL;
    
    printf("Node created with value %d\n", n->data);
    if (l->root == NULL) {
        l->root = n;
    } else {
        struct node* i = l->root;
        while (i->link != NULL){
            i = i->link;
        }
        i->link = n;
    }
}

주 함수()에서 다음과 같이 목록을 선언하십시오.

List* list1 = create(); 
push(list1, 10);

      

앞의 답변은 충분히 좋지만, 「가치별 복사」라는 관점에서 생각하는 것이 훨씬 수월하다고 생각한다.

포인터를 함수에 전달하면 주소 값이 함수 매개 변수에 복사되고 있다.함수의 범위 때문에, 그 복사본은 일단 반환되면 사라질 것이다.

이중 포인터를 사용하면 원래 포인터의 값을 업데이트할 수 있다.이중 포인터는 여전히 가치에 의해 복사될 것이지만, 그것은 중요하지 않다.당신이 정말로 신경쓰는 것은 원래 포인터를 수정해서 함수의 범위나 스택을 우회하는 것뿐이다.

이것이 당신의 질문뿐만 아니라 다른 포인터와 관련된 질문에도 답하기를 바란다.

@R. 마르틴호 페르난데스대답에서 지적했듯이, 포인터로 포인터로 포인터로 하는 것을 의 논거로 삼았다.void push(struct node** head, int data)당신이 그것을 바꿀 수 있도록 허락한다.head안쪽에서 바로 포인터push새 포인터를 반환하는 대신 기능하십시오.

포인터 대신 포인터 하나를 사용하면 코드가 짧아지고 단순화되며 속도가 빨라지는 이유를 보여주는 또 다른 좋은 예가 있다.단일 연결 목록에서 노드를 제거하는 것과 대조적으로 일반적으로 포인터 대 포인터(pointer)가 필요하지 않은 새 노드를 목록에 추가하는 방법에 대해 물어보셨습니다.포인터 대 포인터 없이 목록에서 노드 제거를 구현할 수 있지만, 이는 차선책이다.나는 여기에 자세한 내용을 묘사했다.나는 또한 문제를 해결하는 이 유튜브 비디오를 보는 것을 추천한다.

BTW: 리누스 토발드의견에 따라 세는다면 포인터 투 포인터를 사용하는 방법을 배우는 것이 좋을 것이다. ;-)

라이너스 토발즈: (...) 스펙트럼의 반대쪽 끝에서, 나는 실제로 더 많은 사람들이 정말로 핵심적인 낮은 수준의 코딩을 이해했으면 좋겠어.자물쇠가 없는 이름 조회처럼 크고 복잡한 것이 아니라, 포인터 대 포인터 등을 잘 활용한다.예를 들어, 나는 너무 많은 사람들이 "prev" 항목을 추적하여 단독 링크된 목록 항목을 삭제한 다음, 다음과 같은 일을 하면서 항목을 삭제하는 것을 보았다.

if (prev)
prev->next = entry->next;
else
list_head = entry->next;

그런 코드를 볼 때마다 "이 사람은 포인터를 이해하지 못한다"고만 한다.그리고 슬프게도 꽤 흔하다.

포인터를 이해하는 사람들은 "엔트리 포인터의 포인터"를 사용하고, 그것을 list_head의 주소로 초기화한다.그리고 목록을 가로지르면서 조건 없이 "*pp = entry->next"만 하면 항목을 제거할 수 있다. (...)


도움이 될 수 있는 기타 리소스:

당신의 특별한 예에서는 이중 포인터가 필요하지 않다.그러나, 예를 들어, 다음과 같은 일을 해야 한다면, 그것은 필요할 수 있다.

struct node* push(struct node** head, int data)
{
    struct node* newnode = malloc(sizeof(struct node));
    newnode->data=data;
    newnode->next=*head;
    //vvvvvvvvvvvvvvvv
    *head = newnode; //you say that now the new node is the head.
    //^^^^^^^^^^^^^^^^
    return newnode;
}

관찰 및 찾기, 왜...

몇 가지 실험을 하고 결론을 내리기로 했는데

관찰 1- 연결된 목록이 비어 있지 않으면 포인터 하나만 사용하여 노드(확실히 맨 뒤에 있음)를 추가할 수 있다.

int insert(struct LinkedList *root, int item){
    struct LinkedList *temp = (struct LinkedList*)malloc(sizeof(struct LinkedList));
    temp->data=item;
    temp->next=NULL;
    struct LinkedList *p = root;
    while(p->next!=NULL){
        p=p->next;
    }
    p->next=temp;
    return 0;
}


int main(){
    int m;
    struct LinkedList *A=(struct LinkedList*)malloc(sizeof(struct LinkedList));
    //now we want to add one element to the list so that the list becomes non-empty
    A->data=5;
    A->next=NULL;
    cout<<"enter the element to be inserted\n"; cin>>m;
    insert(A,m);
    return 0;
}

설명하기 간단하다(Basic).우리는 목록의 첫 번째 노드(루트)를 가리키는 포인터를 주요 기능에 가지고 있다.에서insert()함수 우리는 루트 노드의 주소를 전달하고 이 주소를 사용하여 목록의 끝에 도달하여 노드를 추가한다.따라서 만약 우리가 주함수가 아닌 함수에 변수의 주소를 가지고 있다면, 우리는 주함수에 반영될 함수로부터 그 변수의 값을 영구적으로 변경할 수 있다고 결론 내릴 수 있다.

관찰 2- 의 노드 추가 방법은 목록이 비어 있을 때 실패하였다.

int insert(struct LinkedList *root, int item){
    struct LinkedList *temp = (struct LinkedList*)malloc(sizeof(struct LinkedList));
    temp->data=item;
    temp->next=NULL;
    struct LinkedList *p=root;   
    if(p==NULL){
        p=temp;
    }
    else{
      while(p->next!=NULL){
          p=p->next;
      }
      p->next=temp;
    }
    return 0;
}



int main(){
    int m;
    struct LinkedList *A=NULL; //initialise the list to be empty
    cout<<"enter the element to be inserted\n";
    cin>>m;
    insert(A,m);
    return 0;
}

요소를 계속 추가하고 목록을 표시하면 목록이 변경되지 않고 비어 있는 것을 알 수 있다.내 마음에 떠오른 질문은 이 경우에 또한 우리는 루트 노드의 주소를 전달하고 있는데 왜 영구적인 수정과 주요 기능의 목록은 아무런 변화도 겪지 않는지. 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜? 왜?

한 이 있는데, 그 때 내가 가보를 쓸 때, 을을 썼다.A=NULL 주소A0이 되다.이것은 지금을 의미한다.A기억 속의 어떤 장소도 가리키지 않는다.그래서 나는 선을 제거했다.A=NULL;삽입 기능을 약간 수정했다.

insert()함수는 빈 리스트에 한 요소만 추가할 수 있으며, 테스트 목적으로 이 함수를 작성했을 뿐)

int insert(struct LinkedList *root, int item){
    root= (struct LinkedList *)malloc(sizeof(struct LinkedList));
    root->data=item;
    root->next=NULL;
    return 0;
}



int main(){
    int m;
    struct LinkedList *A;    
    cout<<"enter the element to be inserted\n";
    cin>>m;
    insert(A,m);
    return 0;
}

위의 방법 또한 다음 때문에 실패한다.insert()함수 루트 저장소는 다음과 같은 주소A에서main()선후에서 기능하다.root= (struct LinkedList *)malloc(sizeof(struct LinkedList));에 저장된 주소root변화들그래서 지금,root(안에)insert() 및 기) 및A(안에)main()function) 다른 주소 저장

그래서 정확한 최종 프로그램은

int insert(struct LinkedList *root, int item){
    root->data=item;
    root->next=NULL;
    return 0;
}



int main(){
    int m;
    struct LinkedList *A = (struct LinkedList *)malloc(sizeof(struct LinkedList));
    cout<<"enter the element to be inserted\n";
    cin>>m;
    insert(A,m);
    return 0;
}

그러나 우리는 두 가지 다른 기능을 삽입하기를 원하지 않는다. 하나는 목록이 비어 있을 때, 다른 하나는 목록이 비어 있지 않을 때.이제 일을 쉽게 만드는 이중 포인터가 나온다.

내가 중요한 한 가지는 포인터가 저장되어 있는 주소와 '*'와 함께 사용될 때 포인터들은 그 주소에서 가치를 부여하지만 포인터들 자체가 그들만의 주소를 가지고 있다는 것이다.

자, 여기 완전한 프로그램이 있고 나중에 개념을 설명해봐.

int insert(struct LinkedList **root,int item){
    if(*root==NULL){
        (*root)=(struct LinkedList *)malloc(sizeof(struct LinkedList));
        (*root)->data=item;
        (*root)->next=NULL;
    }
    else{
        struct LinkedList *temp=(struct LinkedList *)malloc(sizeof(struct LinkedList));
        temp->data=item;
        temp->next=NULL;
        struct LinkedList *p;
        p=*root;
        while(p->next!=NULL){
            p=p->next;
        }
        p->next=temp;
    }
    return 0;
}


int main(){
    int n,m;
    struct LinkedList *A=NULL;
    cout<<"enter the no of elements to be inserted\n";
    cin>>n;
    while(n--){
        cin>>m;
        insert(&A,m);
    }
    display(A);
    return 0;
}

다음은 관찰사항이다.

1. 루트 저장 포인터 주소 A(&A) *root포인터로 저장된 주소 저장A그리고**root에 저장된 값을 주소에 저장A. 간단한 언어로root=&A *root= A그리고**root= *A.

2. 우리가 글을 쓴다면*root= 1528그리고 그것은 주소의 값이 에 저장되어 있다는 것이다.root1528년이 되고 주소가 저장되기 때문에root a pointer an NECTION.의 주소지.(&A)그래서 지금A=1528(즉, 에 저장된 주소)A1528)이며 이 변화는 영구적이다.

우리가 가치를 바꿀 때마다*root우리는 정말로 주소에 저장된 가치를 변화시키고 있다.root그리고 그 이후로root=&A(주소)A) 우리는 간접적으로 가치를 변화시키고 있다.A또는 저장된 주소A.

그러니 지금이라도A=NULL(목록이 비어 있음)*root=NULL그러므로 우리는 첫 번째 노드를 생성하고 그 주소를 다음 위치에 저장한다.*root즉, 첫 번째 노드의 주소를 다음 위치에 간접적으로 저장함A만약 리스트가 비어있지 않다면, 모든것은 우리가 루트를*root뿌리에 저장된 것이 지금 에 저장되어 있기 때문에*root.

[HEAD]와 같은 헤드의 메모리 위치를 생각해 보십시오.데이터.

두 번째 시나리오에서 호출 함수의 main_head는 이 위치에 대한 포인터 입니다.

main_head--->[HEAD_]데이터]

코드에서 pointer main_head 값을 함수(즉, head_data의 메모리 위치 주소)로 전송함수의 local_head에 복사한 경우.그래서 지금

local_head---> [HEAD_]데이터]

그리고

main_head---> [HEAD_]데이터]

둘 다 같은 위치를 가리키지만 본질적으로 서로 독립적이다.local_head = newnode를 쓸 때, 당신이 한 것은

local_head--/-->[HEAD_]데이터]

local_head-----> [NEWNODE_DATA]

이전 메모리의 메모리 주소를 로컬 포인터의 새 주소로 바꾸기만 하면 된다.main_head(점퍼)는 여전히 옛 [HEAD_]를 가리키고 있다.데이터]

C의 링크된 목록을 처리하는 표준 방법은 푸시 및 팝 기능이 헤드 포인터를 자동으로 업데이트하도록 하는 것이다.

C는 "Call by value"로서 매개변수의 복사본이 함수에 전달된다는 것을 의미한다.헤드 포인터만 전달하면 해당 포인터에 대한 로컬 업데이트는 호출자가 볼 수 없다.두 가지 해결책은

1) Head Pointer의 주소를 전달한다. (Head Pointer to Head Pointer)

2) 새 헤드 포인터를 돌려주고, 호출자가 헤드 포인터를 업데이트하도록 한다.

처음에는 조금 헷갈리지만 옵션 1)이 가장 쉽다.

간단하게 예를 들어,

void my_func(int *p) {
        // allocate space for an int
        int *z = (int *) malloc(sizeof(int));
        // assign a value
        *z = 99;

        printf("my_func - value of z: %d\n", *z);

        printf("my_func - value of p: %p\n", p);
        // change the value of the pointer p. Now it is not pointing to h anymore
        p = z;
        printf("my_func - make p point to z\n");
        printf("my_func - addr of z %p\n", &*z);
        printf("my_func - value of p %p\n", p);
        printf("my_func - value of what p points to: %d\n", *p);
        free(z);
}

int main(int argc, char *argv[])
{
        // our var
        int z = 10;

        int *h = &z;

        // print value of z
        printf("main - value of z: %d\n", z);
        // print address of val
        printf("main - addr of z: %p\n", &z);

        // print value of h.
        printf("main - value of h: %p\n", h);

        // print value of what h points to
        printf("main - value of what h points to: %d\n", *h);
        // change the value of var z by dereferencing h
        *h = 22;
        // print value of val
        printf("main - value of z: %d\n", z);
        // print value of what h points to
        printf("main - value of what h points to: %d\n", *h);


        my_func(h);

        // print value of what h points to
        printf("main - value of what h points to: %d\n", *h);

        // print value of h
        printf("main - value of h: %p\n", h);


        return 0;
}

출력:

main - value of z: 10
main - addr of z: 0x7ffccf75ca64
main - value of h: 0x7ffccf75ca64
main - value of what h points to: 10
main - value of z: 22
main - value of what h points to: 22
my_func - value of z: 99
my_func - value of p: 0x7ffccf75ca64
my_func - make p point to z
my_func - addr of z 0x1906420
my_func - value of p 0x1906420
my_func - value of what p points to: 99
main - value of what h points to: 22
main - value of h: 0x7ffccf75ca64

my_funk:

void my_func(int *p);

출력을 보면, 결국 h가 가리키는 값은 여전히 22이고 h의 값은 같으며 my_func에서 altought가 변경되었다.왜?

my_func에서는 p의 값을 조작하고 있는데, p의 값은 단지 로컬 포인터일 뿐이다.

my_func(ht);

mainhrop p는 h가 hold하는 값을 유지하며, z 변수는 main 함수에 선언된다.

my_func()에서는 공간을 할당한 메모리의 위치에 대한 포인터인 z 값을 고정하기 위해 p 값을 변경할 때 h의 값이 아니라 로컬 포인터 p의 값만 변경하는 것이다.기본적으로 p는 h의 값을 더 이상 보유하지 않고 z가 가리키는 메모리 위치의 주소를 보유하게 된다.

예를 좀 바꾸면:

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

void my_func(int **p) {
    // allocate space for an int
    int *z = (int *) malloc(sizeof(int));
    // assign a value
    *z = 99;

    printf("my_func - value of z: %d\n", *z);

    printf("my_func - value of p: %p\n", p);
    printf("my_func - value of h: %p\n", *p);
    // change the value of the pointer p. Now it is not pointing to h anymore
    *p = z;
    printf("my_func - make p point to z\n");
    printf("my_func - addr of z %p\n", &*z);
    printf("my_func - value of p %p\n", p);
    printf("my_func - value of h %p\n", *p);
    printf("my_func - value of what p points to: %d\n", **p);
    // we are not deallocating, because we want to keep the value in that
    // memory location, in order for h to access it.
    /* free(z); */
}

int main(int argc, char *argv[])
{
    // our var
    int z = 10;

    int *h = &z;

    // print value of z
    printf("main - value of z: %d\n", z);
    // print address of val
    printf("main - addr of z: %p\n", &z);

    // print value of h.
    printf("main - value of h: %p\n", h);

    // print value of what h points to
    printf("main - value of what h points to: %d\n", *h);
    // change the value of var z by dereferencing h
    *h = 22;
    // print value of val
    printf("main - value of z: %d\n", z);
    // print value of what h points to
    printf("main - value of what h points to: %d\n", *h);


    my_func(&h);

    // print value of what h points to
    printf("main - value of what h points to: %d\n", *h);

    // print value of h
    printf("main - value of h: %p\n", h);
    free(h);


    return 0;
}

다음과 같은 결과물이 있다.

main - value of z: 10
main - addr of z: 0x7ffcb94fb1cc
main - value of h: 0x7ffcb94fb1cc
main - value of what h points to: 10
main - value of z: 22
main - value of what h points to: 22
my_func - value of z: 99
my_func - value of p: 0x7ffcb94fb1c0
my_func - value of h: 0x7ffcb94fb1cc
my_func - make p point to z
my_func - addr of z 0xc3b420
my_func - value of p 0x7ffcb94fb1c0
my_func - value of h 0xc3b420
my_func - value of what p points to: 99
main - value of what h points to: 99
main - value of h: 0xc3b420

이제 우리는 h가 가지고 있는 값을 my_func에서 다음과 같이 변경했다.

  1. 변경된 함수 서명
  2. main()에서 호출: my_func(&h); 기본적으로 함수 서명에 매개변수로 선언된 이중 포인터 p에 h 포인터 주소를 전달하고 있다.
  3. my_funcuit에서 우리는 다음과 같이 하고 있다: *p = z; 우리는 한 레벨의 이중 포인터 p를 비참조하고 있다.기본적으로 이것은 당신이 하는 것처럼 번역되었다: h = z;

p의 값은 현재 h 포인터 주소를 가지고 있다.h 포인터에는 z의 주소가 있다.

두 가지 사례를 모두 들어 분산시킬 수 있다.질문으로 돌아가면, 그 기능에서 바로 전달된 포인터를 수정하기 위해서는 이중 포인터가 필요하다.

만약 당신이 시간을 들여 작업 노드 삽입 기능을 쓴다면 답은 더 명백하다; 당신의 것은 그것이 아니다.

앞으로 이동하려면 머리 위로 글을 쓸 수 있어야 하기 때문에, 헤드로 향하는 포인터에는 포인터가 있어야 하며, 헤드로의 포인터를 가져오고 바꾸려면 포인터도 폐기할 수 있다.

특정 변경사항을 적용해야 하고 이러한 변경사항이 다시 호출 기능에 반영되어야 하는 경우를 상상해 보십시오.

예:

void swap(int* a,int* b){
  int tmp=*a;
  *a=*b;
  *b=tmp;
}

int main(void){
  int a=10,b=20;

  // To ascertain that changes made in swap reflect back here we pass the memory address
  // instead of the copy of the values

  swap(&a,&b);
}

마찬가지로 우리는 리스트의 머리글의 메모리 주소를 전달한다.

이렇게 하면, 노드가 추가되고 헤드의 값이 변경되면, 그 변화는 뒤로 반영되며, 우리는 헤드를 호출 기능의 내부에서 수동으로 재설정할 필요가 없다.

따라서 이 접근방식은 통화 기능에서 헤드를 다시 업데이트하는 것을 잊었더라면 새로 할당된 노드에 대한 포인터를 잃어버렸을 것이므로 메모리 누수의 가능성을 줄인다.

이 외에도, 우리가 직접 메모리로 작업하기 때문에 복사하고 돌아오는 데 시간이 낭비되지 않기 때문에 두 번째 코드는 더 빨리 작동될 것이다.

함수에서 포인터를 매개 변수로 전달하고 동일한 포인터에서 업데이트를 원할 때 우리는 이중 포인터를 사용한다.

반면, 만약 우리가 어떤 함수의 매개변수로 포인터를 전달하고 그것을 하나의 포인터로 포착한다면, 그 결과를 사용하기 위해서는 그 결과를 다시 호출 함수로 되돌려야 할 것이다.

내 생각에 요점은 링크된 목록 내의 노드를 더 쉽게 업데이트할 수 있게 해준다는 것이다.보통 이전과 현재에 대해 포인터를 추적해야 하는 경우, 이중 포인터가 모든 것을 처리하도록 할 수 있다.

#include <iostream>
#include <math.h>

using namespace std;

class LL
{
    private:
        struct node 
        {
            int value;
            node* next;
            node(int v_) :value(v_), next(nullptr) {};
        };
        node* head;

    public:
        LL() 
        {
            head = nullptr;
        }
        void print() 
        {
            node* temp = head;
            while (temp) 
            {
                cout << temp->value << " ";
                temp = temp->next;
            }
        }
        void insert_sorted_order(int v_) 
        {
            if (!head)
                head = new node(v_);
            else
            {
                node* insert = new node(v_);
                node** temp = &head;
                while ((*temp) && insert->value > (*temp)->value)
                    temp = &(*temp)->next;
                insert->next = (*temp);
                (*temp) = insert;
            }
        }

        void remove(int v_)
        {
            node** temp = &head;
            while ((*temp)->value != v_)
                temp = &(*temp)->next;
            node* d = (*temp);
            (*temp) = (*temp)->next;
            delete d;
        }

        void insertRear(int v_)//single pointer
        {
            if (!head)
                head = new node(v_);
            else
            {
                node* temp = new node(v_);
                temp->next = head;
                head = temp;
            }
        }
};

내가 너의 집 주소를 카드-1에 적어놨다고 하자.만약 내가 너의 집 주소를 다른 사람에게 알려주고 싶다면, 나는 그 주소를 카드-1에서 카드-2로 복사해서 카드-2를 주든지 아니면 카드-1을 직접 주든지 둘 중 하나야.어느 쪽이든 그 사람이 주소를 알고 당신에게 연락할 수 있다.그러나 내가 직접 카드-1을 줄 때, 카드-1에서는 주소를 변경할 수 있지만 카드-2에서는 주소만 변경할 수 있지만 카드-1에서는 변경할 수 없다.

포인터를 포인터에 전달하는 것은 카드-1에 직접 접근할 수 있는 것과 비슷하다.포인터를 통과하면 주소의 새 사본을 만드는 것과 유사하다.

두 기능 모두 이름이 붙여진 매개 변수를 가지고 있다는 사실에서 당신의 혼란이 올 수 있다고 생각한다.head두 사람head사실 다른 것들이야 head첫 번째 코드에는 헤드 노드 포인터(헤드 노드 구조의 주소를 저장함)의 주소가 저장된다.반면에 두번째는head헤드 노드 구조의 주소를 직접 저장한다.그리고 두 기능 모두 새로 생성된 노드(이것은 새로운 헤드가 되어야 함)를 반환하기 때문에, 첫 번째 접근법은 갈 필요가 없다고 생각한다.이 기능의 호출자는 자신이 가지고 있는 헤드 레퍼런스를 갱신할 책임이 있다.나는 두 번째 이 충분히 좋고 보기에도 간단하다고 생각한다.난 두 번째 걸로 하겠어.

명명규칙- 헤드는 혼란의 원인이다.

머리는 꼬리, 꼬리는 머리다.꼬리가 머리를 흔든다.

머리는 포인터일 뿐이고, 데이터는 Null이며, 꼬리는 데이터일 뿐이고, 포인터도 Null이다.

그럼 구조체 포인터로 가는 포인터가 있는 거군.Structure 포인터는 Linked 목록에서 첫 번째 노드 구조를 가리킨다.첫 번째 구조 노드 포인터로 가는 이 포인터를 Head라고 부른다.그것은 시작자 또는 머리자르기자라고 불리는 것이 더 나을 수 있다.

당신이 스타트렉을 잡으면 당신은 링크드리스트를 잡는다.그러면 모든 구조 노드를 통과할 수 있다.

참조URL: https://stackoverflow.com/questions/7271647/what-is-the-reason-for-using-a-double-pointer-when-adding-a-node-in-a-linked-lis

반응형