Нет, не домашнее задание .... просто хотел знать .. потому что я часто вижу это, когда читаю код на Си.
1
Указатель на указатель не является частным случаем чего-либо, поэтому я не понимаю, что вы не понимаете в void **.
akappa
для двумерных массивов лучшим примером является командная строка args "prog arg1 arg2" сохраняется char ** argv. И если вызывающая сторона не хочет выделять память (вызываемая функция выделяет память)
результаты
1
У вас есть хороший пример использования «указатель на указатель» в Git 2.0: см. Мой ответ ниже
VonC
Ответы:
359
Давайте предположим, что 8-битный компьютер с 8-битными адресами (и, следовательно, только 256 байтов памяти). Это часть этой памяти (цифры вверху - это адреса):
54555657585960616263646566676869+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+||58|||63||55||| h | e | l | l | o | \0 ||+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
Здесь вы можете видеть, что по адресу 63 начинается строка «привет». Таким образом, в этом случае, если это единственное появление «привет» в памяти, то,
constchar*c ="hello";
... определяет cкак указатель на (только для чтения) строку "hello" и, таким образом, содержит значение 63. cСамо должно быть где-то сохранено: в приведенном выше примере в расположении 58. Конечно, мы можем указывать не только на символы , но и к другим указателям. Например:
constchar**cp =&c;
Теперь cpуказывает на то c, что он содержит адрес c(который составляет 58). Мы можем пойти еще дальше. Рассматривать:
constchar***cpp =&cp;
Теперь cppхранит адрес cp. Таким образом, оно имеет значение 55 (на основе приведенного выше примера), и вы уже догадались: оно само хранится по адресу 60.
Что касается того, почему каждый использует указатели на указатели:
Имя массива обычно дает адрес его первого элемента. Таким образом, если массив содержит элементы типа t, ссылка на массив имеет тип t *. Теперь рассмотрим массив массивов типа t: естественно, ссылка на этот 2D-массив будет иметь тип (t *)*= t **и, следовательно, является указателем на указатель.
Несмотря на то, что массив строк звучит одномерно, на самом деле он двумерный, поскольку строки являются символьными массивами. Следовательно: char **.
Функция fдолжна будет принимать аргумент типа, t **если она должна изменить переменную типа t *.
Много других причин, которые слишком многочисленны, чтобы перечислять здесь.
да хороший пример .. я понимаю, что они есть .. но как и когда их использовать, важнее .. сейчас ..
2
Стефан проделал хорошую работу, воспроизведя, в основном, диаграмму на языке программирования C на языке Керниган и Ричи. Если вы программируете на C, у вас нет этой книги, и вы хорошо знакомы с бумажной документацией, я настоятельно рекомендую вам ее получить (довольно) скромные расходы очень быстро окупятся в производительности. Это имеет тенденцию быть очень ясным в своих примерах.
Дж. Полфер
4
char * c = "hello" должно быть const char * c = "hello". Кроме того, ошибочно говорить, что «массив хранится как адрес первого элемента». Массив хранится как ... массив. Часто его имя дает указатель на его первый элемент, но не всегда. Что касается указателей на указатели, я бы просто сказал, что они полезны, когда функция должна модифицировать указатель, переданный в качестве параметра (вместо этого вы передаете указатель на указатель).
Бастьен Леонар
4
Если я не неверно истолковал этот ответ, он выглядит неправильно. c хранится в 58 и указывает на 63, cp хранится в 55 и указывает на 58, а cpp не представлен на диаграмме.
Танатос
1
Выглядит хорошо. Единственное, что мешало мне сказать: «Великий пост». Само объяснение было превосходным. Перехожу на голосование. (Возможно, stackoverflow нуждается в рассмотрении указателей?)
Thanatos
46
Как работают указатели на указатели в C?
Сначала указатель - это переменная, как и любая другая переменная, но она содержит адрес переменной.
Указатель на указатель - это переменная, как и любая другая переменная, но она содержит адрес переменной. Эта переменная просто указатель.
Когда бы вы их использовали?
Вы можете использовать их, когда вам нужно вернуть указатель на некоторую память в куче, но не используя возвращаемое значение.
Пример:
int getValueOf5(int*p){*p =5;return1;//success}int get1024HeapMemory(int**p){*p = malloc(1024);if(*p ==0)return-1;//errorelsereturn0;//success}
И вы называете это так:
int x;
getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in//At this point x holds 5int*p;
get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in//At this point p holds a memory address where 1024 bytes of memory is allocated on the heap
Есть и другие применения, например, аргумент main () каждой программы на C имеет указатель на указатель на argv, где каждый элемент содержит массив символов, которые являются параметрами командной строки. Вы должны быть осторожны, хотя, когда вы используете указатели указателей для указания на 2-мерные массивы, лучше вместо этого использовать указатель на 2-мерный массив.
Вот пример указателя на двумерный массив, выполненный правильно:
int(*myPointerTo2DimArray)[ROWS][COLUMNS]
Вы не можете использовать указатель на двумерный массив, хотя, если вы хотите поддерживать переменное количество элементов для ROWS и COLUMNS. Но когда вы знаете заранее, вы бы использовали двумерный массив.
Мне нравится этот пример кода "реального мира" указателя на использование указателя, в Git 2.0, commit 7b1004b :
Линус однажды сказал:
Я бы хотел, чтобы больше людей понимали действительно базовый низкоуровневый код. Не большие и сложные вещи, такие как поиск по имени без блокировки, но просто хорошее использование указателей на указатели и т. Д.
Например, я видел слишком много людей, которые удаляли односвязную запись списка, отслеживая запись «prev» , а затем удалить запись, делая что-то вроде
и всякий раз, когда я вижу такой код, я просто говорю: «Этот человек не понимает указателей». И это, к сожалению, довольно часто.
Люди, которые понимают указатели, просто используют « указатель на указатель записи » и инициализируют его адресом list_head. И затем, проходя по списку, они могут удалить запись, не используя никаких условных выражений, просто выполнив
*pp = entry->next
Применение этого упрощения позволяет нам потерять 7 строк этой функции даже при добавлении 2 строк комментария.
Вам нужно перебрать его от начала до конца и удалить конкретный элемент, значение которого равно значению to_remove.
Более очевидный способ сделать это будет:
list_entry *entry = head;/* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;while(entry){/* line 4 */if(entry->val == to_remove)/* this is the one to remove ; line 5 */if(prev)
prev->next = entry->next;/* remove the entry ; line 7 */else
head = entry->next;/* special case - first entry ; line 9 *//* move on to the next entry */
prev = entry;
entry = entry->next;}
То, что мы делаем выше, это:
перебирая список, пока запись не будет NULL , что означает, что мы достигли конца списка (строка 4).
Когда мы сталкиваемся с записью, которую мы хотим удалить (строка 5),
мы присваиваем значение текущего следующего указателя предыдущему,
таким образом устраняя текущий элемент (строка 7).
Существует особый случай выше - в начале итерации нет предыдущей записи ( prevестьNULL ), поэтому для удаления первой записи в списке необходимо изменить саму голову (строка 9).
Линус говорил, что приведенный выше код можно упростить, сделав предыдущий элемент указателем на указатель, а не просто указателем .
Код выглядит следующим образом:
Приведенный выше код очень похож на предыдущий вариант, но обратите внимание, что нам больше не нужно следить за частным случаем первого элемента списка, поскольку ppон не находится NULLв начале. Просто и умно.
Кроме того, кто-то в этой теме заметил, что причина в том, что это лучше, потому что *pp = entry->nextон атомарный. Это, безусловно, не атомный .
Вышеприведенное выражение содержит два оператора разыменования ( *и ->) и одно присваивание, и ни одна из этих трех вещей не является атомарной. Это распространенное заблуждение, но , увы , почти ничего в C никогда не следует считать атомными ( в том числе ++и --операторов)!
@kumar хорошая ссылка. я включил его в ответ для большей наглядности.
VonC
Это видео было важно для понимания вашего примера. В частности, я чувствовал растерянность (и воинственность), пока не нарисовал диаграмму памяти и не проследил за ходом программы. Тем не менее, это все еще кажется мне несколько загадочным.
Крис
@Chris Отличное видео, спасибо, что упомянули об этом! Я включил ваш комментарий в ответ для большей наглядности.
VonC
14
При рассмотрении указателей на курс программирования в университете нам дали два совета о том, как начать изучать их. Первым было посмотреть Pointer Fun With Binky . Во-вторых, подумать о проходе «Глаза пикши» из « Зазеркалье» Льюиса Кэрролла.
«Вам грустно, - сказал рыцарь с тревожным тоном, - позвольте мне спеть вам песню, чтобы утешить вас».
"Это очень долго?" Спросила Алиса, потому что она слышала много стихов в тот день.
«Это долго, - сказал Рыцарь, - но это очень, очень красиво. Все, кто слышит меня, поют это - или это вызывает слезы на их глазах, или иначе - ”
"Или что еще?" сказала Алиса, потому что рыцарь сделал внезапную паузу.
«Или это не так, вы знаете. Название песни называется «Глаза пикши». »
«О, это название песни, не так ли?» - сказала Алиса, пытаясь почувствовать интерес.
«Нет, ты не понимаешь», сказал Рыцарь, выглядя немного недовольным. «Так называется это имя. На самом деле его зовут «Старец в возрасте».
«Тогда я должен был сказать:« Так называется песня »?» Алиса поправилась.
«Нет, не стоит: это совсем другое! Песня называется «Пути и средства», но это только то, как она называется, вы знаете! »
«Ну, что за песня?» сказала Алиса, которая к этому времени была совершенно сбита с толку.
«Я пришел к этому», сказал Рыцарь. «Эта песня действительно звучит как« сидя на воротах », а мелодия - мое собственное изобретение».
Когда ссылка на указатель обязательна. Например, когда вы хотите изменить значение (указанный адрес) переменной-указателя, объявленной в области видимости вызывающей функции внутри вызываемой функции.
Если вы передадите один указатель в качестве аргумента, вы будете изменять локальные копии указателя, а не исходный указатель в вызывающей области. С указателем на указатель вы модифицируете последний.
Указатель на указатель также называется дескриптором . Часто его используют, когда объект можно переместить в память или удалить. За блокировку и разблокировку использования объекта часто отвечает один, поэтому он не будет перемещен при доступе к нему.
Он часто используется в среде с ограниченным объемом памяти, то есть в Palm OS.
Рассмотрим приведенный ниже рисунок и программу, чтобы лучше понять эту концепцию .
Согласно рисунку, ptr1 - это единственный указатель, имеющий адрес переменной num .
ptr1 =#
Точно так же ptr2 является указателем на указатель (двойной указатель), который имеет адрес указателя ptr1 .
ptr2 =&ptr1;
Указатель, который указывает на другой указатель, известен как двойной указатель. В этом примере ptr2 является двойным указателем.
Значения сверху диаграммы:
Address of variable num has :1000Address of Pointer ptr1 is:2000Address of Pointer ptr2 is:3000
Пример:
#include<stdio.h>int main (){int num =10;int*ptr1;int**ptr2;// Take the address of var
ptr1 =#// Take the address of ptr1 using address of operator &
ptr2 =&ptr1;// Print the value
printf("Value of num = %d\n", num );
printf("Value available at *ptr1 = %d\n",*ptr1 );
printf("Value available at **ptr2 = %d\n",**ptr2);}
Вывод:
Value of num =10Value available at *ptr1 =10Value available at **ptr2 =10
это указатель на значение адреса указателя. (это ужасно, я знаю)
в основном, это позволяет вам передавать указатель на значение адреса другого указателя, так что вы можете изменить, куда указывает другой указатель из подфункции, например:
Указатель на указатель это, ну, указатель на указатель.
Значительным примером someType ** является двумерный массив: у вас есть один массив, заполненный указателями на другие массивы, поэтому, когда вы пишете
dpointer [5] [6]
Вы получаете доступ к массиву, который содержит указатели на другие массивы в его 5-й позиции, получаете указатель (пусть fpointer его имя), а затем получаете доступ к 6-му элементу массива, на который ссылается этот массив (так, fpointer [6]).
указатели на указатели не следует путать с массивами ранга 2, например, int x [10] [10], где вы пишете x [5] [6], вы получаете доступ к значению в массиве.
Пит Киркхам
Это всего лишь пример, где пустота ** подходит. Указатель на указатель - это только указатель, который указывает на указатель.
akappa
1
Как это работает: это переменная, которая может хранить другой указатель.
Когда вы их используете? Многие используют один из них, если ваша функция хочет создать массив и вернуть его вызывающей стороне.
//returns the array of roll nos {11, 12} through paramater// return value is total number of studentsint fun(int**i ){int*j;*i =(int*)malloc (2*sizeof(int));**i =11;// e.g., newly allocated memory 0x2000 store 11
j =*i;
j++;*j =12;;// e.g., newly allocated memory 0x2004 store 12return2;}int main(){int*i;int n = fun(&i );// hey I don't know how many students are in your class please send all of their roll numbers.for(int j=0; j<n; j++)
printf("roll no = %d \n", i[j]);return0;}
Там очень много полезных объяснений, но я не нашел просто краткое описание, так что ..
В основном указатель является адресом переменной. Краткий сводный код:
int a,*p_a;//declaration of normal variable and int pointer variable
a =56;//simply assign value
p_a =&a;//save address of "a" to pointer variable*p_a =15;//override the value of the variable//print 0xfoo and 15 //- first is address, 2nd is value stored at this address (that is called dereference)
printf("pointer p_a is having value %d and targeting at variable value %d", p_a,*p_a);
Ответы:
Давайте предположим, что 8-битный компьютер с 8-битными адресами (и, следовательно, только 256 байтов памяти). Это часть этой памяти (цифры вверху - это адреса):
Здесь вы можете видеть, что по адресу 63 начинается строка «привет». Таким образом, в этом случае, если это единственное появление «привет» в памяти, то,
... определяет
c
как указатель на (только для чтения) строку "hello" и, таким образом, содержит значение 63.c
Само должно быть где-то сохранено: в приведенном выше примере в расположении 58. Конечно, мы можем указывать не только на символы , но и к другим указателям. Например:Теперь
cp
указывает на тоc
, что он содержит адресc
(который составляет 58). Мы можем пойти еще дальше. Рассматривать:Теперь
cpp
хранит адресcp
. Таким образом, оно имеет значение 55 (на основе приведенного выше примера), и вы уже догадались: оно само хранится по адресу 60.Что касается того, почему каждый использует указатели на указатели:
t
, ссылка на массив имеет типt *
. Теперь рассмотрим массив массивов типаt
: естественно, ссылка на этот 2D-массив будет иметь тип(t *)*
=t **
и, следовательно, является указателем на указатель.char **
.f
должна будет принимать аргумент типа,t **
если она должна изменить переменную типаt *
.источник
Как работают указатели на указатели в C?
Сначала указатель - это переменная, как и любая другая переменная, но она содержит адрес переменной.
Указатель на указатель - это переменная, как и любая другая переменная, но она содержит адрес переменной. Эта переменная просто указатель.
Когда бы вы их использовали?
Вы можете использовать их, когда вам нужно вернуть указатель на некоторую память в куче, но не используя возвращаемое значение.
Пример:
И вы называете это так:
Есть и другие применения, например, аргумент main () каждой программы на C имеет указатель на указатель на argv, где каждый элемент содержит массив символов, которые являются параметрами командной строки. Вы должны быть осторожны, хотя, когда вы используете указатели указателей для указания на 2-мерные массивы, лучше вместо этого использовать указатель на 2-мерный массив.
Почему это опасно?
Вот пример указателя на двумерный массив, выполненный правильно:
Вы не можете использовать указатель на двумерный массив, хотя, если вы хотите поддерживать переменное количество элементов для ROWS и COLUMNS. Но когда вы знаете заранее, вы бы использовали двумерный массив.
источник
Мне нравится этот пример кода "реального мира" указателя на использование указателя, в Git 2.0, commit 7b1004b :
Крис отмечает в комментариях к фильму 2016 года « Проблема двойного указателя Линуса Торвальдса » Филипа Буака .
Кумар отмечает в комментариях пост в блоге « Линус о понимании указателей », где Гриша Трубецкой объясняет:
источник
При рассмотрении указателей на курс программирования в университете нам дали два совета о том, как начать изучать их. Первым было посмотреть Pointer Fun With Binky . Во-вторых, подумать о проходе «Глаза пикши» из « Зазеркалье» Льюиса Кэрролла.
источник
Вы можете прочитать это: Указатели на Указатели
Надеюсь, это поможет прояснить некоторые основные сомнения.
источник
Когда ссылка на указатель обязательна. Например, когда вы хотите изменить значение (указанный адрес) переменной-указателя, объявленной в области видимости вызывающей функции внутри вызываемой функции.
Если вы передадите один указатель в качестве аргумента, вы будете изменять локальные копии указателя, а не исходный указатель в вызывающей области. С указателем на указатель вы модифицируете последний.
источник
Указатель на указатель также называется дескриптором . Часто его используют, когда объект можно переместить в память или удалить. За блокировку и разблокировку использования объекта часто отвечает один, поэтому он не будет перемещен при доступе к нему.
Он часто используется в среде с ограниченным объемом памяти, то есть в Palm OS.
источник
Рассмотрим приведенный ниже рисунок и программу, чтобы лучше понять эту концепцию .
Согласно рисунку, ptr1 - это единственный указатель, имеющий адрес переменной num .
Точно так же ptr2 является указателем на указатель (двойной указатель), который имеет адрес указателя ptr1 .
Указатель, который указывает на другой указатель, известен как двойной указатель. В этом примере ptr2 является двойным указателем.
Значения сверху диаграммы:
Пример:
Вывод:
источник
это указатель на значение адреса указателя. (это ужасно, я знаю)
в основном, это позволяет вам передавать указатель на значение адреса другого указателя, так что вы можете изменить, куда указывает другой указатель из подфункции, например:
источник
У вас есть переменная, которая содержит адрес чего-либо. Это указатель.
Затем у вас есть другая переменная, которая содержит адрес первой переменной. Это указатель на указатель.
источник
Указатель на указатель это, ну, указатель на указатель.
Значительным примером someType ** является двумерный массив: у вас есть один массив, заполненный указателями на другие массивы, поэтому, когда вы пишете
dpointer [5] [6]
Вы получаете доступ к массиву, который содержит указатели на другие массивы в его 5-й позиции, получаете указатель (пусть fpointer его имя), а затем получаете доступ к 6-му элементу массива, на который ссылается этот массив (так, fpointer [6]).
источник
Как это работает: это переменная, которая может хранить другой указатель.
Когда вы их используете? Многие используют один из них, если ваша функция хочет создать массив и вернуть его вызывающей стороне.
источник
Я создал 5-минутное видео, которое объясняет, как работают указатели:
https://www.youtube.com/watch?v=3X-ray3tDjQ
источник
Там очень много полезных объяснений, но я не нашел просто краткое описание, так что ..
В основном указатель является адресом переменной. Краткий сводный код:
Также полезную информацию можно найти в теме Что означает ссылка и разыменование
И я не совсем уверен, когда могут быть полезны указатели, но в общем случае их необходимо использовать, когда вы выполняете какое-то ручное / динамическое распределение памяти - malloc, calloc и т. Д.
Поэтому я надеюсь, что это также поможет прояснить проблему :)
источник