ทำไมต้องใช้การเปลี่ยนทิศทางแบบคู่ หรือทำไมต้องใช้พอยน์เตอร์กับพอยน์เตอร์?


272

เมื่อใดควรใช้การเปลี่ยนทิศทางแบบคู่ใน C มีใครอธิบายได้บ้างไหม?

สิ่งที่ฉันรู้คือว่าการเบี่ยงเบนสองครั้งเป็นตัวชี้ไปยังตัวชี้ ทำไมฉันต้องใช้ตัวชี้ไปยังตัวชี้?


49
ระวัง; วลี "ชี้คู่" double*ยังหมายถึงชนิด
Keith Thompson

จดบันทึก: คำตอบสำหรับคำถามนี้แตกต่างกันสำหรับ C และ C ++ - อย่าเพิ่ม c + tag กับคำถามที่เก่ามากนี้
BЈовић

คำตอบ:


479

หากคุณต้องการมีรายชื่อตัวละคร (คำ) คุณสามารถใช้ char *word

หากคุณต้องการรายการคำ (ประโยค) คุณสามารถใช้ char **sentence

หากคุณต้องการรายการของประโยค (คำพูดคนเดียว) คุณสามารถใช้ char ***monologue

หากคุณต้องการรายการสะสม (ชีวประวัติ) คุณสามารถใช้ char ****biography

หากคุณต้องการรายชื่อชีวประวัติ (ห้องสมุดชีวภาพ) คุณสามารถใช้ char *****biolibrary

หากคุณต้องการรายชื่อห้องสมุดชีวภาพ (a ?? lol) คุณสามารถใช้ char ******lol

... ...

ใช่ฉันรู้ว่าสิ่งเหล่านี้อาจไม่ใช่โครงสร้างข้อมูลที่ดีที่สุด


ตัวอย่างการใช้งานกับlol ที่น่าเบื่อมาก

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

int wordsinsentence(char **x) {
    int w = 0;
    while (*x) {
        w += 1;
        x++;
    }
    return w;
}

int wordsinmono(char ***x) {
    int w = 0;
    while (*x) {
        w += wordsinsentence(*x);
        x++;
    }
    return w;
}

int wordsinbio(char ****x) {
    int w = 0;
    while (*x) {
        w += wordsinmono(*x);
        x++;
    }
    return w;
}

int wordsinlib(char *****x) {
    int w = 0;
    while (*x) {
        w += wordsinbio(*x);
        x++;
    }
    return w;
}

int wordsinlol(char ******x) {
    int w = 0;
    while (*x) {
        w += wordsinlib(*x);
        x++;
    }
    return w;
}

int main(void) {
    char *word;
    char **sentence;
    char ***monologue;
    char ****biography;
    char *****biolibrary;
    char ******lol;

    //fill data structure
    word = malloc(4 * sizeof *word); // assume it worked
    strcpy(word, "foo");

    sentence = malloc(4 * sizeof *sentence); // assume it worked
    sentence[0] = word;
    sentence[1] = word;
    sentence[2] = word;
    sentence[3] = NULL;

    monologue = malloc(4 * sizeof *monologue); // assume it worked
    monologue[0] = sentence;
    monologue[1] = sentence;
    monologue[2] = sentence;
    monologue[3] = NULL;

    biography = malloc(4 * sizeof *biography); // assume it worked
    biography[0] = monologue;
    biography[1] = monologue;
    biography[2] = monologue;
    biography[3] = NULL;

    biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
    biolibrary[0] = biography;
    biolibrary[1] = biography;
    biolibrary[2] = biography;
    biolibrary[3] = NULL;

    lol = malloc(4 * sizeof *lol); // assume it worked
    lol[0] = biolibrary;
    lol[1] = biolibrary;
    lol[2] = biolibrary;
    lol[3] = NULL;

    printf("total words in my lol: %d\n", wordsinlol(lol));

    free(lol);
    free(biolibrary);
    free(biography);
    free(monologue);
    free(sentence);
    free(word);
}

เอาท์พุท:

คำทั้งหมดใน lol ของฉัน: 243

6
แค่อยากจะชี้ให้เห็นว่าไม่ได้เป็นarr[a][b][c] ***arrตัวชี้ของพอยน์เตอร์ใช้การอ้างอิงของการอ้างอิงในขณะที่arr[a][b][c]ถูกเก็บไว้เป็นอาร์เรย์ปกติในลำดับหลักของแถว
MCCCS

170

เหตุผลหนึ่งคือคุณต้องการเปลี่ยนค่าของตัวชี้ที่ส่งไปยังฟังก์ชันเป็นอาร์กิวเมนต์ของฟังก์ชันเพื่อทำสิ่งนี้คุณต้องใช้ตัวชี้เป็นตัวชี้

ในคำง่าย ๆใช้**เมื่อคุณต้องการรักษา (หรือคงการเปลี่ยนแปลง) การจัดสรรหน่วยความจำหรือการมอบหมายแม้อยู่นอกการเรียกใช้ฟังก์ชัน (ดังนั้นผ่านฟังก์ชั่นดังกล่าวด้วย ARG ตัวชี้คู่)

