Зачем использовать двойной указатель? или Зачем использовать указатели для указателей?

Когда следует использовать двойной указатель в C? Может ли кто-нибудь объяснить на примере?

Я знаю, что двойной указатель является указателем на указатель. Зачем мне нужен указатель на указатель?

Если вы хотите иметь список символов (слово), вы можете использовать char *word

Если вам нужен список слов (предложение), вы можете использовать предложение char **sentence

Если вам нужен список предложений (monoлог), вы можете использовать char ***monologue

Если вы хотите список monoлогов (биография), вы можете использовать char ****biography

Если вам нужен список биографий (био-библиотека), вы можете использовать char *****biolibrary

Если вы хотите список био-библиотек (a lol), вы можете использовать char ******lol

… …

да, я знаю, что это могут быть не лучшие структуры данных

Одна из причин заключается в том, что вы хотите изменить значение указателя, переданного функции как аргумент функции, для этого вам потребуется указатель на указатель.

Простыми словами: Используйте ** когда вы хотите сохранить (ИЛИ сохранить изменение) выделение или присвоение памяти даже вне вызова функции. (Итак, передайте такую ​​функцию с аргументом double pointer arg.)

Это может быть не очень хороший пример, но покажет вам основное использование:

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

Вот ПРОСТОЙ ответ !!!!

  • скажем, у вас есть указатель, что его значение является адресом.
  • но теперь вы хотите изменить этот адрес.
  • вы могли бы, выполнив pointer1 = pointer2, а указатель1 теперь получит адрес указателя2.
  • НО! если вы хотите, чтобы функция выполняла это для вас, и вы хотите, чтобы результат сохранялся после выполнения функции, вам нужна дополнительная работа, вам нужен новый указатель3, чтобы указать на указатель1 и передать указатель3 функции.

  • вот забавный пример (сначала посмотрите на выход ниже, чтобы понять!):

 #include  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("\na's value: %x \n", a); printf("\nb's value: %x \n", b); printf("\nf's value: %x \n", f); printf("\n can we change a?, lets see \n"); printf("\na = b \n"); a = b; printf("\na'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("\na'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("\na'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 = ba'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!!!. 

Добавляя к ответу 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; alloc1(p); //printf("%d ",*p);//value is undefined alloc2(&p); printf("%d ",*p);//will print 10 free(p); return 0; } 

Причина, по которой это происходит, заключается в том, что в alloc1 указатель передается по значению. Таким образом, когда он переназначается на результат вызова malloc внутри alloc1 , это изменение не относится к коду в другой области.

1. Основная концепция –

Когда вы заявляете следующее: –

1. char * ch – (указатель символа)
– ch содержит адрес одного символа.
– (* ch) приведет к разыменованию значения символа.

2. char ** ch –
‘ch’ содержит адрес массива указателей символов. (как в 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); } 

В этом примере метод ожидает двойной указатель в качестве параметра для обновления значения строки.

Сегодня я увидел очень хороший пример из этого сообщения в блоге , как я резюмирую ниже.

Представьте, что у вас есть структура для узлов в связанном списке, что, вероятно,

 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->nexthead теперь указывает на указатель нового узла заголовка. Вы делаете это, напрямую изменяя содержимое head на новый указатель.
  2. если entry != *head : аналогично, *curr – это то, что prev->next указывает на, и теперь указывает на entry->next .

Независимо от того, в каком случае, вы можете повторно организовать указатели в унифицированном виде с помощью двух указателей.

Указатели на указатели также пригождаются как «ручки» в память, где вы хотите передать «дескриптор» между функциями, чтобы переустановить память. Это в основном означает, что функция может изменить память, на которую указывает указатель внутри переменной дескриптора, и каждая функция или объект, который использует дескриптор, правильно укажут на вновь перемещенную (или выделенную) память. Библиотеки любят делать это с «непрозрачными» типами данных, то есть типами данных, вам не нужно беспокоиться о том, что они делают, когда указывается память, вы просто обходите «дескриптор» между функции библиотеки для выполнения некоторых операций с этой памятью … библиотечные функции могут выделять и де-распределять память под капотом, не заставляя явно беспокоиться о процессе управления памятью или о том, где указывает ручка.

