Основные причины, по которым мы используем указатели (на языках, производных от C и C):
- Для имитации семантики передачи по ссылке
- Для отслеживания динамически выделяемой памяти
- Для создания самоссылочных и динамических структур данных
- Поскольку иногда язык вынуждает вас
Для имитации семантики передачи по ссылке: В C все аргументы функции передаются по значению.Формальные параметры и фактические параметры являются различными объектами в памяти, поэтому запись в формальный параметр не влияет на фактический параметр.Например, учитывая код
void swap(int a, int b)
{
int tmp = a; a = b; b = tmp;
}
int main(void)
{
int x = 2, y = 3;
printf("before swap: x = %d, y = %d\n", x, y);
swap(x, y);
printf("after swap: x = %d, y = %d\n", x, y);
return 0;
}
a
и x
являются физически различными объектами;запись в a
не влияет на x
или наоборот.Таким образом, вывод до и после в программе выше будет одинаковым.Чтобы swap
мог изменить содержимое x
и y
, мы должны передать указатели на эти объекты и разыменовать указатели в функции:
void swap(int *a, int *b)
{
int tmp = *a; *a = *b; *b = tmp;
}
int main(void)
{
int x = 2, y = 3;
printf("before swap: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("after swap: x = %d, y = %d\n", x, y);
return 0;
}
a
и x
все еще являются различными объектами в памяти, но выражение *a
относится к той же памяти, что и выражение x
;таким образом, запись в *a
обновляет содержимое x
и наоборот.Теперь функция swap
будет обмениваться содержимым x
и y
.
Обратите внимание, что в C ++ введена концепция reference , которая действует как указатель, но не требует явного разыменования:
void swap(int &a, int &b)
{
int tmp = a; a = b; b = tmp;
}
int main(void)
{
int x = 2, y = 3;
std::cout << "before swap: x = " << x << ", y = " << y << std::endl;
swap(x, y);
std::cout << "after swap: x = " << x << ", y = " << y << std::endl;
return 0;
}
В этом случаевыражения a
и x
относятся к одной и той же ячейке памяти;письмо одному влияет на другое.Это, однако, C ++.
Я недостаточно знаком с Obj-C, чтобы знать, есть ли у них подобный механизм.
Для отслеживания динамически выделяемой памяти: Функции выделения памяти C malloc
, calloc
и realloc
вместе с оператором C ++ new
возвращают все указатели на динамически выделяемую память.Если вам нужно распределять память на лету, вы должны использовать указатели для обращения к ней.Опять же, я недостаточно знаком с Obj-C, чтобы знать, используют ли они другой механизм выделения памяти.
Для создания самоссылочных и динамических структур данных: Агрегированные типы, такие как struct
или union
, не могут содержать экземпляр себя;например, вы не можете сделать что-то вроде
struct node
{
int value;
struct node next;
};
для создания узла связанного списка.struct node
не является полным типом до закрытия }
, и вы не можете объявлять объекты неполного типа.Однако структура может содержать указатель на сам экземпляр:
struct node
{
int value;
struct node *next;
};
Вы можете объявить указатель на неполный тип , так что это работает.Каждый узел в списке может ссылаться на узел, следующий сразу за ним.А поскольку вы имеете дело с указателями, вы можете достаточно легко добавлять или удалять узлы из списка;вам просто нужно обновить значения указателя, а не физически перемещать данные.
Я могу в значительной степени гарантировать, что любой тип контейнера в Obj-C использует манипуляции с указателями под капотом.
Поскольку иногда язык заставляет вас: В C и C ++выражение типа массива будет неявно преобразовано в тип указателя в большинстве случаев.Подписка на массив выполняется с точки зрения арифметики указателей;выражение a[i]
оценивается так, как если бы оно было написано *(a + i)
.Итак, вы находите адрес i-го элемента после a
и разыменовываете его.