Tại sao sử dụng hai lần gián tiếp? hoặc Tại sao sử dụng con trỏ để con trỏ?


272

Khi nào nên sử dụng một chỉ định kép trong C? Bất cứ ai có thể giải thích với một ví dụ?

Những gì tôi biết là một cảm ứng kép là một con trỏ đến một con trỏ. Tại sao tôi cần một con trỏ đến một con trỏ?


49
Hãy cẩn thận; cụm từ "con trỏ kép" cũng dùng để chỉ loại double*.
Keith Thompson

1
Lưu ý: câu trả lời cho câu hỏi này khác với C và C ++ - không thêm thẻ c + vào câu hỏi rất cũ này.
Bовић

Câu trả lời:


479

Nếu bạn muốn có một danh sách các ký tự (một từ), bạn có thể sử dụng char *word

Nếu bạn muốn một danh sách các từ (một câu), bạn có thể sử dụng char **sentence

Nếu bạn muốn có một danh sách các câu (độc thoại), bạn có thể sử dụng char ***monologue

Nếu bạn muốn có một danh sách độc thoại (tiểu sử), bạn có thể sử dụng char ****biography

Nếu bạn muốn có một danh sách tiểu sử (thư viện sinh học), bạn có thể sử dụng char *****biolibrary

Nếu bạn muốn có một danh sách các thư viện sinh học (a ?? lol), bạn có thể sử dụng char ******lol

... ...

vâng, tôi biết đây có thể không phải là cấu trúc dữ liệu tốt nhất


Ví dụ sử dụng với một lol rất rất rất rất nhàm chán

#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);
}

Đầu ra:

tổng số từ trong lol của tôi: 243

7
Chỉ muốn chỉ ra rằng a arr[a][b][c]không phải là a ***arr. Con trỏ của con trỏ sử dụng các tham chiếu của các tham chiếu, trong khi arr[a][b][c]được lưu trữ như một mảng thông thường theo thứ tự chính hàng.
MCCCS

170

Một lý do là bạn muốn thay đổi giá trị của con trỏ được truyền cho một hàm làm đối số hàm, để làm điều này, bạn cần con trỏ tới một con trỏ.

Nói một cách đơn giản, Sử dụng **khi bạn muốn duy trì (HOẶC giữ lại thay đổi) Phân bổ bộ nhớ hoặc Phân công ngay cả bên ngoài lệnh gọi hàm.(Vì vậy, truyền hàm như vậy với con trỏ kép arg.)

Đây có thể không phải là một ví dụ hay, nhưng sẽ cho bạn thấy cách sử dụng cơ bản:

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

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

14
Điều gì sẽ khác nếu phân bổ void allocate(int *p)và bạn gọi nó là allocate(p)?
ア レ ッ

@AlexanderSupertramp Có. Mã sẽ segfault. Xin vui lòng xem câu trả lời của Silviu.
Abhishek

@Asha sự khác biệt giữa phân bổ (p) và phân bổ (& p) là gì?
dùng2979872

1
@Asha - Chúng ta không thể trả lại con trỏ? Nếu chúng ta phải giữ nó trống thì trường hợp sử dụng thực tế của kịch bản này là gì?
Shabirmean

91
  • Giả sử bạn có một con trỏ. Giá trị của nó là một địa chỉ.
  • nhưng bây giờ bạn muốn thay đổi địa chỉ đó.
  • bạn có thể. bằng cách làmpointer1 = pointer2 , bạn cung cấp cho con trỏ1 địa chỉ của con trỏ2.
  • nhưng! nếu bạn làm điều đó trong một hàm và bạn muốn kết quả được duy trì sau khi hàm được thực hiện, bạn cần thực hiện thêm một số công việc. bạn cần một con trỏ mới 3 chỉ để trỏ đến con trỏ1. truyền con trỏ3 cho hàm.

  • đây là một ví dụ. nhìn vào đầu ra dưới đây đầu tiên, để hiểu.

#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);
}

Đây là đầu ra: ( đọc cái này trước )

 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
Đây là một câu trả lời tuyệt vời và thực sự giúp tôi hình dung ra mục đích và sự hữu ích của một con trỏ kép.
Justin

1
@Justin bạn đã kiểm tra câu trả lời của tôi ở trên này chưa? nó sạch hơn :)
Brian Joseph Spinos

10
Câu trả lời tuyệt vời, chỉ thiếu để giải thích rằng <code> void cant_change (int * x, int * z) </ code> không thành công vì các tham số của nó chỉ là các con trỏ phạm vi cục bộ mới được khởi tạo tương tự a và f con trỏ (vì vậy chúng không phải là giống như a và f).
Pedro Reis