Например:

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

Надеюсь это поможет,

Джейсон

Строки – отличный пример использования двойных указателей. Сама строка является указателем, поэтому в любое время, когда вам нужно указывать на строку, вам понадобится двойной указатель.

Ниже приведен очень простой пример C ++, который показывает, что если вы хотите использовать функцию для указания указателя на объект, вам нужен указатель на указатель . В противном случае указатель будет продолжать возвращаться к нулевому значению .

(Ответ на C ++, но я считаю, что это то же самое в C.)

(Кроме того, для справки: Google («pass by value c ++») = «По умолчанию аргументы в C ++ передаются по значению. Когда аргумент передается по значению, значение аргумента копируется в параметр функции.»)

Поэтому мы хотим установить указатель b равным строке a .

 #include  #include  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 . Поэтому в настоящее время в памяти два этих адреса, оба нулевые указатели. На линии b = a; , локальная переменная Function_1::b затем изменяется на равную Function_1::a (= &main::a ), но переменная main::b не изменяется. После вызова Function_1 main::b по-прежнему является нулевым указателем.

Что происходит на линии Function_2(&a, &b); ?

  • Обработка переменной одинакова: внутри функции Function_2::a является адресом строки main::a .

  • Но переменная b теперь передается как указатель на указатель. «Значение» &main::b ( адрес указателя main::b ) копируется в std::string** Function_2::b . Поэтому в Function_2 разыменование этого как *Function_2::b будет иметь доступ и изменение main::b . Итак, строка *b = a; на самом деле устанавливает main::b (адрес), равный Function_2::a (= адрес main::a ), который мы хотим.

Если вы хотите использовать функцию для изменения вещи, будь то объект или адрес (указатель), вы должны передать указатель на эту вещь. Вещь, которую вы фактически проходите, не может быть изменена (в области вызова), поскольку создается локальная копия.

(Исключение - если параметр является ссылкой, например std::string& a . Но обычно это const . Обычно, если вы вызываете f(x) , если x является объектом, вы можете предположить, что f выиграл ' t изменить x . Но если x является указателем, вы должны предположить, что f может изменить объект, на который указывает x .)

Простой пример, который вы, вероятно, видели много раз

 int main(int argc, char **argv) 

Во втором параметре у вас есть: указатель на указатель на char.

Обратите внимание, что нотация указателя ( char* c ) и нотация массива ( char c[] ) взаимозаменяемы в аргументах функции. Таким образом, вы также можете написать char *argv[] . Другими словами, char *argv[] и char **argv взаимозаменяемы.

То, что указано выше, на самом деле представляет собой массив последовательностей символов (аргументы командной строки, которые передаются программе при запуске).

См. Также этот ответ для получения дополнительной информации о вышеуказанной сигнатуре функции.

Например, вы можете захотеть убедиться, что когда вы освободите память от чего-то, вы установите указатель на нуль после этого.

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

Когда вы вызываете эту функцию, вы вызываете ее с адресом указателя

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

Теперь myMemory установлен в NULL, и любая попытка повторного использования будет очень ошибочной.

Например, если вам нужен произвольный доступ к несмежным данным.

 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 , указывающий на массив указателей. Каждый указатель указывает на fragment данных.

Если sizeof(T) велико, может быть невозможно выделить смежный блок (т. Е. Используя malloc) sizeof(T) * n байтов.

Я использую их постоянно, когда у меня есть массив объектов, и мне нужно выполнять поиск (бинарный поиск) на них разными полями.
Я сохраняю исходный массив …

 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 

Вы можете сделать столько отсортированных массивов указателей, сколько вам нужно, а затем использовать двоичный поиск в отсортированном массиве указателей для доступа к объекту, который вам нужен, данными. Исходный массив объектов может оставаться несортированным, но каждый массив указателей будет отсортирован по заданному полю.

Сегодня я использовал двойные указатели, в то время как я программировал что-то для работы, поэтому я могу ответить, почему мы должны были их использовать (это первый раз, когда мне приходилось использовать двойные указатели). Нам приходилось иметь дело с кодированием в реальном времени фреймов, содержащихся в буферах, которые являются членами некоторых структур. В кодере мы должны были использовать указатель на одну из этих структур. Проблема заключалась в том, что наш указатель менялся, чтобы указывать на другие структуры из другого streamа. Чтобы использовать текущую структуру в кодере, мне пришлось использовать двойной указатель, чтобы указать на указатель, который был изменен в другом streamе. Сначала не было очевидно, по крайней мере для нас, что мы должны были придерживаться такого подхода. В этом процессе было напечатано много адресов :)).

