====== указатели ====== Указатели представляют собой объекты, значением которых служат адреса других объектов (переменных, констант, указателей) или функций. // определение указателя тип_данных* название_указателя; Указатель должен быть того же типа, что и объект, адрес которого он хранит. Указатель типа int не может указывать на переменную float. Указатель на void может хранить адрес объекта __любого__ типа. Указателю можно присвоить (=) значение другово указателя (хранимый в нем адрес). Операции сравнения применяются только к указателям одного типа и константе NULL. Если объект (например int) занимает в памяти больше 1 байта - указатель будет ссылаться на ячейку, где храниться первый байт объекта. Если мы не хотим, чтобы указатель указывал на какой-то конкретный адрес, то можно присвоить ему условное нулевое значение с помощью константы NULL, которая определена в заголовочном файле stdio.h: int *pa = NULL; Иногда требуется присвоить указателю одного типа значение указателя другого типа. В этом случае следует выполнить операцию приведения типов. ===== Получение адреса ===== Для получения адреса к переменной применяется операция & #include int main(void) { int x = 10; int *p; p = &x; printf("%p \n", p); // например: 0060FEA8 return 0; } таким же образом (&) можно получить адрес самого указателя. ===== Разыменование указателя ===== Для получения значения по адресу применяется операция * или операция разыменования (dereference operator). #include int main(void){ int x = 10; int *p; p = &x; printf("Address = %p \n", (void*) p); // Address = 0060FEA8 printf("x = %d \n", *p); // x = 10 return 0; } ===== Константы ===== Мы можем манипулировать значениями констант через указатели #include int main(void) { const int cx = 10; // получаем адрес константы, преобразуем в указатель типа int* и изменяем по нему значение *(int*) &cx = 20; printf("cx: %d\n", cx); // cx: 20 } Сам указатель тоже может быть const. Константный указатель может указывать и на обычную автоматическую переменную. Константному указателю нельзя изменить адрес, который в нем храниться, зато можно изменить значение по этому адресу. Константный указатель на константу не может менять ни свой адрес, ни значение по адресу. ===== Массивы указателей ===== Массив указателей определяется одним из трех способов: int array[] = {1, 2, 3, 4}; int *p1[3]; int *p2[] = { &array[1], &array[2], &array[0] }; int *p3[3] = { &array[3], &array[1], &array[2] }; Вместо %%*p[i]%% для доступа к элементу из array мы могли бы написать %%**(p+i)%%: * ''p+i'' - к адресу в указателе p прибавляем число i и таким образом перемещаемся по указателям в массиве p. * ''%%(p+i)%%'' - разыменовываем i-тый указатель в массиве и в результате получаем адрес одного из элементов из массива array. * ''%%**(p+i)%%'' - получаем значение по полученному на предыдущем шаге адресу элемента из массива array. ===== Указатель на массив ===== ''тип_элементов_массива (*имя_переменной_указателя)[количество_элементов];'' int array1[] = {11, 12, 13}; int (*pa1)[3] = &array; // указатель на массив с 3 элементами int int array2[] = {11, 12, 13, 14, 15}; int (*pa2)[] = &array2; // указатель типа int (*)[5] int (*pa3)[3] = &array2; // некоторые компиляторы такое не пропускают ==== Указатель и массив строк ==== Соответственно если указатель типа char можно представить в виде строки, то массив указателей типа char представляет собой массив строк #include int main(void) { char *fruit[] = {"apricot", "apple", "banana", "lemon", "orange"}; for(int i=0; i < 5; i++) { printf("%s \n", fruit[i]); // аналогично printf("%s \n", *(fruit + i)); } return 0; } ===== Указатели на указатели ===== Если указатель хранит адрес переменной, то указатель на указатель хранит адрес указателя, на который он указывает((Такие ситуации еще называются многоуровневой адресацией.)). #include int main(void) { int x = 22; int *px = &x; // указатель px хранит адрес переменной x int **ppx = &px; // указатель ppx хранит адрес указателя px printf("Address of px: %p \n", (void *)ppx); printf("Address of x: %p \n", (void *)*ppx); printf("Value of x: %d \n", **ppx); return 0; } Здесь указатель ppx хранит адрес указателя px. Поэтому через выражение *ppx можно получить значение, которое хранится в указателе px - адрес переменной x. А через выражение %%**ppx%% можно получить значение по адресу из px, то есть значение переменной x. {{:c:c_ultimate_guide:ptr_to_ptr.png}} ===== Указатели на функцию ===== Указатель на функцию представляет собой выражение или переменную, которые используются для представления адреса функции. Указатель на функцию содержит адрес первого байта в памяти, по которому располагается выполняемый код функции. Самым распространенным указателем на функцию является ее имя. общий синтаксис:\\ ''тип (*имя_указателя) (типы_параметров);'' #include void hello() { printf("Hello, World \n"); } void goodbye() { printf("Good Bye, World \n"); } int sum(int x, int y) { return x + y; } int subtract(int x, int y) { return x - y; } int main(void) { // определяем указатель на функцию void (*message) (void); message=hello; // указатель указывает на функцию hello message(); // вызываем функцию, на которую указыывет указатель message = goodbye; // указатель указывает на функцию goodbye message(); // вызываем функцию, на которую указыывет указатель int a = 10; int b = 5; int result; int (*operation)(int, int); operation=sum; result = operation(a, b); printf("result = %d \n", result); // result=15 operation = subtract; result = operation(a, b); printf("result = %d \n", result); // result=5 return 0; } ==== Массивы указателей на функции ==== ''тип (*имя_массива[размер]) (параметры)'' #include void sum(int x, int y) { printf("x + y = %d \n", x + y); } void subtract(int x, int y) { printf("x - y = %d \n", x - y); } void multiply(int x, int y) { printf("x * y = %d \n", x * y); } int main(void) { int a = 10; int b = 5; void (*operations[3])(int, int) = {sum, subtract, multiply}; // получаем длину массива int length = sizeof(operations)/sizeof(operations[0]); for(int i=0; i < length; i++) { operations[i](a, b); // вызов функции по указателю } return 0; }