นี่อาจไม่ใช่ตัวอย่างที่ดีมาก แต่จะแสดงให้คุณเห็นถึงการใช้งานพื้นฐาน:

void allocate(int** p)
{
  *p = (int*)malloc(sizeof(int));
}

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}

14
อะไรจะแตกต่างกันถ้าจัดสรรvoid allocate(int *p)และคุณเรียกมันว่าallocate(p)อะไร?
アレックス

@AlexanderSupertramp ใช่ รหัสจะ segfault โปรดดูคำตอบของ Silviu
Abhishek

@Asha ความแตกต่างระหว่างการจัดสรร (p) และการจัดสรร (& p) คืออะไร?
user2979872

1
@Asha - เราไม่เพียงแค่ส่งคืนตัวชี้? หากเราต้องทำให้มันเป็นโมฆะแล้วกรณีการใช้งานจริงของสถานการณ์นี้คืออะไร?
Shabirmean

91
  • สมมติว่าคุณมีตัวชี้ ค่าของมันคือที่อยู่
  • แต่ตอนนี้คุณต้องการเปลี่ยนที่อยู่
  • คุณทำได้ โดยการทำpointer1 = pointer2คุณให้ตัวชี้ 1 ที่อยู่ของตัวชี้ 2
  • แต่! หากคุณทำสิ่งนั้นภายในฟังก์ชั่นและคุณต้องการให้ผลลัพธ์คงอยู่หลังจากที่ฟังก์ชันเสร็จสิ้นคุณต้องทำงานพิเศษบางอย่าง คุณต้องการตัวชี้ใหม่ 3 เพียงชี้ไปที่ตัวชี้ 1 pass pointer3 ไปยังฟังก์ชั่น

  • นี่คือตัวอย่าง ดูผลลัพธ์ด้านล่างก่อนเพื่อทำความเข้าใจ

#include <stdio.h>

int main()
{

    int c = 1;
    int d = 2;
    int e = 3;
    int * a = &c;
    int * b = &d;
    int * f = &e;
    int ** pp = &a;  // pointer to pointer 'a'

    printf("\n a's value: %x \n", a);
    printf("\n b's value: %x \n", b);
    printf("\n f's value: %x \n", f);
    printf("\n can we change a?, lets see \n");
    printf("\n a = b \n");
    a = b;
    printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
    printf("\n cant_change(a, f); \n");
    cant_change(a, f);
    printf("\n a's value is now: %x, Doh! same as 'b'...  that function tricked us. \n", a);

    printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
     printf("\n change(pp, f); \n");
    change(pp, f);
    printf("\n a's value is now: %x, YEAH! same as 'f'...  that function ROCKS!!!. \n", a);
    return 0;
}

void cant_change(int * x, int * z){
    x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}

void change(int ** x, int * z){
    *x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}

นี่คือผลลัพธ์: ( อ่านก่อนนี้ )

 a's value: bf94c204

 b's value: bf94c208 

 f's value: bf94c20c 

 can we change a?, lets see 

 a = b 

 a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see... 

 cant_change(a, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c208, Doh! same as 'b'...  that function tricked us. 

 NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' 

 change(pp, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c20c, YEAH! same as 'f'...  that function ROCKS!!!. 

4
นี่เป็นคำตอบที่ดีมากและช่วยให้ฉันเห็นภาพวัตถุประสงค์และประโยชน์ของตัวชี้ดับเบิล
Justin

1
@Justin คุณตรวจสอบคำตอบของฉันข้างต้นนี้หรือไม่ มันทำความสะอาด :)
Brian Joseph Spinos

10
คำตอบที่ดีเยี่ยมเพียงแค่ขาดการอธิบายว่า <code> void cant_change (int * x, int * z) </code> ล้มเหลวเนื่องจากพารามิเตอร์ 'ของมันเป็นเพียงตัวชี้ขอบเขตที่สร้างใหม่ในท้องถิ่นซึ่งเริ่มต้นด้วยตัวชี้ a และ f เช่นเดียวกัน เช่นเดียวกับ a และ f)
Pedro Reis

1
ง่าย? จริงๆ? ;)
alk

1
คำตอบนี้อธิบายการใช้พอยน์เตอร์พอยน์เตอร์ที่ใช้กันมากที่สุดด้วย
tonyjosi

48

การเพิ่มการตอบสนองของ Ashaถ้าคุณใช้ตัวชี้เดียวกับตัวอย่างร้อง (เช่น alloc1 ()) คุณจะสูญเสียการอ้างอิงไปยังหน่วยความจำที่จัดสรรภายในฟังก์ชัน

void alloc2(int** p) {
   *p = (int*)malloc(sizeof(int));
   **p = 10;
}

void alloc1(int* p) {
   p = (int*)malloc(sizeof(int));
   *p = 10;
}

int main(){
   int *p = NULL;
   alloc1(p);
   //printf("%d ",*p);//undefined
   alloc2(&p);
   printf("%d ",*p);//will print 10
   free(p);
   return 0;
}