СЛЕДУЕТ использовать двойные указатели при работе с указателями, которые были изменены в других местах вашего приложения. Вы также можете найти двойные указатели, чтобы быть обязательным, когда вы имеете дело с аппаратным обеспечением, которое возвращает вас и обращается к вам.

Почему двойные указатели?

objective состоит в том, чтобы изменить то, на что указывает ученикA, используя функцию.

 #include  #include  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!) */ 

Как сказано, одним из применений double poinnter является обновление строки, так что сделанные изменения отражаются обратно.

 #include  #include  // for using strcpy using namespace std; void change(char **temp) { strcpy(temp[0],"new"); strcpy(temp[1],"value"); } int main() { char **str; str = (char **)malloc(sizeof(char *)*3); str[0]=(char *)malloc(10); str[1]=(char *)malloc(10); strcpy(str[0],"old"); strcpy(str[1],"name"); char **temp = str; // always use the temporary variable while(*temp!=NULL) { cout<<*temp< в #include  #include  // for using strcpy using namespace std; void change(char **temp) { strcpy(temp[0],"new"); strcpy(temp[1],"value"); } int main() { char **str; str = (char **)malloc(sizeof(char *)*3); str[0]=(char *)malloc(10); str[1]=(char *)malloc(10); strcpy(str[0],"old"); strcpy(str[1],"name"); char **temp = str; // always use the temporary variable while(*temp!=NULL) { cout<<*temp< в #include  #include  // for using strcpy using namespace std; void change(char **temp) { strcpy(temp[0],"new"); strcpy(temp[1],"value"); } int main() { char **str; str = (char **)malloc(sizeof(char *)*3); str[0]=(char *)malloc(10); str[1]=(char *)malloc(10); strcpy(str[0],"old"); strcpy(str[1],"name"); char **temp = str; // always use the temporary variable while(*temp!=NULL) { cout<<*temp< 

