Указатель — это переменная, которая хранит адрес памяти другой переменной. В C++ указатели используются для:
Прямого доступа к памяти
Динамического выделения памяти
Эффективной передачи больших данных в функции
Реализации сложных структур данных
Синтаксис и базовые операции
Объявление указателя:
тип* имя_указателя; // * может быть рядом с типом или именем
Примеры инициализации:
int num = 42;
int* ptr = # // ptr хранит адрес num
double d = 3.14;
double* dPtr = &d;
char c = 'A';
char* cPtr = &c;
Операции с указателями:
int x = 10;
int* p = &x;
cout << p; // Выведет адрес памяти (например, 0x7ffd4d96e8ac)
cout << *p; // Выведет 10 (разыменование)
*p = 20; // Изменяем значение x через указатель
cout << x; // Теперь x = 20
Указатель на указатель
int val = 5;
int* ptr = &val;
int** pptr = &ptr; // Указатель на указатель
cout << **pptr; // Выведет 5
Арифметика указателей
Указатели поддерживают операции +, -, ++, -- с учетом размера типа:
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr; // Эквивалентно &arr[0]
cout << *ptr; // 10
ptr++; // Перемещаемся на следующий int (4 байта)
cout << *ptr; // 20
cout << *(ptr+2); // 40
Указатели и массивы
Массивы неявно преобразуются в указатели:
int nums[3] = {1, 2, 3};
int* p = nums; // p указывает на nums[0]
// Эквивалентные способы доступа:
cout << nums[1]; // 2
cout << *(p+1); // 2
cout << p[1]; // 2 (синтаксический сахар)
Указатели на функции
// Объявление функции
int add(int a, int b) { return a + b; }
// Указатель на функцию
int (*funcPtr)(int, int) = &add;
// Вызов через указатель
int result = funcPtr(3, 4); // result = 7
Нулевые указатели
int* ptr1 = nullptr; // Современный C++ (предпочтительно)
int* ptr2 = NULL; // Устаревший стиль
int* ptr3 = 0; // Альтернатива
if (ptr1 == nullptr) {
cout << "Указатель не инициализирован";
}
Динамическая память
int* dynArr = new int[10]; // Выделение памяти
dynArr[0] = 42;
delete[] dynArr; // Освобождение памяти
int* single = new int(7); // Одиночное выделение
delete single;
Для чего нужны указатели?
Динамическое выделение памяти
Указатели позволяют создавать структуры данных, размер которых неизвестен на этапе компиляции.
int size;
cin >> size; // Размер массива вводит пользователь
int* dynamicArray = new int[size]; // Без указателя так не сделать
for (int i = 0; i < size; ++i) {
dynamicArray[i] = i * 10;
}
delete[] dynamicArray; // Освобождаем память
Почему без указателей нельзя? Массивы с размером из переменной (int arr[size]) — запрещены в стандартном C++. Только через new и указатели.
Работа с функциями
а) Изменение переменных внутри функций
Без указателей функция не может изменить переданную переменную:
void increment(int* num) {
(*num)++; // Разыменовываем указатель и меняем значение
}
int main() {
int x = 5;
increment(&x); // Передаём адрес x
cout << x; // 6
}
б) Возврат нескольких значений
Через указатели можно "вернуть" несколько значений:
void calculate(int a, int b, int* sum, int* product) {
*sum = a + b;
*product = a * b;
}
int main() {
int s, p;
calculate(3, 4, &s, &p);
cout << "Sum: " << s << ", Product: " << p;
}
Работа с массивами и строками
Указатели позволяют эффективно обрабатывать данные:
void printArray(const int* arr, int size) {
for (int i = 0; i < size; ++i) {
cout << arr[i] << " ";
}
}
int main() {
int nums[] = {1, 2, 3};
printArray(nums, 3); // Передаём массив через указатель
}
Почему не копировать массив? Копирование больших массивов — дорогая операция. Указатели экономят память и время.
Создание сложных структур данных
Без указателей невозможны:
Связные списки
Деревья
Графы
Пример узла списка:
struct Node {
int data;
Node* next; // Указатель на следующий узел
};
Node* head = new Node{1, new Node{2, nullptr}};
Взаимодействие с оборудованием и ОС
Чтение/запись в конкретные адреса памяти (например, в драйверах)
Работа с API операционной системы (например, WinAPI)