Ликбез по указателям

Ячейки памяти нумеруются целыми числами с нуля. Каждая ячейка — один байт.

Указатель — адрес (номер) некоторой ячейки памяти.
Тип указателя включает в себя информацию о том, на объект какого типа он указывает.
Например, переменная типа int* содержит адрес некоторого int-а (точнее, его первого байта).

1. Получение адреса переменной.

У любой переменной можно узнать адрес в памяти, по которой лежит значение, которое она содержит.
Для этого используется оператор & перед названием переменной.

int x = 5; printf("%p\n", &x); // выведет адрес (в шестнадцатеричной форме), по которому в памяти лежит число 5 int *a = &x; // сохранение адреса в переменной a типа int* printf("%p\n", a); // выведет тот же адрес x = 7; printf("%p\n", a); // адрес не изменится

2. Разыменование указателя.

По указателю можно получить значение, которое лежит в памяти по соответствующему адресу.
Для этого используется оператор * перед названием переменной-указателя.

int x = 5; printf("%d\n", *&x); // выведет 5: операция * обратна операции & int *a = &x; printf("%d\n", *a); // выведет 5 x = 7; printf("%d\n", *a); // выведет 7 printf("%d\n", x); // выведет 7 *a = 3; // с помощью разыменования можно не только получать значение, но и изменять его printf("%d\n", *a); // выведет 3 printf("%d\n", x); // выведет 3

3. Арифметика указателей.

Можно представлять себе указатель как целое число.
На 32-битных компиляторах указатель (на любой тип) занимает 4 байта (как int), на 64-битных — 8 байт (как long long).
Арифметика указателей несколько отличается от обычной арифметики целых типов.

int x = 5; int *a = &x; int *b = a + 1; printf("%p\n", a); // выведет адрес printf("%p\n", b); // выведет адрес на 4 байта больше

Вообще говоря, операция (p + k), где p имеет тип mytype*, а k имеет тип int, вернёт на (k * sizeof(mytype)) больший адрес.
Аналогично с вычитанием (p - k).

int x = 5; int *a = &x; int *b = a + 3; char *c = ((char*) a) + 3; printf("%p\n", a); // выведет адрес addr printf("%p\n", b); // выведет адрес на 12 = 3 * sizeof(int) = 3 * 4 байт больший addr printf("%p\n", c); // выведет адрес на 3 = 3 * sizeof(char) = 3 * 1 байт больший addr Кроме того, указатели можно вычитать друг из друга (но не складывать). int x = 5; int *a = &x; int *b = a + 3; printf("%p\n", a); // выведет адрес printf("%p\n", b); // выведет адрес на 12 = 3 * sizeof(int) = 3 * 4 байт больше int d = b - a; printf("%d\n", d); // выведет 3

4. Объявление указателей.

int *a; // так принято int* a; // так не принято

Это связано с тем, что конструкция int* a, b; будет интерпретирована компилятором как объявление переменной a типа int* и переменной b типа int (а не int*).
Чтобы объявить два указателя, необходимо написать int *a, *b;

5. Оператор ->.

Пусть объявлена структура следующего вида: struct Node { int x, y; double z; Node() { z = x = y = -1; } Node(int _x, int _y) { x = _x; y = _y; z = sqrt(x * x + y * y); } } И пусть мы имеем указатель на переменную типа Node: Node *nodeptr; Если мы захотим вывести поле x объекта типа Node, который лежит по адресу, сохранённому в nodeptr, мы напишем printf("%d\n", (*nodeptr).x); Так писать не очень удобно. Поэтому в языке есть синтаксический сахар: можно писать printf("%d\n", nodeptr->x); Конструкция nodeptr->x эквивалентна (*nodeptr).x.

6. Оператор new.

Оператор new позволяет выделять память под переменные и массивы. int *a = new int(); // выделили память под одну переменную типа int (4 байта) и адрес записали в a *a = 5; // записали по этому адресу в памяти значение 5 Node *nodeptr = new Node(5, 3); // выделили память под одну переменную типа Node и вызвали на этом месте конструктор Node(int, int) Также можно выделять память для массивов: int *as = new int[100](); Node *nodesptr = new Node[100](); // для всех элементов будет выполнен конструктор по умолчанию (от нуля аргументов); использовать другой конструктор нельзя

7. NULL

NULL — это нулевой указатель; так принято помечать отсутствие указателя.
На самом деле, NULL является просто нулём: int *a = NULL; printf("%p\n", a); // выведет 0 Разыменовывание нулевого указателя вызовет Runtime Error (так же, как и обращение к любой не принадлежащей вам памяти).