- Получать адрес оператором
&и читать или менять значение по указателю оператором* - Изменять переменные вызывающей стороны, передавая в функцию указатели
- Объяснять арифметику указателей и то, почему
arr[i]— это то же, что*(arr + i) - Понимать опасность
NULLи неинициализированных указателей
Чтобы показать другу, где ты живёшь, ты не приносишь ему дом — ты просто называешь адрес: «Баку, улица Низами, 25». Тот, кто знает адрес, может найти дом, войти внутрь и даже что-то там изменить. Память компьютера устроена так же: каждая переменная живёт по определённому адресу. Переменная, которая хранит адрес, называется указателем. Указатели — самая мощная и самая пугающая тема C, но идея у них такая же простая, как этот адрес.
Адреса и указатели
Переменная, значением которой является адрес другой переменной в памяти. int *p; означает: «p хранит адрес значения типа int».
| Запись | Значение |
|---|---|
int *p; | объявляет указатель на int |
&x | адрес переменной x |
p = &x; | теперь p указывает на x |
*p | значение по адресу из p (разыменование) |
NULL | указатель, который никуда не указывает |
#include <stdio.h>
int main(void) {
int x = 10;
int *p = &x;
printf("x = %d\n", x);
printf("*p = %d\n", *p);
*p = 25;
printf("x = %d\n", x);
printf("Same address? %d\n", p == &x);
return 0;
}x = 10 *p = 10 x = 25 Same address? 1
*p = 25; изменил x, даже не называя её, потому что p хранит её адрес.Сам адрес можно напечатать спецификатором %p. Обычно он выглядит как шестнадцатеричное число вроде 0x7ffd… и может меняться при каждом запуске программы:
int x = 10;
int *p = &x;
printf("Address of x: %p\n", (void *) &x);
printf("Value of p: %p\n", (void *) p);Передача указателей в функции: swap
Вспомни: в C функция получает копии аргументов, поэтому через обычные параметры поменять местами значения двух переменных невозможно. Решение — передать функции адреса переменных. По адресам функция добирается до исходных переменных. Именно поэтому scanf("%d", &age) требует &.
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main(void) {
int x = 3, y = 8;
printf("Before: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("After: x = %d, y = %d\n", x, y);
return 0;
}Before: x = 3, y = 8 After: x = 8, y = 3
Указатели позволяют функции «вернуть» и несколько результатов: return возвращает лишь одно значение, но функция может записать результаты по переданным ей адресам. Функция ниже находит самую низкую и самую высокую из нескольких температур, измеренных в Баку:
#include <stdio.h>
void min_max(const int *arr, int n, int *min, int *max) {
*min = arr[0];
*max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
}
}
int main(void) {
int temps[5] = {18, 25, 21, 30, 16};
int low, high;
min_max(temps, 5, &low, &high);
printf("Min: %d, max: %d\n", low, high);
return 0;
}Min: 16, max: 30
Арифметика указателей и массивы
В выражениях имя массива превращается в адрес его первого элемента, поэтому можно написать int *p = arr;. Если прибавить к указателю 1, он сдвинется не на 1 байт, а на один элемент: для int * обычно на 4 байта. Значит, *(p + 2) — третий элемент, а запись arr[i] — просто сокращение для *(arr + i). При передаче массива в функцию на самом деле передаётся указатель на первый элемент — вот почему функция может менять исходный массив и почему приём с sizeof не работает с параметром.
#include <stdio.h>
int main(void) {
int arr[4] = {10, 20, 30, 40};
int *p = arr;
printf("%d\n", *p);
printf("%d\n", *(p + 2));
p++;
printf("%d\n", *p);
printf("%d\n", arr[3] == *(arr + 3));
int sum = 0;
for (int *q = arr; q < arr + 4; q++) {
sum += *q;
}
printf("Sum: %d\n", sum);
return 0;
}10 30 20 1 Sum: 100
p++ сдвинул указатель на следующий элемент. Последний цикл перебирает массив указателем вместо индекса.Опасные указатели
- Неинициализированный указатель хранит случайный адрес. Запись по нему может испортить другие данные. Всегда задавай указателю адрес переменной или
NULL. - **Разыменование указателя
NULL** (*p, когдаpравенNULL) обычно сразу приводит к аварийному завершению. Проверяй перед использованием:if (p != NULL). - Висячий указатель указывает на переменную, которой уже нет. Например, если функция возвращает адрес своей локальной переменной, эта переменная исчезает, как только функция завершается.
int *make_number(void) {
int local = 5;
return &local;
}local исчезает, когда функция завершается, и возвращённый адрес указывает «в никуда». Результат непредсказуем.Главное
- Указатель хранит адрес:
int *p = &x;—pсодержит адресx. &даёт адрес,*читает или меняет значение по этому адресу.- Функция, получившая указатели, может менять переменные вызывающей стороны (
swap,scanf). p + 1сдвигает на один элемент;arr[i]означает*(arr + i).- Всегда инициализируй указатели, проверяй на
NULLи не возвращай адрес локальной переменной.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
x после int x = 4; int *p = &x; *p = 9;?