Быть осторожен; фраза «двойной указатель» также относится к типу double*.
Кит Томпсон
1
Обратите внимание: ответ на этот вопрос различен для C и C ++ - не добавляйте тег c + к этому очень старому вопросу.
BЈовић
Ответы:
479
Если вы хотите иметь список символов (слово), вы можете использовать char *word
Если вы хотите список слов (предложение), вы можете использовать char **sentence
Если вы хотите список предложений (монолог), вы можете использовать char ***monologue
Если вы хотите список монологов (биография), вы можете использовать char ****biography
Если вы хотите список биографий (биобиблиотеки), вы можете использовать char *****biolibrary
Если вы хотите список биобиблиотек (a lol), вы можете использовать char ******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);}
Просто хотел отметить, что это arr[a][b][c]не так ***arr. Указатель указателей использует ссылки ссылок, в то время arr[a][b][c]как хранится как обычный массив в порядке следования строк.
MCCCS
170
Одна из причин заключается в том, что вы хотите изменить значение указателя, переданного функции в качестве аргумента функции, для этого вам потребуется указатель на указатель.
Проще говоря, используйте, **если вы хотите сохранить (ИЛИ сохранить изменение) Распределение памяти или Назначение даже вне вызова функции. (Итак, передайте такую функцию с двойным указателем arg.)
Это может быть не очень хороший пример, но покажет вам основное использование:
что было бы иначе, если бы выделить были void allocate(int *p)и вы назвали это как allocate(p)?
ス レ ッ ク ス
@AlexanderSupertramp Да. Код будет segfault. Пожалуйста, смотрите ответ Сильвиу.
Абхишек
@ Аша, в чем разница между allocate (p) и allocate (& p)?
user2979872
1
@ Аша - Разве мы не можем просто вернуть указатель? Если мы должны сохранить его недействительным, то каков практический вариант использования этого сценария?
Shabirmean
91
Допустим, у вас есть указатель. Его значение - это адрес.
но теперь вы хотите изменить этот адрес.
ты мог. тем самым pointer1 = pointer2вы даете указателю1 адрес указателя2.
но! если вы делаете это внутри функции и хотите, чтобы результат сохранялся после выполнения функции, вам нужно проделать дополнительную работу. вам нужен новый указатель3 только для указания на указатель1. передать указатель 3 в функцию.
вот пример. сначала посмотрите на вывод ниже, чтобы понять.
#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);return0;}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!!!.
Это отличный ответ, который действительно помог мне визуализировать цель и полезность двойного указателя.
Джастин
1
@ Джастин, ты проверил мой ответ над этим? его чище :)
Брайан Джозеф Спинос
10
Отличный ответ, просто не хватает объяснения того, что <code> void cant_change (int * x, int * z) </ code> не работает, потому что его параметры - это просто новые локальные указатели на области видимости, которые инициализируются аналогично указателям a и f (поэтому они не являются такой же как а и е).
Педро Рейс
1
Просто? В самом деле? ;)
алк
1
этот ответ действительно объясняет одно из наиболее распространенного использования указателей на указатели, спасибо!
Tonyjosi
50
В дополнение к ответу Аши , если вы используете единственный указатель на приведенный ниже пример (например, alloc1 ()), вы потеряете ссылку на память, выделенную внутри функции.
Причина, по которой это происходит, заключается в alloc1том, что указатель передается по значению. Таким образом, когда оно переназначается на результат mallocвызова внутри alloc1, изменение не относится к коду в другой области видимости.
Теперь вы хотите реализовать 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 pointerfor(node** curr = head;*curr;){
node * entry =*curr;if(rm(entry)){*curr = entry->next;
free(entry);}else
curr =&entry->next;}
Вам не нужно prevсейчас, потому что вы можете напрямую изменить то, prev->nextна что указывает .
Чтобы прояснить ситуацию, давайте немного следуем коду. Во время удаления:
if entry == *head: это будет *head (==*curr) = *head->next- headтеперь указывает на указатель нового узла заголовка. Вы делаете это путем непосредственного изменения headсодержимого на новый указатель.
if entry != *head: аналогично, *currэто то , на что prev->nextуказывает, а теперь указывает на entry->next.
Независимо от того, в каком случае вы можете реорганизовать указатели единым образом с двойными указателями.
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**str){
strcpy(str,"Second");}int main(){char**str;// printf("%d\n", sizeof(char));*str =(char**)malloc(sizeof(char)*10);//Can hold 10 character pointersint 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; } Но вы можете сделать это без использования двойного указателя тоже.
Кумар
" char ** ch - 'ch' содержит адрес массива указателей символов. " Нет, он содержит адрес 1-го элемента массива charуказателей. Указатель на массив char*будет напечатан, например, так: char(*(*p)[42])определяется pкак указатель на массив из 42 указателей на char.
алк
Последний фрагмент полностью сломан. Для начала: здесь *str = ...strразыменовывается неинициализированный вызов неопределенного поведения.
алк
Это malloc(sizeof(char) * 10);не выделяет место для 10 указателя, charно charтолько для 10 ..
алк
Этот цикл for(i=0;i<10;i++) { str = ... пропускает использование индекса i.
алк
17
Указатели на указатели также пригодятся в качестве «дескрипторов» памяти, где вы хотите передать «дескриптор» между функциями в перемещаемую память. По сути, это означает, что функция может изменять память, на которую указывает указатель внутри переменной handle, и каждая функция или объект, использующий дескриптор, будет правильно указывать на вновь перемещенную (или выделенную) память. Библиотеки любят делать это с «непрозрачными» типами данных, то есть с типами данных, если вам не нужно беспокоиться о том, что они делают с указанной памятью, вы просто передаете «дескриптор» между функции библиотеки для выполнения некоторых операций с этой памятью ...
Например:
#include<stdlib.h>typedefunsignedchar** handle_type;//some data_structure that the library functions would work withtypedefstruct{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);return0;}
В чем причина того, что типом дескриптора является unsigned char **? Будет ли void ** работать так же хорошо?
Коннор Кларк
5
unsigned charспециально используется, потому что мы храним указатель на двоичные данные, которые будут представлены в виде необработанных байтов. Использование voidпотребует приведения в некоторый момент, и, как правило, не так читаемо, как намерение того, что делается.
Джейсон
7
Простой пример, который вы, наверное, видели много раз
int main(int argc,char**argv)
Во втором параметре он есть: указатель на указатель на символ.
Обратите внимание, что указатель notation ( char* c) и массив notation ( char c[]) взаимозаменяемы в аргументах функции. Таким образом, вы могли бы также написать char *argv[]. Другими словами char *argv[]и char **argvвзаимозаменяемы.
То, что представлено выше, на самом деле представляет собой массив последовательностей символов (аргументы командной строки, которые передаются программе при запуске).
Смотрите также этот ответ для более подробной информации о вышеупомянутой функции подписи.
«Указатель notation ( char* c) и массив notation ( char c[]) взаимозаменяемы» (и имеют одинаковое точное значение) в аргументах функции . Однако они отличаются от внешних аргументов функции.
pmg
6
Строки являются отличным примером использования двойных указателей. Сама строка является указателем, поэтому каждый раз, когда вам нужно указать на строку, вам понадобится двойной указатель.
Хороший момент, потеря сигнала между мозгом и клавиатурой. Я отредактировал это, чтобы иметь немного больше смысла.
Джефф Фостер
Почему мы не можем сделать следующее ... void safeFree (void * memory) {if (memory) {free (memory); память = NULL; }}
Peter_pk
@Peter_pk Назначение памяти для нуля не поможет, потому что вы передали указатель по значению, а не по ссылке (отсюда и пример указателя на указатель).
Джефф Фостер
2
Например, если вы хотите произвольный доступ к несмежным данным.
p ->[p0, p1, p2,...]
p0 -> data1
p1 -> data2
- в С
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байтов.
Я постоянно использую их, когда у меня есть массив объектов, и мне нужно выполнить поиск (бинарный поиск) по ним по разным полям.
Я сохраняю оригинальный массив ...
int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);
Затем создайте массив отсортированных указателей на объекты.
Вы можете создать столько отсортированных массивов указателей, сколько вам нужно, а затем использовать двоичный поиск в отсортированном массиве указателей, чтобы получить доступ к нужному объекту по имеющимся у вас данным. Исходный массив объектов может остаться несортированным, но каждый массив указателей будет отсортирован по указанному полю.
Цель состоит в том, чтобы изменить то, на что ученик указывает, используя функцию.
#include<stdio.h>#include<stdlib.h>typedefstructPerson{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);return0;}/**
* OUTPUT:
* 1. studentA = brian (not changed)
* 2. studentA = brian (not changed)
* 3. studentA = erich (changed!)
*/
Ниже приведен очень простой пример C ++, который показывает, что если вы хотите использовать функцию для установки указателя, указывающего на объект, вам нужен указатель на указатель . В противном случае указатель будет возвращаться к нулю .
(Ответ на C ++, но я считаю, что в Си тоже самое)
(Также для справки: Google («передать по значению c ++») = «По умолчанию аргументы в C ++ передаются по значению. Когда аргумент передается по значению, значение аргумента копируется в параметр функции.»)
Поэтому мы хотим установить указатель bравным строке a.
«Значение» &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по - прежнему является указателем NULL.
Что происходит на линии Function_2(&a, &b);?
Обработка aпеременной одинакова: внутри функции 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не будет изменять x. Но если xэто указатель, то вы должны предположим, что это fможет изменить объект, на который указывает x.)
Немного опоздал на вечеринку, но, надеюсь, это кому-нибудь поможет.
В массивах C всегда выделяется память в стеке, поэтому функция не может возвращать (нестатический) массив из-за того, что память, выделенная в стеке, освобождается автоматически, когда выполнение достигает конца текущего блока. Это действительно раздражает, когда вы хотите иметь дело с двумерными массивами (то есть матрицами) и реализовать несколько функций, которые могут изменять и возвращать матрицы. Чтобы достичь этого, вы можете использовать указатель-указатель для реализации матрицы с динамически распределенной памятью:
/* Initializes a matrix */double** init_matrix(int num_rows,int num_cols){// Allocate memory for num_rows float-pointersdouble** A = calloc(num_rows,sizeof(double*));// return NULL if the memory couldn't allocatedif(A == NULL)return NULL;// For each double-pointer (row) allocate memory for num_cols floatsfor(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 memoryif(A[i]== NULL){for(int j =0; j < i; j++){
free(A[j]);}
free(A);return NULL;}}return A;}
Двойной указатель на двойной указатель A указывает на первый элемент A [0] блока памяти, элементы которого сами являются двойными указателями. Вы можете представить эти двойные указатели как строки матрицы. Вот почему каждый двойной указатель выделяет память для элементов num_cols типа double. Кроме того, A [i] указывает на i-ю строку, то есть A [i] указывает на A [i] [0], и это только первый двойной элемент блока памяти для i-й строки. Наконец, вы можете легко получить доступ к элементу в i-й строке и j-м столбце с помощью 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-pointersdouble** matrix = calloc(num_rows,sizeof(double*));// return NULL if the memory couldn't allocatedif(matrix == NULL)return NULL;// For each double-pointer (row) allocate memory for num_cols// doublesfor(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 memoryif(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);return0;}
Я использовал двойные указатели сегодня, когда программировал что-то для работы, поэтому я могу ответить, почему мы должны их использовать (это первый раз, когда мне фактически пришлось использовать двойные указатели). Нам пришлось иметь дело с кодированием кадров в реальном времени, содержащихся в буферах, которые являются членами некоторых структур. В кодировщике нам пришлось использовать указатель на одну из этих структур. Проблема заключалась в том, что наш указатель изменялся, чтобы указывать на другие структуры из другого потока. Чтобы использовать текущую структуру в кодировщике, мне пришлось использовать двойной указатель, чтобы указывать на указатель, который был изменен в другом потоке. Сначала не было очевидно, по крайней мере для нас, что мы должны были использовать этот подход. Много адресов было напечатано в процессе :)).
Вы ДОЛЖНЫ использовать двойные указатели, когда работаете с указателями, которые изменены в других местах вашего приложения. Вы также можете найти двойные указатели обязательными при работе с оборудованием, которое возвращает и обращается к вам.
double*
.Ответы:
Если вы хотите иметь список символов (слово), вы можете использовать
char *word
Если вы хотите список слов (предложение), вы можете использовать
char **sentence
Если вы хотите список предложений (монолог), вы можете использовать
char ***monologue
Если вы хотите список монологов (биография), вы можете использовать
char ****biography
Если вы хотите список биографий (биобиблиотеки), вы можете использовать
char *****biolibrary
Если вы хотите список биобиблиотек (a lol), вы можете использовать
char ******lol
... ...
да, я знаю, что это могут быть не самые лучшие структуры данных
Пример использования с очень очень очень скучным лол
Вывод:
источник
arr[a][b][c]
не так***arr
. Указатель указателей использует ссылки ссылок, в то времяarr[a][b][c]
как хранится как обычный массив в порядке следования строк.Одна из причин заключается в том, что вы хотите изменить значение указателя, переданного функции в качестве аргумента функции, для этого вам потребуется указатель на указатель.
Проще говоря, используйте,
**
если вы хотите сохранить (ИЛИ сохранить изменение) Распределение памяти или Назначение даже вне вызова функции. (Итак, передайте такую функцию с двойным указателем arg.)Это может быть не очень хороший пример, но покажет вам основное использование:
источник
void allocate(int *p)
и вы назвали это какallocate(p)
?pointer1 = pointer2
вы даете указателю1 адрес указателя2.но! если вы делаете это внутри функции и хотите, чтобы результат сохранялся после выполнения функции, вам нужно проделать дополнительную работу. вам нужен новый указатель3 только для указания на указатель1. передать указатель 3 в функцию.
вот пример. сначала посмотрите на вывод ниже, чтобы понять.
Вот результат: ( сначала прочтите это )
источник
В дополнение к ответу Аши , если вы используете единственный указатель на приведенный ниже пример (например, alloc1 ()), вы потеряете ссылку на память, выделенную внутри функции.
Причина, по которой это происходит, заключается в
alloc1
том, что указатель передается по значению. Таким образом, когда оно переназначается на результатmalloc
вызова внутриalloc1
, изменение не относится к коду в другой области видимости.источник
free(p)
не достаточно, вамif(p) free(*p)
тоже нужно*p
Оцениваетint
значение 10, передаваяint
его free () `- плохая идея.alloc1()
вводит утечку памяти. Значение указателя, которое должно быть передано свободно, теряется при возврате из функции.Сегодня я увидел очень хороший пример из этого поста в блоге , который я подытожил ниже.
Представьте, что у вас есть структура для узлов в связанном списке, которая, вероятно,
Теперь вы хотите реализовать
remove_if
функцию, которая принимает критерий удаления вrm
качестве одного из аргументов и пересекает связанный список: если запись удовлетворяет критерию (что-то вродеrm(entry)==true
), ее узел будет удален из списка. В концеremove_if
возвращает заголовок (который может отличаться от исходного заголовка) связанного списка.Вы можете написать
как ваша
for
петля. Сообщение состоит в том, что без двойных указателей вам нужно поддерживатьprev
переменную для реорганизации указателей и обрабатывать два разных случая.Но с двойными указателями, вы можете написать
Вам не нужно
prev
сейчас, потому что вы можете напрямую изменить то,prev->next
на что указывает .Чтобы прояснить ситуацию, давайте немного следуем коду. Во время удаления:
entry == *head
: это будет*head (==*curr) = *head->next
-head
теперь указывает на указатель нового узла заголовка. Вы делаете это путем непосредственного измененияhead
содержимого на новый указатель.entry != *head
: аналогично,*curr
это то , на чтоprev->next
указывает, а теперь указывает наentry->next
.Независимо от того, в каком случае вы можете реорганизовать указатели единым образом с двойными указателями.
источник
1. Основная концепция -
Когда вы заявляете следующее: -
1. char * ch - (вызываемый символьный указатель)
- ch содержит адрес одного символа.
- (* ch) будет ссылаться на значение символа.
2. char ** ch -
'ch' содержит адрес массива указателей символов. (как в 1)
'* ch' содержит адрес одного символа. (Обратите внимание, что это отличается от 1, из-за разницы в объявлении).
(** ch) будет разыменовывать точное значение символа.
Добавление дополнительных указателей расширяет размерность типа данных, от символа до строки, до массива строк и т. Д. Вы можете связать его с 1d, 2d, 3d матрицей.
Таким образом, использование указателя зависит от того, как вы его объявите.
Вот простой код
2. Другое применение двойных указателей -
(это также относится к передаче по ссылке)
Предположим, вы хотите обновить персонажа из функции. Если вы попробуете следующее: -
Выход будет АА. Это не работает, так как вы «передали по значению» в функцию.
Правильный способ сделать это будет -
Теперь расширьте это требование для обновления строки вместо символа.
Для этого вам необходимо получить параметр в функции в виде двойного указателя.
В этом примере метод ожидает двойной указатель в качестве параметра для обновления значения строки.
источник
#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; }
Но вы можете сделать это без использования двойного указателя тоже.char
указателей. Указатель на массивchar*
будет напечатан, например, так:char(*(*p)[42])
определяетсяp
как указатель на массив из 42 указателей наchar
.*str = ...
str
разыменовывается неинициализированный вызов неопределенного поведения.malloc(sizeof(char) * 10);
не выделяет место для 10 указателя,char
ноchar
только для 10 ..for(i=0;i<10;i++) { str = ...
пропускает использование индексаi
.Указатели на указатели также пригодятся в качестве «дескрипторов» памяти, где вы хотите передать «дескриптор» между функциями в перемещаемую память. По сути, это означает, что функция может изменять память, на которую указывает указатель внутри переменной handle, и каждая функция или объект, использующий дескриптор, будет правильно указывать на вновь перемещенную (или выделенную) память. Библиотеки любят делать это с «непрозрачными» типами данных, то есть с типами данных, если вам не нужно беспокоиться о том, что они делают с указанной памятью, вы просто передаете «дескриптор» между функции библиотеки для выполнения некоторых операций с этой памятью ...
Например:
Надеюсь это поможет,
Джейсон
источник
unsigned char
специально используется, потому что мы храним указатель на двоичные данные, которые будут представлены в виде необработанных байтов. Использованиеvoid
потребует приведения в некоторый момент, и, как правило, не так читаемо, как намерение того, что делается.Простой пример, который вы, наверное, видели много раз
Во втором параметре он есть: указатель на указатель на символ.
Обратите внимание, что указатель notation (
char* c
) и массив notation (char c[]
) взаимозаменяемы в аргументах функции. Таким образом, вы могли бы также написатьchar *argv[]
. Другими словамиchar *argv[]
иchar **argv
взаимозаменяемы.То, что представлено выше, на самом деле представляет собой массив последовательностей символов (аргументы командной строки, которые передаются программе при запуске).
Смотрите также этот ответ для более подробной информации о вышеупомянутой функции подписи.
источник
char* c
) и массив notation (char c[]
) взаимозаменяемы» (и имеют одинаковое точное значение) в аргументах функции . Однако они отличаются от внешних аргументов функции.Строки являются отличным примером использования двойных указателей. Сама строка является указателем, поэтому каждый раз, когда вам нужно указать на строку, вам понадобится двойной указатель.
источник
Например, вы можете убедиться, что когда вы освобождаете память чего-либо, вы впоследствии устанавливаете указатель на ноль.
Когда вы вызываете эту функцию, вы вызываете ее с адресом указателя.
Сейчас
myMemory
установлено значение NULL, и любая попытка его повторного использования будет совершенно очевидно неверной.источник
if(*memory)
иfree(*memory);
Например, если вы хотите произвольный доступ к несмежным данным.
- в С
Вы храните указатель,
p
который указывает на массив указателей. Каждый указатель указывает на часть данных.Если
sizeof(T)
оно велико, может быть невозможно выделить непрерывный блок (т. Е. Используя malloc)sizeof(T) * n
байтов.источник
Я постоянно использую их, когда у меня есть массив объектов, и мне нужно выполнить поиск (бинарный поиск) по ним по разным полям.
Я сохраняю оригинальный массив ...
Затем создайте массив отсортированных указателей на объекты.
Вы можете создать столько отсортированных массивов указателей, сколько вам нужно, а затем использовать двоичный поиск в отсортированном массиве указателей, чтобы получить доступ к нужному объекту по имеющимся у вас данным. Исходный массив объектов может остаться несортированным, но каждый массив указателей будет отсортирован по указанному полю.
источник
Почему двойные указатели?
Цель состоит в том, чтобы изменить то, на что ученик указывает, используя функцию.
источник
Ниже приведен очень простой пример C ++, который показывает, что если вы хотите использовать функцию для установки указателя, указывающего на объект, вам нужен указатель на указатель . В противном случае указатель будет возвращаться к нулю .
(Ответ на C ++, но я считаю, что в Си тоже самое)
(Также для справки: Google («передать по значению c ++») = «По умолчанию аргументы в C ++ передаются по значению. Когда аргумент передается по значению, значение аргумента копируется в параметр функции.»)
Поэтому мы хотим установить указатель
b
равным строкеa
.Что происходит на линии
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
по - прежнему является указателем NULL.Что происходит на линии
Function_2(&a, &b);
?Обработка
a
переменной одинакова: внутри функции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
не будет изменятьx
. Но еслиx
это указатель, то вы должны предположим, что этоf
может изменить объект, на который указываетx
.)источник
Немного опоздал на вечеринку, но, надеюсь, это кому-нибудь поможет.
В массивах C всегда выделяется память в стеке, поэтому функция не может возвращать (нестатический) массив из-за того, что память, выделенная в стеке, освобождается автоматически, когда выполнение достигает конца текущего блока. Это действительно раздражает, когда вы хотите иметь дело с двумерными массивами (то есть матрицами) и реализовать несколько функций, которые могут изменять и возвращать матрицы. Чтобы достичь этого, вы можете использовать указатель-указатель для реализации матрицы с динамически распределенной памятью:
Вот иллюстрация:
Двойной указатель на двойной указатель A указывает на первый элемент A [0] блока памяти, элементы которого сами являются двойными указателями. Вы можете представить эти двойные указатели как строки матрицы. Вот почему каждый двойной указатель выделяет память для элементов num_cols типа double. Кроме того, A [i] указывает на i-ю строку, то есть A [i] указывает на A [i] [0], и это только первый двойной элемент блока памяти для i-й строки. Наконец, вы можете легко получить доступ к элементу в i-й строке и j-м столбце с помощью A [i] [j].
Вот полный пример, который демонстрирует использование:
источник
Я использовал двойные указатели сегодня, когда программировал что-то для работы, поэтому я могу ответить, почему мы должны их использовать (это первый раз, когда мне фактически пришлось использовать двойные указатели). Нам пришлось иметь дело с кодированием кадров в реальном времени, содержащихся в буферах, которые являются членами некоторых структур. В кодировщике нам пришлось использовать указатель на одну из этих структур. Проблема заключалась в том, что наш указатель изменялся, чтобы указывать на другие структуры из другого потока. Чтобы использовать текущую структуру в кодировщике, мне пришлось использовать двойной указатель, чтобы указывать на указатель, который был изменен в другом потоке. Сначала не было очевидно, по крайней мере для нас, что мы должны были использовать этот подход. Много адресов было напечатано в процессе :)).
Вы ДОЛЖНЫ использовать двойные указатели, когда работаете с указателями, которые изменены в других местах вашего приложения. Вы также можете найти двойные указатели обязательными при работе с оборудованием, которое возвращает и обращается к вам.
источник
Сравните изменяемое значение переменной с изменяющимся значением указателя :
Это помогло мне избежать возврата значения указателя, когда указатель был изменен вызываемой функцией (используется в односвязном списке).
СТАРЫЙ (плохо):
NEW (лучше):
источник