Как указатель на указатели работает в C?
Как указатели на указатели работают в C? Когда вы их используете?
- Эффективная реализация log2 (__ m256d) в AVX2
- Программа для конверсии Фаренгейта в цель
- Что означает ... означает в списке аргументов в C?
- C struct hack на работе
- Передача многомерных массивов в качестве аргументов функции в C
- Разбор аргументов командной строки?
- Каков эффективный способ подсчета бит в позиции или ниже?
- что такое использование fflush (stdin) в программировании c
Предположим, что 8-битный компьютер с 8-разрядными адресами (и, следовательно, всего 256 байт памяти). Это часть этой памяти (цифры вверху – это адреса):
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | | 58 | | | 63 | | 55 | | | h | e | l | l | o | \0 | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
Здесь вы можете видеть, что на адресе 63 начинается строка «привет». Таким образом, в этом случае, если это единственное появление «привет» в памяти,
const char *c = "hello";
… определяет c
как указатель на строку (только для чтения) «hello» и, следовательно, содержит значение 63. c
должен быть где-то сохранен: в примере выше в точке 58. Конечно, мы можем не только указать для персонажей, но и для других указателей. Например:
const char **cp = &c;
Теперь cp
указывает на c
, т. Е. Содержит адрес c
(который равен 58). Мы можем пойти еще дальше. Рассматривать:
const char ***cpp = &cp;
Теперь cpp
хранит адрес cp
. Поэтому он имеет значение 55 (на примере выше), и вы догадались: он сам хранится по адресу 60.
Что касается использования указателей указателей:
- Имя массива обычно дает адрес его первого элемента. Поэтому, если массив содержит элементы типа
t
, ссылка на массив имеет типt *
. Теперь рассмотрим массив массивов типаt
: естественно, ссылка на этот 2D-массив будет иметь тип(t *)*
=t **
и, следовательно, указатель на указатель. - Несмотря на то, что массив строк звучит одномерно, он фактически двумерный, так как строки являются массивами символов. Следовательно:
char **
. - Функция
f
должна принять аргумент типаt **
если он должен изменить переменную типаt *
. - Многие другие причины, которые слишком многочисленны, перечислены здесь.
Как указатели на указатели работают в C?
Сначала указатель представляет собой переменную, как и любую другую переменную, но которая содержит адрес переменной.
Указатель на указатель – это переменная, как и любая другая переменная, но которая содержит адрес переменной. Эта переменная просто является указателем.
Когда вы их используете?
Вы можете использовать их, когда вам нужно вернуть указатель на некоторую память в куче, но не использовать возвращаемое значение.
Пример:
int getValueOf5(int *p) { *p = 5; return 1;//success } int get1024HeapMemory(int **p) { *p = malloc(1024); if(*p == 0) return -1;//error else return 0;//success }
И вы называете это так:
int x; getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in //At this point x holds 5 int *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-мерный массив.
Почему это опасно?
void test() { double **a; int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*) double matrix[ROWS][COLUMNS]; int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double) }
Ниже приведен пример указателя на 2-мерный массив, выполненный правильно:
int (*myPointerTo2DimArray)[ROWS][COLUMNS]
Вы не можете использовать указатель на 2-мерный массив, хотя, если вы хотите поддерживать переменное количество элементов для ROWS и COLUMNS. Но когда вы знаете перед собой, вы должны использовать 2-мерный массив.
Мне нравится этот «реальный мир» пример кода указателя на использование указателя, в Git 2.0, commit 7b1004b :
Линус однажды сказал:
Я на самом деле желаю, чтобы больше людей понимали действительно базовый низкоуровневый вид кодирования. Не большие, сложные вещи, такие как поиск без блокировки, но просто хорошее использование указателей для указателей и т. Д.
Например, я видел слишком много людей, которые удаляют запись с односвязным списком, отслеживая запись «prev», а затем удаляют запись, делая что-то вроде
if (prev) prev->next = entry->next; else list_head = entry->next;
и всякий раз, когда я вижу такой код, я просто иду «Этот человек не понимает указателей». И это, к сожалению, довольно распространено.
Люди, которые понимают указатели, просто используют « указатель на указатель ввода » и инициализируют это с адресом list_head. И затем, когда они пересекают список, они могут удалить запись без каких-либо условностей, просто выполняя
*pp = entry->next
Применение этого упрощения позволяет нам потерять 7 строк из этой функции, даже добавляя 2 строки комментария.
- struct combine_diff_path *p, *pprev, *ptmp; + struct combine_diff_path *p, **tail = &curr;
Крис указывает на комментарии к видеофильму 2016 года « Задача двойного указателя Линуса Торвальдса » Филиппа Буака .
Кумар указывает в комментариях сообщение в блоге « Linus on Understanding Pointer », где объясняет Гриша Трубецкой :
Представьте, что у вас есть связанный список, определенный как:
typedef struct list_entry { int val; struct list_entry *next; } list_entry;
Вам нужно перебрать его с начала на конец и удалить конкретный элемент, значение которого равно значению 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
isNULL
), и поэтому для удаления первой записи в списке вам нужно изменить сам заголовок (строка 9).То, что сказал Линус, заключается в том, что приведенный выше код можно упростить, сделав предыдущий элемент указателем на указатель, а не только указателем .
Код выглядит следующим образом:
list_entry **pp = &head; /* pointer to a pointer */ list_entry *entry = head; while (entry) { if (entry->val == to_remove) *pp = entry->next; pp = &entry->next; entry = entry->next; }
Вышеприведенный код очень похож на предыдущий вариант, но обратите внимание на то, что нам больше не нужно наблюдать за особым случаем первого элемента списка, так как
pp
не являетсяNULL
в начале. Простой и умный.Кроме того, кто-то из этого streamа заметил, что причина в том, что это лучше, потому что
*pp = entry->next
является атомарным. Это, безусловно, НЕ атомный .
Вышеприведенное выражение содержит два оператора разыменования (*
и->
) и одно назначение, и ни одна из этих трех вещей не является атомарной.
Это распространенное заблуждение, но, увы, почти ничего в C не должно считаться атомарным (в том числе операторы++
и--
)!
Прикрывая указатели на курсе программирования в университете, нам дали два намека на то, как начать узнавать о них. Первым был просмотр Pointer Fun With Binky . Во-вторых, подумать о прохождении « Чаддских глаз» из фильма Льюиса Кэрролла « Через зеркало»
«Вы грустите», – спросил Рыцарь тревожным тоном: «Позвольте мне спеть вам песню, чтобы утешить вас».
«Это очень долго?» Спросила Алиса, потому что в тот день она много слышала стихи.
«Долго, – сказал Рыцарь, – но это очень, очень красиво. Все, кто меня слышит, петь – либо он приносит слезы на глаза, либо … –
«Или что еще?» – спросила Алиса, потому что Рыцарь внезапно замолчал.
«Или это не так, вы знаете. Название песни называется «Глаза Чаддоков».
«О, это имя песни, не так ли?» – сказала Алиса, пытаясь почувствовать интерес.
«Нет, ты не понимаешь», – сказал Рыцарь, немного расстроенный. «Это то, что называется. На самом деле это «пожилой человек».
«Тогда я должен был сказать« Это то, что называется песней »?» Алиса поправила себя.
«Нет, ты не должен: это совсем другое дело! Песня называется «Пути и средства»: но это только то, что она называется, вы знаете! »
«Ну, что же это за песня?» – сказала Алиса, которая к тому времени полностью сбилась с толку.
«Я подошел к этому, – сказал Рыцарь. «Песня действительно« A-Sitting On A Gate »: и моя песня – мое изобретение».
Вы можете прочитать следующее: Указатели на указатели
Надеюсь, это поможет прояснить некоторые основные сомнения.
Когда требуется ссылка на указатель. Например, если вы хотите изменить значение (адрес, на которое указывает) переменной указателя, объявленной в области вызывающей функции внутри вызываемой функции.
Если вы передаете один указатель в качестве аргумента, вы будете изменять локальные копии указателя, а не оригинальный указатель в области вызова. С указателем на указатель вы модифицируете последний.
Указатель на указатель также называется ручкой . Одно использование для него часто происходит, когда объект можно перемещать в памяти или удалять. Один из них часто несет ответственность за блокировку и разблокирование использования объекта, чтобы он не перемещался при доступе к нему.
Он часто используется в среде с ограниченной памятью, то есть в Palm OS.
computer.howstuffworks.com Ссылка >>
http://www.flippinbits.com Ссылка >>
У вас есть переменная, которая содержит адрес чего-то. Это указатель.
Затем у вас есть другая переменная, содержащая адрес первой переменной. Это указатель на указатель.
это указатель на значение адреса указателя. (это ужасно, я знаю)
в основном, он позволяет передавать указатель на значение адреса другого указателя, поэтому вы можете изменить, где другой указатель указывает на вспомогательную функцию, например:
void changeptr(int** pp) { *pp=&someval; }
Рассмотрим приведенный ниже рисунок и программу, чтобы лучше понять эту концепцию .
Согласно рисунку, ptr1 является единственным указателем, который имеет адрес переменной num .
ptr1 = #
Аналогично, ptr2 является указателем на указатель (двойной указатель), который имеет адрес указателя ptr1 .
ptr2 = &ptr1;
Указатель, указывающий на другой указатель, называется двойным указателем. В этом примере ptr2 является двойным указателем.
Значения сверху диаграммы:
Address of variable num has : 1000 Address of Pointer ptr1 is: 2000 Address of Pointer ptr2 is: 3000
Пример:
#include 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 = 10 Value available at *ptr1 = 10 Value available at **ptr2 = 10
Указатель на указатель – это, скорее, указатель на указатель.
Значимым примером someType ** является двумерный массив: у вас есть один массив, заполненный указателями на другие массивы, поэтому, когда вы пишете
dpointer [5] [6]
вы получаете доступ к массиву, содержащему указатели на другие массивы в его 5-й позиции, получите указатель (пусть он назовет его имя), а затем получите доступ к 6-му элементу массива, на который ссылается этот массив (так, fpointer [6]).
Как это работает: Это переменная, которая может хранить другой указатель.
Когда вы их используете: многие используют один из них, если ваша функция хочет построить массив и вернуть его вызывающему.
//returns the array of roll nos {11, 12} through paramater // return value is total number of students int fun( int **i ) { int *j; *i = (int*)malloc ( 2*sizeof(int) ); **i = 11; // eg, newly allocated memory 0x2000 store 11 j = *i; j++; *j = 12; ; // eg, newly allocated memory 0x2004 store 12 return 2; } 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
Я создал 5-минутное видео, в котором объясняется, как работают указатели:
https://www.youtube.com/watch?v=3X-ray3tDjQ
Там так много полезных объяснений, но я не нашел просто краткое описание, поэтому ..
В основном указатель – это адрес переменной. Код короткой сводки:
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);
Также полезную информацию можно найти в разделе Что означает ссылка и разыменование
И я не уверен, когда могут быть указатели полезными, но в общем случае их необходимо использовать, когда вы выполняете какое-то ручное / динамическое распределение памяти – malloc, calloc и т. Д.
Поэтому я надеюсь, что это также поможет прояснить проблему 🙂