1
Đơn giản? Có thật không? ;)
kiềm

1
câu trả lời này thực sự giải thích một trong những cách sử dụng phổ biến nhất của con trỏ tới con trỏ, cảm ơn!
tonyjosi

49

Thêm vào phản hồi của Asha , nếu bạn sử dụng một con trỏ vào ví dụ dưới đây (ví dụ alloc1 ()), bạn sẽ mất tham chiếu đến bộ nhớ được phân bổ bên trong hàm.

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;
}

Lý do nó xảy ra như thế này là trong alloc1con trỏ được truyền bằng giá trị. Vì vậy, khi được gán lại cho kết quả của malloccuộc gọi bên trong alloc1, thay đổi không liên quan đến mã trong một phạm vi khác.


1
Điều gì xảy ra nếu p là con trỏ nguyên tĩnh? Bắt lỗi phân đoạn.
kapilddit

free(p)vẫn chưa đủ, bạn cũng cần if(p) free(*p)như vậy
Shijing Lv

@ShijingLv: Số *pđánh giá việc intgiữ giá trị 10, chuyển cái này intsang free () `là một ý tưởng tồi.
kiềm

Việc phân bổ được thực hiện trong alloc1()việc giới thiệu một rò rỉ bộ nhớ. Giá trị con trỏ được truyền miễn phí bị mất bằng cách trả về từ hàm.
kiềm

Không (!) Cần bỏ kết quả của malloc trong C.
kiềm

23

Tôi đã thấy một ví dụ rất tốt ngày hôm nay, từ bài đăng trên blog này , như tôi tóm tắt dưới đây.

Hãy tưởng tượng bạn có một cấu trúc cho các nút trong danh sách được liên kết, có thể là

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

Bây giờ bạn muốn thực hiện một remove_ifhàm, chấp nhận một tiêu chí loại bỏ rmlà một trong những đối số và đi qua danh sách được liên kết: nếu một mục thỏa mãn tiêu chí (giống như rm(entry)==true), nút của nó sẽ bị xóa khỏi danh sách. Cuối cùng, remove_iftrả về đầu (có thể khác với đầu ban đầu) của danh sách được liên kết.

Bạn có thể viết

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;
}

như forvòng lặp của bạn . Thông điệp là, không có con trỏ kép, bạn phải duy trì một prevbiến để tổ chức lại các con trỏ và xử lý hai trường hợp khác nhau.

Nhưng với con trỏ kép, bạn thực sự có thể viết

// 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;
}

Bạn không cần prevngay bây giờ vì bạn có thể trực tiếp sửa đổi những gì được prev->nextchỉ ra .

Để làm cho mọi thứ rõ ràng hơn, hãy làm theo mã một chút. Trong quá trình gỡ bỏ:

  1. if entry == *head: nó sẽ là *head (==*curr) = *head->next- headbây giờ trỏ đến con trỏ của nút tiêu đề mới. Bạn làm điều này bằng cách trực tiếp thay đổi headnội dung của một con trỏ mới.
  2. if entry != *head: tương tự, *currlà những gì được prev->nextchỉ ra, và bây giờ trỏ đến entry->next.

Bất kể trong trường hợp nào, bạn có thể tổ chức lại các con trỏ theo cách thống nhất với các con trỏ kép.


22

1. Khái niệm cơ bản -

Khi bạn khai báo như sau: -

1. char * ch - (được gọi là con trỏ ký tự)
- ch chứa địa chỉ của một ký tự.
- (* ch) sẽ quy định giá trị của nhân vật ..

2. char ** ch -
'ch' chứa địa chỉ của một mảng các con trỏ ký tự. (như trong 1)
'* ch' chứa địa chỉ của một ký tự. (Lưu ý rằng nó khác với 1, do sự khác biệt trong khai báo).
(** ch) sẽ quy định giá trị chính xác của nhân vật ..

Thêm nhiều con trỏ mở rộng kích thước của kiểu dữ liệu, từ ký tự sang chuỗi, đến mảng chuỗi, v.v ... Bạn có thể liên kết nó với ma trận 1d, 2d, 3d ..

Vì vậy, việc sử dụng con trỏ phụ thuộc vào cách bạn khai báo nó.

Đây là một mã đơn giản ..

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. Một ứng dụng khác của con trỏ kép -
(điều này cũng sẽ bao gồm cả việc chuyển qua tham chiếu)

Giả sử bạn muốn cập nhật một ký tự từ một hàm. Nếu bạn thử như sau: -

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

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

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

Đầu ra sẽ là AA. Điều này không hoạt động, vì bạn đã "Truyền theo giá trị" cho hàm.

Cách chính xác để làm điều đó sẽ là -

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);
}

Bây giờ mở rộng yêu cầu này để cập nhật một chuỗi thay vì ký tự.
Đối với điều này, bạn cần nhận tham số trong hàm dưới dạng con trỏ kép.

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);
}

Trong ví dụ này, phương thức mong đợi một con trỏ kép làm tham số để cập nhật giá trị của chuỗi.


#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; } Nhưng bạn có thể làm điều đó mà không cần sử dụng con trỏ kép quá.
kumar

" Char ** ch - 'ch' chứa địa chỉ của một mảng các con trỏ ký tự. " Không, nó chứa địa chỉ của phần tử thứ nhất của một mảng các charcon trỏ. Một con trỏ tới một mảng char*sẽ được gõ ví dụ như thế này: char(*(*p)[42])định nghĩa plà con trỏ tới một mảng gồm 42 con trỏ tới char.
kiềm

Đoạn cuối bị hỏng hoàn toàn. Đối với người mới bắt đầu: Dưới đây *str = ... strlà quy định chưa được xác nhận yêu cầu hành vi không xác định.
kiềm

Điều malloc(sizeof(char) * 10);này không phân bổ chỗ cho 10 con trỏ tới charmà chỉ dành cho 10 char..
kiềm

Vòng lặp này for(i=0;i<10;i++) { str = ... bỏ lỡ để sử dụng chỉ mụci .
kiềm

17

Con trỏ tới con trỏ cũng có ích như "tay cầm" cho bộ nhớ mà bạn muốn chuyển qua "tay cầm" giữa các chức năng để định vị lại bộ nhớ. Về cơ bản, điều đó có nghĩa là hàm có thể thay đổi bộ nhớ được trỏ bởi con trỏ bên trong biến xử lý và mọi hàm hoặc đối tượng đang sử dụng tay cầm sẽ trỏ đúng vào bộ nhớ mới được định vị lại (hoặc được cấp phát). Các thư viện thích làm điều này với các kiểu dữ liệu "mờ", đó là các kiểu dữ liệu mà bạn không phải lo lắng về những gì họ đang làm với bộ nhớ được làm, bạn chỉ cần chuyển qua "xử lý" giữa các chức năng của thư viện để thực hiện một số thao tác trên bộ nhớ đó ...

Ví dụ:

#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;
}

Hi vọng điêu nay co ich,

Jason


Lý do cho loại xử lý là unsign char ** là gì? Sẽ làm mất hiệu lực ** cũng như vậy?
Connor Clark

5
unsigned charđược sử dụng cụ thể vì chúng tôi đang lưu trữ một con trỏ tới dữ liệu nhị phân sẽ được biểu diễn dưới dạng byte thô. Việc sử dụng voidsẽ yêu cầu diễn viên tại một số điểm và thường không thể đọc được như mục đích của những gì đang được thực hiện.
Jason

7

Ví dụ đơn giản mà bạn có thể đã thấy nhiều lần trước đây

int main(int argc, char **argv)

Trong tham số thứ hai, bạn có nó: con trỏ tới con trỏ tới char.

Lưu ý rằng ký hiệu con trỏ ( char* c) và ký hiệu mảng ( char c[]) có thể hoán đổi cho nhau trong các đối số hàm. Vì vậy, bạn cũng có thể viết char *argv[]. Nói cách khác char *argv[]char **argvcó thể thay thế cho nhau.

Trên thực tế, những gì ở trên là một mảng các chuỗi ký tự (các đối số dòng lệnh được đưa cho một chương trình khi khởi động).

Xem thêm câu trả lời này để biết thêm chi tiết về chữ ký chức năng trên.


2
"Ký hiệu con trỏ ( char* c) và ký hiệu mảng ( char c[]) có thể hoán đổi cho nhau" (và có cùng ý nghĩa chính xác) trong các đối số hàm . Chúng khác nhau tuy nhiên đối số chức năng bên ngoài.
PMG

6

Chuỗi là một ví dụ tuyệt vời về việc sử dụng con trỏ kép. Bản thân chuỗi là một con trỏ, vì vậy bất cứ khi nào bạn cần trỏ đến một chuỗi, bạn sẽ cần một con trỏ kép.


5

Ví dụ, bạn có thể muốn đảm bảo rằng khi bạn giải phóng bộ nhớ của một cái gì đó, bạn đặt con trỏ thành null sau đó.

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

Khi bạn gọi hàm này, bạn sẽ gọi nó bằng địa chỉ của một con trỏ

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

Bây giờ myMemoryđược đặt thành NULL và mọi nỗ lực sử dụng lại sẽ rất rõ ràng là sai.


1
nó nên if(*memory)free(*memory);
Asha

1
Điểm tốt, mất tín hiệu giữa não và bàn phím. Tôi đã chỉnh sửa nó để có ý nghĩa hơn một chút.
Jeff Foster

Tại sao chúng ta không thể làm như sau ... void safeFree (void * memory) {if (memory) {free (memory); bộ nhớ = NULL; }}
Peter_pk

@Peter_pk Gán bộ nhớ thành null sẽ không giúp ích gì vì bạn đã truyền con trỏ theo giá trị, không phải bằng tham chiếu (do đó là ví dụ về con trỏ cho con trỏ).
Jeff Foster

2

Chẳng hạn, nếu bạn muốn truy cập ngẫu nhiên vào dữ liệu không liên tục.

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

- trong C

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

Bạn lưu trữ một con trỏ trỏ pđến một mảng các con trỏ. Mỗi con trỏ trỏ đến một phần dữ liệu.

Nếu sizeof(T)lớn, có thể không thể phân bổ một khối liền kề (tức là sử dụng malloc) sizeof(T) * nbyte.


1
Không (!) Cần bỏ kết quả của malloc trong C.
kiềm

2

Một điều tôi sử dụng chúng liên tục là khi tôi có một mảng các đối tượng và tôi cần thực hiện tra cứu (tìm kiếm nhị phân) trên chúng theo các trường khác nhau.
Tôi giữ mảng ban đầu ...

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

Sau đó tạo một mảng các con trỏ được sắp xếp đến các đối tượng.

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);

Bạn có thể thực hiện bao nhiêu mảng con trỏ được sắp xếp mà bạn cần, sau đó sử dụng tìm kiếm nhị phân trên mảng con trỏ được sắp xếp để truy cập vào đối tượng bạn cần bằng dữ liệu bạn có. Mảng ban đầu của các đối tượng có thể không được sắp xếp, nhưng mỗi mảng con trỏ sẽ được sắp xếp theo trường được chỉ định của chúng.


2

Tại sao con trỏ kép?

Mục tiêu là thay đổi những gì studentA trỏ đến, sử dụng một hàm.

#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
Không (!) Cần bỏ kết quả của malloc trong C.
kiềm

2

Sau đây là một ví dụ C ++ rất đơn giản cho thấy rằng nếu bạn muốn sử dụng một hàm để đặt một con trỏ trỏ đến một đối tượng, bạn cần một con trỏ tới một con trỏ . Nếu không, con trỏ sẽ tiếp tục trở về null .

(Câu trả lời của C ++, nhưng tôi tin rằng nó giống với C.)

(Ngoài ra, để tham khảo: Google ("pass by value c ++") = "Theo mặc định, các đối số trong C ++ được truyền theo giá trị. Khi một đối số được truyền bằng giá trị, giá trị của đối số được sao chép vào tham số của hàm.")

Vì vậy, chúng tôi muốn đặt con trỏ bbằng chuỗi a.

#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

Điều gì xảy ra ở dòng Function_1(&a, b);?

  • "Giá trị" của &main::a(một địa chỉ) được sao chép vào tham số std::string* Function_1::a. Do đó, Function_1::alà một con trỏ tới (tức là địa chỉ bộ nhớ của) chuỗi main::a.

  • "Giá trị" của main::b(một địa chỉ trong bộ nhớ) được sao chép vào tham số std::string* Function_1::b. Do đó, hiện có 2 trong số các địa chỉ này trong bộ nhớ, cả hai con trỏ null. Tại dòng b = a;, biến cục bộ Function_1::bsau đó được thay đổi thành bằng Function_1::a(= &main::a), nhưng biến main::bkhông thay đổi. Sau cuộc gọi đến Function_1, main::bvẫn là một con trỏ null.

Điều gì xảy ra ở dòng Function_2(&a, &b);?

  • Cách xử lý của abiến là như nhau: trong hàm, Function_2::alà địa chỉ của chuỗi main::a.

  • Nhưng biến bhiện đang được truyền dưới dạng con trỏ đến con trỏ. "Giá trị" của &main::b( địa chỉ của con trỏ main::b ) được sao chép vào std::string** Function_2::b. Do đó, trong Function_2, hãy bỏ qua phần này để *Function_2::btruy cập và sửa đổi main::b. Vì vậy, dòng *b = a;thực sự là thiết lập main::b(một địa chỉ) bằng Function_2::a(= địa chỉ của main::a) đó là những gì chúng ta muốn.

Nếu bạn muốn sử dụng một hàm để sửa đổi một vật, có thể là một đối tượng hoặc một địa chỉ (con trỏ), bạn phải chuyển một con trỏ tới vật đó. Điều mà bạn thực sự vượt qua không thể được sửa đổi (trong phạm vi cuộc gọi) vì một bản sao cục bộ được tạo.

(Một ngoại lệ là nếu tham số là một tài liệu tham khảo, chẳng hạn như std::string& a. Nhưng thông thường đây là những const. Nói chung, nếu bạn gọi f(x), nếu xlà một đối tượng bạn sẽ có thể giả định rằng f sẽ không thay đổi x. Nhưng nếu xlà một con trỏ, sau đó bạn nên giả sử rằng f có thể sửa đổi đối tượng được chỉ bởi x.)


Mã C ++ để trả lời một câu hỏi C không phải là ý tưởng tốt nhất.
kiềm

1

Một chút muộn để dự tiệc, nhưng hy vọng điều này sẽ giúp được ai đó.

Trong các mảng C luôn phân bổ bộ nhớ trên ngăn xếp, do đó, một hàm không thể trả về một mảng (không tĩnh) do thực tế là bộ nhớ được phân bổ trên ngăn xếp sẽ được giải phóng tự động khi thực hiện đến cuối khối hiện tại. Điều đó thực sự khó chịu khi bạn muốn xử lý các mảng hai chiều (tức là ma trận) và thực hiện một vài chức năng có thể thay đổi và trả về ma trận. Để đạt được điều này, bạn có thể sử dụng một con trỏ tới con trỏ để thực hiện một ma trận với bộ nhớ được cấp phát động:

/* 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;
} 

Đây là một minh họ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] |
             -------------       ---------------------------------------------------------

Con trỏ kép đến con trỏ kép A trỏ đến phần tử đầu tiên A [0] của khối bộ nhớ có các phần tử là chính con trỏ kép. Bạn có thể tưởng tượng những con trỏ kép này là các hàng của ma trận. Đó là lý do tại sao mọi con trỏ kép phân bổ bộ nhớ cho các phần tử num_cols của kiểu double. Hơn nữa, A [i] trỏ đến hàng thứ i, tức là A [i] trỏ đến A [i] [0] và đó chỉ là phần tử kép đầu tiên của khối bộ nhớ cho hàng thứ i. Cuối cùng, bạn có thể dễ dàng truy cập phần tử trong hàng thứ i và cột thứ j với A [i] [j].

Đây là một ví dụ hoàn chỉnh thể hiện việc sử dụng:

#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

Tôi đã sử dụng con trỏ kép ngày hôm nay khi tôi đang lập trình một cái gì đó cho công việc, vì vậy tôi có thể trả lời tại sao chúng ta phải sử dụng chúng (đó là lần đầu tiên tôi thực sự phải sử dụng con trỏ kép). Chúng tôi đã phải đối phó với mã hóa thời gian thực của các khung có trong bộ đệm là thành viên của một số cấu trúc. Trong bộ mã hóa, chúng ta phải sử dụng một con trỏ tới một trong các cấu trúc đó. Vấn đề là con trỏ của chúng tôi đã được thay đổi để trỏ đến các cấu trúc khác từ một luồng khác. Để sử dụng cấu trúc hiện tại trong bộ mã hóa, tôi phải sử dụng một con trỏ kép, để trỏ đến con trỏ đang được sửa đổi trong một luồng khác. Ban đầu, rõ ràng là chúng tôi phải thực hiện phương pháp này. Rất nhiều địa chỉ đã được in trong quá trình :)).

Bạn NÊN sử dụng con trỏ kép khi bạn làm việc trên các con trỏ được thay đổi ở những nơi khác trong ứng dụng của bạn. Bạn cũng có thể thấy con trỏ kép là điều bắt buộc khi bạn xử lý phần cứng trả về và giải quyết cho bạn.


0

So sánh giá trị sửa đổi của biến so với giá trị sửa đổi của con trỏ :

#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;
}

Điều này giúp tôi tránh trả về giá trị của con trỏ khi con trỏ được sửa đổi bởi hàm được gọi (được sử dụng trong danh sách liên kết đơn).

TUỔI (xấu):

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

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

MỚI (tốt hơn):

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

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.