Указатели

Указатель — это переменная, значением которой является адрес ячейки памяти (оперативной). Указатели объявляются точно так же, как и обычные переменные, только со звёздочкой (причем, здвездочка по отношению к переменной и типом может быть на любом расстоянии). Указатели являются типизированными.

int* p; // указатель на переменную типа int
bool * pb; // указатель на переменную типа bool
double *p2; // указатель на переменную типа double
char *p3, *p4; // 2 указателя на переменную типа char

Сейчас мы только что объявили указатели, но не присвоили им никакого значения. Как только что было сказано - указатель содержит в себе адрес ячейки (пямяти) на другую переменную:

Pointer

Код иллюстрирующий данный пример:

int value = 5;
int *p = &value; // инициализируем p адресом значения переменной

И здесь нужно обраить внимание на оператор & - оператор взятия адреса (позволяет узнать, какой адрес памяти который присвоен определенной переменной). При этом важно подчеркнуть, что указатель - это такая же переменная у которой тоже можно взять адрес (то есть адрес, по которому записан адрес переменной в другой ячейке). Получается “двойной указатель” (так же есть и тройные и сколько вашей фантазии угодно, но об это позже). В объявлении таких многомерных указателей необходимо задать нужное кол-во звезд.

int value = 5;
int *p = &value; // инициализируем p адресом значения переменной
int **p2 = &p; // нициализируем p2 адресом значения (то есть адреса) указателя p

Теперь у нас есть указатель, инциированный адресом переменной - что мы с ним можем сделать. Очевидно, по этому адресу получить значение:

int value = 5;
int *p = &value;
int value2 = *p; // поулчаем значение по адрусу p

Обратите вниамание, что оператор взятия адреса по указателю это *

Таким образом * в C++ встречается в 3 случаях:

  • как оператор умножения
  • приобъявлении указателя
  • при разъименования (взятия значения) указателя

Оператор взятия значения, очевидно, может быть применен только к переменной типа указатель.

Практическое применение

Окей, мы узнали что такое указатель и как с ним работать, но зачем на практике это может пригодится? Рассмотри пример ниже:

void swap(int a, int b)
{
	int t = a;
	a = b;
	b = t;
}
int main()
{
	int k = 10, m = 20;
	swap(k, m);
	cout << k << " " << m << endl; // 10 20
	return 0;
}

В данном примере идет попытка поменять местати значения двух переменных. Для этого создана функция swap в ней идет обмен значений переменных с помощью третьей… но в результате все равно перемнные k и m не поменяли значения. Как такое могло произойти? Согласно стеку вызовов при вызове функции идет копирование переменных, что в данном случае и произошло. В функции swap мы меня местами значения не оригинальные переменные k и m, а их копии.

Теперь давайте рассмотрим ниже пример:

void swap(int* a, int* b)
{
	int t = *a;
	*a = *b;
	*b = t;
}
int main()
{
	int k = 10, m = 20;
	swap(&k, &m);
	cout << k << " " << m << endl; // 20 10
	return 0;
}

Здесь, вмест значений мы передаем в функцию адреса переменных k и m. Адреса коприруются при вызове функции (как и раньше), но это все еще адреса, на оригинальные переменные k и m. Таким образом, преобразования переменных в функции swap затронут именно k и m, и в резльутате мы получим, что хотим.

Указатели в современном языке

Необходимо отметить, что в новой версии C++ (еще фоициально не вышла) палнируется полностью отказаться от указателей как от пережитка прошлого - комитет по стандартизации языка принял решение о том, что указатели будут объявлены устаревшими в C++20 и с большой долей вероятности будут удалены из C++23.

Pointer evolution

Тем не менее, указтели являются одним из фундаментальным инстуремнтом позволяющим как “выстрелить себе в ногу”, так и, напротив, писать высокопроизводительные программы, игры и их знание и понимание является необходимым для любого программиста.