Надеемся, что следующий пример очистит некоторые понятия относительно указателей и двойных указателей, их различий и использования в общих сценариях.

  int* setptr(int *x) { printf("%u\n",&x); x=malloc(sizeof(int)); *x=1; return x; } In the above function setptr we can manipulate x either 1. by taking fn arg as int *x , doing malloc and setting value of x and return x Or 2. By taking arg as int ** and malloc and then set **x value to some value. Note: we cant set any general pointer directly without doing malloc.Pointer indicates that it is a type of variable which can hold address of any data type.Now either we define a variable and give reference to it or we declare a pointer(int *x=NULL) and allocate some memory to it inside the called function where we pass x or a reference to it .. In either case we need to have address of a memory in the pointer and in the case pointer initially points to NULL or it is defined like int *x where it points to any random address then we need to assign a valid memory address to pointer 1. either we need to allocate memory to it by malloc int *x=NULL means its address is 0. Now we need to either o following 1. void main() { int *x; x=malloc *x=some_val; } Or void main() { int *x Fn(x); } void Fn(int **x) { *x=malloc; **x=5; } OR int * Fn(int *x) { x=malloc(); *x=4; Return x; } 2. Or we need to point it to a valid memory like a defined variable inside the function where pointer is defined. OR int main() { int a; int *x=&a; Fn(x); printf("%d",*x); } void Fn(int *x) { *x=2; } in both cases value pointed by x is changed inside fn But suppose if we do like int main() { int *x=NULL; printf("%u\n",sizeof(x)); printf("%u\n",&x); x=setptr(x); //*x=2; printf("%d\n",*x); return 0; } /* output 4 1 */ #include void setptr(int *x) { printf("inside setptr\n"); printf("x=%u\n",x); printf("&x=%u\n",&x); //x=malloc(sizeof(int)); *x=1; //return x; } int main() { int *x=NULL; printf("x=%u\n",x); printf("&x=%u\n",&x); int a; x=&a; printf("x=%u\n",x); printf("&a=%u\n",&a); printf("&x=%u\n",&x); setptr(x); printf("inside main again\n"); //*x=2; printf("x=%u\n",x); printf("&x=%u\n",&x); printf("*x=%d\n",*x); printf("a=%d\n",a); return 0; } 

применение двойного указателя, как показано Бхавуком Матуром, кажется неправильным. Здесь следующий пример – действительный

 void func(char **str) { strcpy(str[0],"second"); } int main(){ char **str; str = (char **)malloc(sizeof(char*)*1); // allocate 1 char* or string str[0] = (char *)malloc(sizeof(char)*10); // allocate 10 character strcpy(str[0],"first"); // assign the string printf("%s\n",*str); func(str); printf("%s\n",*str); free(str[0]); free(str); } 

следующий пример, который я даю, даст представление или интуицию о том, как работают двойные указатели, я пройду шаги

 1) try to understand the following statements char **str ; a) str is of type char ** whose value is an address of another pointer. b) *str is of type char * whose value is an address of variable or (it is a string itself). c) **str is of type char ,gives the value stored, in this case a character. 

ниже приведен код, к которому вы можете относиться к вышеуказанным точкам (a, b, c), чтобы понять

 str = (char **)malloc(sizeof(char *) *2); // here i am assigning the mem for two char * str[0]=(char *)"abcdefghij"; // assignin the value str[1]=(char *)"xyzlmnopqr"; 

теперь, чтобы напечатать значение, т. е. строки в массиве, просто посмотрите на точку b. В случае строки значение так же, как и адрес, будет таким же, поэтому нет необходимости разыгрывать его снова.

 cout<<*str< 

теперь, чтобы напечатать следующую строку, выйдите из одного разыменования, т.е. (*), от * str до str, а затем увеличивайте, как показано ниже

 str++; 

теперь напечатаем строку

 cout<<*str< 

теперь для печати только символов в строке, см. пункт c)

 cout<<**str< 

теперь для печати следующего символа строки, т.е. «b» выходят из 1 оператора разыменования и увеличивают его, т.е. переходя от ** str к * str и do * str ++

 *str++; 

теперь напечатайте символ

 cout<<**str< 

поскольку два массива («abcdefghij», «xylmnopqr») хранятся в непрерывном блоке памяти, если это делается для увеличения адреса, все символы двух строк будут распечатаны

  • Является ли указатель с правильным адресом и типом все еще всегда действительным указателем с C ++ 17?
  • Разница между char * и const char *?
  • поведение malloc (0)
  • Является ли стандартный мандат преобразованием переменной lvalue-rval переменной указателя при применении косвенности?
  • В чем разница между дальними указателями и ближайшими указателями?
  • Создать указатель на двумерный массив
  • Что происходит с самим указателем после удаления?
  • Изменение адреса, содержащегося в указателе с использованием функции
  • Возвращаемые массивы / указатели от функции
  • Какова цель выделения определенного объема памяти для массивов в C ++?
  • преобразование двумерного массива в указатель на указатель
  • Interesting Posts
    Давайте будем гением компьютера.