เหตุผลที่มันเกิดขึ้นเช่นนี้คือในalloc1ตัวชี้จะถูกส่งผ่านโดยค่า ดังนั้นเมื่อมีการกำหนดใหม่ให้กับผลลัพธ์ของการmallocโทรภายในalloc1การเปลี่ยนแปลงจะไม่เกี่ยวข้องกับโค้ดในขอบเขตอื่น


1
จะเกิดอะไรขึ้นถ้า p เป็นตัวชี้จำนวนเต็มคงที่ รับข้อผิดพลาดการแบ่งกลุ่ม
kapilddit

free(p)ยังไม่พอคุณต้องทำif(p) free(*p)เช่นนั้นด้วย
Shijing Lv

@ShijingLv: ไม่*pให้คะแนนเพื่อintเก็บค่า 10 ผ่านสิ่งนี้ intเพื่อฟรี () `เป็นความคิดที่ไม่ดี
alk

การจัดสรรที่ทำในการalloc1()แนะนำหน่วยความจำรั่ว ค่าตัวชี้ที่จะส่งผ่านฟรีจะหายไปโดยกลับมาจากฟังก์ชัน
alk

ไม่จำเป็นต้องแสดงผลลัพธ์ของ malloc ใน C.
alk

23

วันนี้ฉันเห็นตัวอย่างที่ดีมากจากโพสต์บล็อกนี้เนื่องจากฉันสรุปไว้ด้านล่าง

ลองนึกภาพคุณมีโครงสร้างสำหรับโหนดในรายการที่เชื่อมโยงซึ่งอาจเป็น

typedef struct node
{
    struct node * next;
    ....
} node;

ตอนนี้คุณต้องการที่จะใช้remove_ifฟังก์ชั่นที่ยอมรับเกณฑ์การลบrmเป็นหนึ่งในข้อโต้แย้งและสำรวจรายการที่เชื่อมโยง: ถ้ารายการเป็นไปตามเกณฑ์ (เช่นrm(entry)==true) โหนดของมันจะถูกลบออกจากรายการ ในท้ายที่สุดremove_ifส่งกลับหัว (ซึ่งอาจแตกต่างจากหัวเดิม) ของรายการที่เชื่อมโยง

คุณอาจจะเขียน

for (node * prev = NULL, * curr = head; curr != NULL; )
{
    node * const next = curr->next;
    if (rm(curr))
    {
        if (prev)  // the node to be removed is not the head
            prev->next = next;
        else       // remove the head
            head = next;
        free(curr);
    }
    else
        prev = curr;
    curr = next;
}

เป็นforวงของคุณ ข้อความคือหากไม่มีพอยน์เตอร์คู่คุณต้องเก็บรักษาprevตัวแปรเพื่อจัดระเบียบพอยน์เตอร์ใหม่และจัดการกับทั้งสองกรณีที่แตกต่างกัน

แต่ด้วยพอยน์เตอร์สองตัวคุณสามารถเขียนได้จริง

// now head is a double pointer
for (node** curr = head; *curr; )
{
    node * entry = *curr;
    if (rm(entry))
    {
        *curr = entry->next;
        free(entry);
    }
    else
        curr = &entry->next;
}

คุณไม่ต้องการprevตอนนี้เพราะคุณสามารถแก้ไขสิ่งที่prev->nextชี้ไปโดยตรง

เพื่อให้สิ่งต่าง ๆ ชัดเจนขึ้นลองทำตามโค้ดสักหน่อย ระหว่างการกำจัด:

  1. ถ้าentry == *head: มันจะเป็น*head (==*curr) = *head->next- headตอนนี้ชี้ไปที่ตัวชี้ของโหนดส่วนหัวใหม่ คุณทำได้โดยการเปลี่ยนheadเนื้อหาของตัวชี้เป็นตัวชี้ใหม่โดยตรง
  2. ถ้า entry != *head : ในทำนองเดียวกัน*currคือสิ่งที่ชี้ไปและตอนนี้ชี้ไปที่prev->nextentry->next

ไม่ว่าในกรณีใดคุณสามารถจัดระเบียบพอยน์เตอร์อีกครั้งด้วยวิธีการรวมกันด้วยพอยน์เตอร์คู่


22

1. แนวคิดพื้นฐาน -

เมื่อคุณประกาศดังต่อไปนี้: -

1. char * ch - (เรียกว่าตัวชี้อักขระ)
- ch มีที่อยู่ของอักขระเดียว
- (* ch) จะพิจารณาค่าตามตัวอักษร ..

2. char ** ch -
'ch' มีที่อยู่ของ Array ของพอยน์เตอร์พอยน์เตอร์ (ดังใน 1)
'* ch' มีที่อยู่ของตัวละครเดียว (โปรดทราบว่ามันแตกต่างจาก 1 เนื่องจากความแตกต่างในการประกาศ)
(** ch) จะพิจารณาถึงค่าที่แท้จริงของตัวละคร ..

การเพิ่มพอยน์เตอร์ให้มากขึ้นจะขยายขนาดของประเภทข้อมูลจากอักขระไปยังสตริงไปยังอาร์เรย์ของสตริงและอื่น ๆ ... คุณสามารถเชื่อมโยงมิติข้อมูลกับเมทริกซ์ 1d, 2d, 3d

ดังนั้นการใช้งานตัวชี้จึงขึ้นอยู่กับว่าคุณประกาศอย่างไร

นี่คือรหัสง่ายๆ ..

int main()
{
    char **p;
    p = (char **)malloc(100);
    p[0] = (char *)"Apple";      // or write *p, points to location of 'A'
    p[1] = (char *)"Banana";     // or write *(p+1), points to location of 'B'

    cout << *p << endl;          //Prints the first pointer location until it finds '\0'
    cout << **p << endl;         //Prints the exact character which is being pointed
    *p++;                        //Increments for the next string
    cout << *p;
}

2. แอปพลิเคชั่นตัวชี้สองตัวอื่น -
(ซึ่งจะครอบคลุมถึงการอ้างอิงผ่าน)

สมมติว่าคุณต้องการอัปเดตอักขระจากฟังก์ชัน หากคุณลองทำสิ่งต่อไปนี้: -

void func(char ch)
{
    ch = 'B';
}

int main()
{
    char ptr;
    ptr = 'A';
    printf("%c", ptr);

    func(ptr);
    printf("%c\n", ptr);
}

ผลลัพธ์จะเป็น AA วิธีนี้ใช้ไม่ได้เนื่องจากคุณมี "ส่งผ่านมูลค่า" ไปยังฟังก์ชัน

วิธีที่ถูกต้องในการทำเช่นนั้นคือ -

void func( char *ptr)        //Passed by Reference
{
    *ptr = 'B';
}

int main()
{
    char *ptr;
    ptr = (char *)malloc(sizeof(char) * 1);
    *ptr = 'A';
    printf("%c\n", *ptr);

    func(ptr);
    printf("%c\n", *ptr);
}

ตอนนี้ขยายข้อกำหนดนี้สำหรับการอัพเดตสตริงแทนอักขระ
สำหรับสิ่งนี้คุณจะต้องได้รับพารามิเตอร์ในฟังก์ชั่นเป็นตัวชี้คู่

void func(char **str)
{
    strcpy(str, "Second");
}

int main()
{
    char **str;
    // printf("%d\n", sizeof(char));
    *str = (char **)malloc(sizeof(char) * 10);          //Can hold 10 character pointers
    int i = 0;
    for(i=0;i<10;i++)
    {
        str = (char *)malloc(sizeof(char) * 1);         //Each pointer can point to a memory of 1 character.
    }

    strcpy(str, "First");
    printf("%s\n", str);
    func(str);
    printf("%s\n", str);
}

ในตัวอย่างนี้เมธอดต้องการตัวชี้ดับเบิลเป็นพารามิเตอร์เพื่ออัพเดตค่าของสตริง


#include <stdio.h> int main() { char *ptr = 0; ptr = malloc(255); // allocate some memory strcpy( ptr, "Stack Overflow Rocks..!!"); printf("%s\n", ptr); printf("%d\n",strlen(ptr)); free(ptr); return 0; } แต่คุณสามารถทำได้โดยไม่ต้องใช้ตัวชี้คู่
Kumar

" char ** ch - 'ch' มีที่อยู่ของ Array ของพอยน์เตอร์พอยน์เตอร์ " ไม่มันมีที่อยู่ขององค์ประกอบที่ 1 ของอาเรย์พcharอยน์เตอร์ ตัวชี้ไปยังอาร์เรย์ของchar*จะได้รับการพิมพ์ตัวอย่างเช่นนี้char(*(*p)[42])กำหนดpเป็นตัวชี้ไปยังอาร์เรย์ของ 42 charชี้ไปยัง
alk

ตัวอย่างสุดท้ายเสียอย่างสมบูรณ์ สำหรับผู้เริ่ม: นี่*str = ... strคือพฤติกรรมที่ไม่ได้กำหนดที่กล่าวอ้าง
alk

สิ่งนี้malloc(sizeof(char) * 10);ไม่ได้จัดสรรห้องสำหรับตัวชี้ 10 ตัวcharแต่สำหรับ 10 charเท่านั้น ..
alk

วงนี้เฉียงไปใช้ดัชนีfor(i=0;i<10;i++) { str = ... i
alk

17

พอยน์เตอร์ไปยังพอยน์เตอร์ยังมีประโยชน์ในฐานะ "จับ" ไปยังหน่วยความจำที่คุณต้องการส่งผ่าน "จับ" ระหว่างฟังก์ชั่นไปยังหน่วยความจำที่ตั้งใหม่ได้ นั่นหมายความว่าฟังก์ชั่นสามารถเปลี่ยนหน่วยความจำที่ถูกชี้ไปที่ตัวชี้ภายในตัวแปรจับและทุกฟังก์ชั่นหรือวัตถุที่ใช้จับจะถูกชี้ไปที่หน่วยความจำ (หรือจัดสรร) ย้ายใหม่ ไลบรารี่ชอบทำสิ่งนี้ด้วยข้อมูลแบบ "ทึบ" ซึ่งเป็นประเภทข้อมูลที่คุณไม่ต้องกังวลเกี่ยวกับสิ่งที่พวกเขากำลังทำกับหน่วยความจำที่ถูกชี้คุณแค่ผ่าน "มือจับ" ระหว่าง ฟังก์ชั่นของห้องสมุดเพื่อดำเนินการบางอย่างกับหน่วยความจำ ...

ตัวอย่างเช่น

#include <stdlib.h>

typedef unsigned char** handle_type;

//some data_structure that the library functions would work with
typedef struct 
{
    int data_a;
    int data_b;
    int data_c;
} LIB_OBJECT;

handle_type lib_create_handle()
{
    //initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
    handle_type handle = malloc(sizeof(handle_type));
    *handle = malloc(sizeof(LIB_OBJECT) * 10);

    return handle;
}

void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }

void lib_func_b(handle_type handle)
{
    //does something that takes input LIB_OBJECTs and makes more of them, so has to
    //reallocate memory for the new objects that will be created

    //first re-allocate the memory somewhere else with more slots, but don't destroy the
    //currently allocated slots
    *handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);

    //...do some operation on the new memory and return
}

void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }

void lib_free_handle(handle_type handle) 
{
    free(*handle);
    free(handle); 
}


int main()
{
    //create a "handle" to some memory that the library functions can use
    handle_type my_handle = lib_create_handle();

    //do something with that memory
    lib_func_a(my_handle);

    //do something else with the handle that will make it point somewhere else
    //but that's invisible to us from the standpoint of the calling the function and
    //working with the handle
    lib_func_b(my_handle); 

    //do something with new memory chunk, but you don't have to think about the fact
    //that the memory has moved under the hood ... it's still pointed to by the "handle"
    lib_func_c(my_handle);

    //deallocate the handle
    lib_free_handle(my_handle);

    return 0;
}

หวังว่าจะช่วยได้

เจสัน


เหตุผลของการจัดการประเภทที่ไม่ได้ลงนามถ่าน ** คืออะไร? โมฆะจะใช้ได้เหมือนกันไหม
Connor Clark

5
unsigned charมีการใช้โดยเฉพาะเพราะเรากำลังเก็บตัวชี้ไปยังข้อมูลไบนารีที่จะแสดงเป็นไบต์ดิบ การใช้voidจะต้องมีการโยนในบางจุดและโดยทั่วไปจะไม่สามารถอ่านได้ตามเจตนาของสิ่งที่กำลังทำ
Jason

7

ตัวอย่างง่ายๆที่คุณอาจเคยเห็นมาหลายครั้งแล้ว

int main(int argc, char **argv)

ในพารามิเตอร์ที่สองคุณมีมัน: ตัวชี้ไปยังตัวชี้เพื่อถ่าน

โปรดทราบว่าสัญกรณ์พอยน์เตอร์ ( char* c) และสัญกรณ์อาร์เรย์ ( char c[]) สามารถใช้แทนกันในอาร์กิวเมนต์ของฟังก์ชัน char *argv[]ดังนั้นคุณยังสามารถเขียน กล่าวอีกนัยหนึ่งchar *argv[]และchar **argvสามารถใช้แทนกันได้

สิ่งที่แสดงข้างต้นคืออาร์เรย์ของลำดับอักขระ (อาร์กิวเมนต์บรรทัดคำสั่งที่กำหนดให้กับโปรแกรมเมื่อเริ่มต้น)

ดูคำตอบนี้สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับลายเซ็นฟังก์ชั่นด้านบน


2
"สัญกรณ์ชี้ ( char* c) และสัญกรณ์อาร์เรย์ ( char c[]) สามารถใช้แทนกัน" (และมีความหมายที่แน่นอนเดียวกัน) ในการขัดแย้งฟังก์ชั่น พวกเขาจะแตกต่างกันอย่างไรก็ตามข้อโต้แย้งฟังก์ชั่นนอก
pmg

6

สตริงเป็นตัวอย่างที่ดีของการใช้พอยน์เตอร์พอยน์เตอร์ สตริงนั้นเป็นตัวชี้ดังนั้นเมื่อใดก็ตามที่คุณต้องการชี้ไปที่สตริงคุณจะต้องมีตัวชี้คู่


5

ตัวอย่างเช่นคุณอาจต้องการตรวจสอบให้แน่ใจว่าเมื่อคุณเพิ่มหน่วยความจำของสิ่งที่คุณตั้งค่าตัวชี้เป็นโมฆะในภายหลัง

void safeFree(void** memory) {
    if (*memory) {
        free(*memory);
        *memory = NULL;
    }
}

เมื่อคุณเรียกใช้ฟังก์ชั่นนี้คุณจะเรียกมันด้วยที่อยู่ของตัวชี้

void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);

ตอนนี้myMemoryถูกตั้งค่าเป็น NULL และความพยายามใด ๆ ที่จะนำมาใช้ซ้ำจะผิดอย่างเห็นได้ชัด


1
มันควรจะเป็นif(*memory)และfree(*memory);
Asha

1
จุดดีการสูญเสียสัญญาณระหว่างสมองและคีย์บอร์ด ฉันได้ทำการแก้ไขเพื่อให้เข้าใจได้ง่ายขึ้น
Jeff Foster

ทำไมเราไม่สามารถทำสิ่งต่อไปนี้ ... void safeFree (void * memory) {if (memory) {free (memory); memory = NULL; }}
Peter_pk

@Peter_pk การกำหนดหน่วยความจำให้เป็นโมฆะจะไม่ช่วยเพราะคุณได้ผ่านตัวชี้ตามค่าไม่ใช่การอ้างอิง (ดังนั้นตัวอย่างของตัวชี้ไปยังตัวชี้)
Jeff Foster

2

ตัวอย่างเช่นหากคุณต้องการเข้าถึงข้อมูลที่ไม่ต่อเนื่องแบบสุ่ม

p -> [p0, p1, p2, ...]  
p0 -> data1
p1 -> data2

- ใน C

T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));

คุณเก็บตัวชี้pที่ชี้ไปยังอาร์เรย์ของพอยน์เตอร์ ตัวชี้แต่ละตัวชี้ไปยังส่วนของข้อมูล

หากsizeof(T)มีขนาดใหญ่อาจไม่สามารถจัดสรรบล็อกที่ต่อเนื่องกัน (เช่นใช้ malloc) เป็นsizeof(T) * nไบต์


1
ไม่จำเป็นต้องแสดงผลลัพธ์ของ malloc ใน C.
alk

2

สิ่งหนึ่งที่ฉันใช้พวกมันตลอดเวลาคือเมื่อฉันมีอาร์เรย์ของวัตถุและฉันต้องทำการค้นหา (การค้นหาแบบไบนารี) กับพวกเขาโดยสาขาที่แตกต่างกัน
ฉันเก็บอาร์เรย์เดิม ...

int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);

จากนั้นสร้างอาร์เรย์ของพอยน์เตอร์ที่เรียงลำดับกับวัตถุ

int compare_object_by_name( const void *v1, const void *v2 ) {
  OBJECT *o1 = *(OBJECT **)v1;
  OBJECT *o2 = *(OBJECT **)v2;
  return (strcmp(o1->name, o2->name);
}

OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
  int i = 0;
  for( ; i<num_objects; i++)
    object_ptrs_by_name[i] = original_array+i;
  qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);

คุณสามารถสร้างอาร์เรย์ของตัวชี้ที่เรียงลำดับได้มากเท่าที่คุณต้องการจากนั้นใช้การค้นหาแบบไบนารี่บนอาเรย์ตัวชี้ที่เรียงเพื่อเข้าถึงวัตถุที่คุณต้องการโดยข้อมูลที่คุณมี อาเรย์ของออบเจกต์ดั้งเดิมยังคงไม่เรียงลำดับ แต่อาเรย์ตัวชี้แต่ละอันจะถูกจัดเรียงตามฟิลด์ที่ระบุ


2

ทำไมต้องพอยน์เตอร์คู่

วัตถุประสงค์คือเพื่อเปลี่ยนสิ่งที่ studentA ชี้ไปโดยใช้ฟังก์ชัน

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


typedef struct Person{
    char * name;
} Person; 

/**
 * we need a ponter to a pointer, example: &studentA
 */
void change(Person ** x, Person * y){
    *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}

void dontChange(Person * x, Person * y){
    x = y;
}

int main()
{

    Person * studentA = (Person *)malloc(sizeof(Person));
    studentA->name = "brian";

    Person * studentB = (Person *)malloc(sizeof(Person));
    studentB->name = "erich";

    /**
     * we could have done the job as simple as this!
     * but we need more work if we want to use a function to do the job!
     */
    // studentA = studentB;

    printf("1. studentA = %s (not changed)\n", studentA->name);

    dontChange(studentA, studentB);
    printf("2. studentA = %s (not changed)\n", studentA->name);

    change(&studentA, studentB);
    printf("3. studentA = %s (changed!)\n", studentA->name);

    return 0;
}

/**
 * OUTPUT:
 * 1. studentA = brian (not changed)
 * 2. studentA = brian (not changed)
 * 3. studentA = erich (changed!)
 */

1
ไม่จำเป็นต้องแสดงผลลัพธ์ของ malloc ใน C.
alk

2

ต่อไปนี้เป็นเรื่องง่ายมากที่ C ++ ตัวอย่างที่แสดงให้เห็นว่าถ้าคุณต้องการที่จะใช้ฟังก์ชั่นที่จะตั้งตัวชี้ไปยังจุดที่จะวัตถุที่คุณต้องชี้ไปชี้ มิฉะนั้นตัวชี้จะเปลี่ยนเป็นโมฆะต่อไป

(คำตอบ C ++ แต่ฉันเชื่อว่ามันเหมือนกันใน C. )

(นอกจากนี้สำหรับการอ้างอิง: Google ("pass by value c ++") = "โดยค่าเริ่มต้นอาร์กิวเมนต์ใน C ++ จะถูกส่งผ่านโดยค่าเมื่ออาร์กิวเมนต์ถูกส่งผ่านตามค่าค่าของอาร์กิวเมนต์จะถูกคัดลอกไปยังพารามิเตอร์ของฟังก์ชัน")

ดังนั้นเราจึงต้องการที่จะตั้งตัวชี้เท่ากับสตริงba

#include <iostream>
#include <string>

void Function_1(std::string* a, std::string* b) {
  b = a;
  std::cout << (b == nullptr);  // False
}

void Function_2(std::string* a, std::string** b) {
  *b = a;
  std::cout << (b == nullptr);  // False
}

int main() {
  std::string a("Hello!");
  std::string* b(nullptr);
  std::cout << (b == nullptr);  // True

  Function_1(&a, b);
  std::cout << (b == nullptr);  // True

  Function_2(&a, &b);
  std::cout << (b == nullptr);  // False
}

// Output: 10100

สิ่งที่เกิดขึ้นที่เส้นFunction_1(&a, b);?

  • "มูลค่า" ของ&main::a(ที่อยู่) std::string* Function_1::aจะถูกคัดลอกลงในพารามิเตอร์ ดังนั้นจึงFunction_1::aเป็นตัวชี้ไป (เช่นที่อยู่ของหน่วยความจำ) main::aสตริง

  • "มูลค่า" ของmain::b(ที่อยู่ในความทรงจำ) std::string* Function_1::bจะถูกคัดลอกลงในพารามิเตอร์ ดังนั้นขณะนี้มี 2 ที่อยู่เหล่านี้ในหน่วยความจำทั้งตัวชี้โมฆะ ที่บรรทัดb = a;นั้นตัวแปรโลคัลFunction_1::bจะถูกเปลี่ยนเป็นเท่ากับFunction_1::a(= &main::a) แต่ตัวแปรmain::bไม่เปลี่ยนแปลง หลังจากที่การเรียกร้องให้Function_1, main::bก็ยังคงเป็นตัวชี้โมฆะ

สิ่งที่เกิดขึ้นที่เส้นFunction_2(&a, &b);?

  • การรักษาของaตัวแปรเดียวกัน: ภายในฟังก์ชั่นที่เป็นที่อยู่ของสตริงFunction_2::amain::a

  • แต่bตอนนี้ตัวแปรกำลังถูกส่งผ่านเป็นตัวชี้ไปยังตัวชี้ "มูลค่า" ของ&main::b(คนที่อยู่ของตัวชี้ main::b ) std::string** Function_2::bถูกคัดลอกลง ดังนั้นภายใน Function_2, dereferencing นี้เช่นการเข้าถึงและจะปรับเปลี่ยน*Function_2::b main::bดังนั้นบรรทัด*b = a;จึงตั้งค่าmain::b(ที่อยู่) เท่ากับFunction_2::a(= ที่อยู่ของmain::a) ซึ่งเป็นสิ่งที่เราต้องการ

หากคุณต้องการใช้ฟังก์ชั่นในการปรับเปลี่ยนสิ่งต่าง ๆ ไม่ว่าจะเป็นวัตถุหรือที่อยู่ (ตัวชี้) คุณจะต้องผ่านตัวชี้ไปยังสิ่งนั้น สิ่งที่คุณจริงผ่านไม่สามารถแก้ไขได้ (ในขอบเขตโทร) เพราะสำเนาทำ

(ข้อยกเว้นคือถ้าพารามิเตอร์เป็นการอ้างอิงเช่นstd::string& aแต่โดยปกติแล้วจะเป็นconstโดยทั่วไปถ้าคุณเรียกใช้f(x)ถ้าxเป็นวัตถุคุณควรจะสามารถสมมติว่าf จะไม่แก้ไขxแต่ถ้าxเป็นตัวชี้คุณควร สมมติว่าf อาจแก้ไขวัตถุที่ชี้ไปตามx)


รหัส C ++ ที่จะตอบคำถาม C ไม่ใช่ความคิดที่ดีที่สุด
alk

1

สายไปงานเลี้ยงเล็ก ๆ น้อย ๆ แต่หวังว่ามันจะช่วยให้ใครบางคน

ในอาร์เรย์ C จะจัดสรรหน่วยความจำบนสแต็กเสมอดังนั้นฟังก์ชั่นจะไม่สามารถคืนค่าอาร์เรย์ (ไม่คงที่) ได้เนื่องจากหน่วยความจำที่จัดสรรในสแต็กจะได้รับการปลดปล่อยโดยอัตโนมัติเมื่อการดำเนินการถึงจุดสิ้นสุดของบล็อกปัจจุบัน มันน่ารำคาญจริงๆเมื่อคุณต้องการจัดการกับอาร์เรย์สองมิติ (เช่นเมทริกซ์) และใช้ฟังก์ชั่นบางอย่างที่สามารถแก้ไขและคืนเมทริกซ์ได้ เพื่อให้บรรลุเป้าหมายนี้คุณสามารถใช้ตัวชี้ไปยังตัวชี้เพื่อนำเมทริกซ์ไปใช้กับหน่วยความจำที่จัดสรรแบบไดนามิก:

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    double** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols floats
    for(int i = 0; i < num_rows; i++){
        A[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(A[i] == NULL){
            for(int j = 0; j < i; j++){
                free(A[j]);
            }
            free(A);
            return NULL;
        }
    }
    return A;
} 

นี่คือภาพประกอบ:

double**       double*           double
             -------------       ---------------------------------------------------------
   A ------> |   A[0]    | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
             | --------- |       ---------------------------------------------------------
             |   A[1]    | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             |   A[i]    | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             | A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
             -------------       ---------------------------------------------------------

double-pointer-to-double-pointer A ชี้ไปที่องค์ประกอบแรก [0] ของบล็อกหน่วยความจำที่มีองค์ประกอบเป็นตัวชี้ดับเบิล คุณสามารถนึกพอยน์เตอร์พอยน์เตอร์เหล่านี้เป็นแถวของเมทริกซ์ นั่นคือเหตุผลที่ทุก ๆ ตัวชี้ดับเบิลจัดสรรหน่วยความจำสำหรับองค์ประกอบ num_cols ของประเภทคู่ นอกจากนี้ A [i] ชี้ไปยังแถวที่ i นั่นคือ A [i] ชี้ไปที่ A [i] [0] และนั่นเป็นเพียงองค์ประกอบสองครั้งแรกของบล็อกหน่วยความจำสำหรับแถวที่ i สุดท้ายคุณสามารถเข้าถึงองค์ประกอบในแถวที่ i และคอลัมน์ j-th ได้อย่างง่ายดายด้วย A [i] [j]

นี่คือตัวอย่างที่สมบูรณ์ที่แสดงให้เห็นถึงการใช้งาน:

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

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows double-pointers
    double** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols
    // doubles
    for(int i = 0; i < num_rows; i++){
        matrix[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(matrix[i] == NULL){
            for(int j = 0; j < i; j++){
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
    }
    return matrix;
}

/* Fills the matrix with random double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        free(matrix[i]);
    }
    free(matrix);
}

/* Outputs the matrix to the console */
void print_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < cols; j++){
            printf(" %- f ", matrix[i][j]);
        }
        printf("\n");
    }
}


int main(){
    srand(time(NULL));
    int m = 3, n = 3;
    double** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}

0

ฉันใช้พอยน์เตอร์พอยน์เตอร์วันนี้ในขณะที่ฉันกำลังเขียนโปรแกรมเพื่อทำงานดังนั้นฉันจึงสามารถตอบได้ว่าทำไมเราถึงต้องใช้มัน (เป็นครั้งแรกที่ฉันต้องใช้พอยน์เตอร์สองตัว) เราต้องจัดการกับการเข้ารหัสเฟรมแบบเรียลไทม์ที่มีอยู่ในบัฟเฟอร์ซึ่งเป็นสมาชิกของโครงสร้างบางอย่าง ในตัวเข้ารหัสเราต้องใช้ตัวชี้ไปยังหนึ่งในโครงสร้างเหล่านั้น ปัญหาคือว่าตัวชี้ของเราถูกเปลี่ยนให้ชี้ไปที่โครงสร้างอื่นจากเธรดอื่น เพื่อที่จะใช้โครงสร้างปัจจุบันในตัวเข้ารหัสฉันต้องใช้ตัวชี้คู่เพื่อที่จะชี้ไปยังตัวชี้ที่ถูกแก้ไขในเธรดอื่น ในตอนแรกมันไม่ชัดเจนอย่างน้อยก็สำหรับเราที่เราต้องใช้วิธีนี้ พิมพ์ที่อยู่จำนวนมากในกระบวนการ :))

คุณควรใช้พอยน์เตอร์คู่เมื่อคุณทำงานกับพอยน์เตอร์ที่เปลี่ยนไปในที่อื่น ๆ ของแอปพลิเคชันของคุณ คุณอาจพบว่าพอยน์เตอร์สองตัวเป็นสิ่งจำเป็นเมื่อคุณจัดการกับฮาร์ดแวร์ที่ส่งคืนและที่อยู่ให้กับคุณ


0

เปรียบเทียบการปรับเปลี่ยนค่าของตัวแปรเมื่อเทียบกับการปรับเปลี่ยนค่าของตัวชี้ :

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

void changeA(int (*a))
{
  (*a) = 10;
}

void changeP(int *(*P))
{
  (*P) = malloc(sizeof((*P)));
}

int main(void)
{
  int A = 0;

  printf("orig. A = %d\n", A);
  changeA(&A);
  printf("modi. A = %d\n", A);

  /*************************/

  int *P = NULL;

  printf("orig. P = %p\n", P);
  changeP(&P);
  printf("modi. P = %p\n", P);

  free(P);

  return EXIT_SUCCESS;
}

สิ่งนี้ช่วยให้ฉันหลีกเลี่ยงการคืนค่าของตัวชี้เมื่อตัวชี้ถูกแก้ไขโดยฟังก์ชันที่เรียกใช้ (ใช้ในรายการที่ลิงก์โดยลำพัง)

เก่า (ไม่ดี):

int *func(int *P)
{
  ...
  return P;
}

int main(void)
{
  int *pointer;
  pointer = func(pointer);
  ...
}    

ใหม่ (ดีกว่า):

void func(int **pointer)
{
  ...
}

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.