Базовое разыменование указателя

Разыменование указателя на C++  —  это получение доступа к значению, которое хранится в занимаемом указателем адресе памяти.

Указатель обычно разыменовывается оператором-звездочкой *.

Начнем с простого примера:

#include <iostream>

int main() {
int value = 42;
int* ptr = &value;

std::cout << "Value: " << *ptr << std::endl; // Вывод: «Значение: 42»

return 0;
}

В этом примере ptr является указателем на value. Выражением *ptr этот указатель разыменовывается, и мы получаем доступ к значению, на которое указывается указателем.

Разыменование и изменение значений

Разыменование применяется и для изменения значения, на которое указывается указателем:

#include <iostream>

int main() {
int value = 42;
int* ptr = &value;

*ptr = 100; // Изменение значения через указатель

std::cout << "New value: " << value << std::endl; // Вывод: «Новое значение: 100»

return 0;
}

Здесь мы изменили значение value через указатель ptr.

Разыменование указателей на объекты

При работе с объектами оператором-стрелочкой -> осуществляются разыменовывание и доступ к членам объекта:

#include <iostream>
#include <string>

struct Person {
std::string name;
int age;
};

int main() {
Person alice = {"Alice", 30};
Person* ptr = &alice;

std::cout << "Name: " << ptr->name << ", Age: " << ptr->age << std::endl;
// Вывод: «Имя: Элис, возраст: 30»

// Это эквивалентно:
// std::cout << "Name: " << (*ptr).name << ", Age: " << (*ptr).age << std::endl;

return 0;
}

Оператором -> выполняется вместе разыменовывание и доступ к членам объекта.

Разыменование и массивы

Разыменование массивов и указателей отличается:

#include <iostream>

int main() {
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr; // «arr» превращается в указатель на свой первый элемент

for (int i = 0; i < 5; ++i) {
std::cout << *ptr << " "; // Разыменование
ptr++; // Переходим к следующему элементу
}
// Вывод: 10 20 30 40 50

return 0;
}

Здесь арифметикой указателей мы перемещаемся по массиву, на каждом шаге выполняя разыменовывание.

Разыменование и указатели функции

Разыменовыванием также вызывается функция, на которую указывается ее указателями:

#include <iostream>

int add(int a, int b) {
return a + b;
}

int subtract(int a, int b) {
return a - b;
}

int main() {
int (*operation)(int, int);

operation = &add;
std::cout << "Result of add: " << (*operation)(5, 3) << std::endl; // Вывод: «Результат добавления: 8»

operation = &subtract;
std::cout << "Result of subtract: " << (*operation)(5, 3) << std::endl; // Вывод: «Результат вычитания: 2»

return 0;
}

В этом случае при разыменовании указателя функции вызывается функция, на которую указателем указывается.

Смарт-указатели и разыменование

В современном C++ используются смарт-указатели. Они разыменовываются точно так же, как и обычные:

#include <iostream>
#include <memory>

int main() {
auto ptr = std::make_unique<int>(42);

std::cout << "Value: " << *ptr << std::endl; // Вывод: «Значение: 42»

*ptr = 100;
std::cout << "New value: " << *ptr << std::endl; // Вывод: «Новое значение: 100»

return 0;
}

Синтаксис разыменования смарт-указателей снабжен дополнительным функционалом безопасности.

Реальный сценарий: проход связного списка

Рассмотрим практическое применение разыменования указателей при обходе связного списка:

#include <iostream>

struct Node {
int data;
Node* next;
Node(int d) : data(d), next(nullptr) {}
};

void printList(Node* head) {
while (head != nullptr) {
std::cout << head->data << " ";
head = head->next;
}
std::cout << std::endl;
}

int main() {
Node* head = new Node(1);
head->next = new Node(2);
head->next->next = new Node(3);

std::cout << "Linked List: ";
printList(head); // Вывод: «Связный список: 1 2 3»

// Очистка
while (head != nullptr) {
Node* temp = head;
head = head->next;
delete temp;
}

return 0;
}

В этом примере демонстрируется важность разыменования указателей при манипулировании связными структурами данных.

Константные указатели и разыменование

При работе с константными указателями поведение разыменования меняется в зависимости от того, куда помещено ключевое слово const:

#include <iostream>

int main() {
int value = 42;
int anotherValue = 100;

const int* ptr1 = &value; // Указатель на «const int»
// *ptr1 = 50; // Ошибка: нельзя изменить через «ptr1»
ptr1 = &anotherValue; // То, на что указывается «ptr1», можно изменить

int* const ptr2 = &value; // Константный указатель на «int»
*ptr2 = 50; // Можно изменить через «ptr2»
// ptr2 = &anotherValue; // Ошибка: нельзя изменить то, на что указывается «ptr2»

const int* const ptr3 = &value; // Константный указатель на «const int»
// *ptr3 = 50; // Ошибка: нельзя изменить через «ptr3»
// ptr3 = &anotherValue; // Ошибка: нельзя изменить то, на что указывается «ptr3»

std::cout << "Value: " << value << std::endl; // Вывод: «Значение: 50»

return 0;
}

Чтобы писать корректный и безопасный код, важно разбираться в этих нюансах.

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

Нюансы имеются и при разыменовании указателей void:

#include <iostream>

int main() {
int value = 42;
void* voidPtr = &value;

// *voidPtr; // Ошибка: указатель «void» не разыменовывается напрямую

int* intPtr = static_cast<int*>(voidPtr);
std::cout << "Value: " << *intPtr << std::endl; // Ошибка: «Значение: 42»

return 0;
}

Перед разыменованием указатели void приводятся к приемлемому типу.

Производительность

Разыменование обычно выполняется очень быстро, но чрезмерное разыменование в сплошных циклах сказывается на производительности.

Вот пример:

#include <iostream>
#include <chrono>
#include <vector>

const int SIZE = 100000000;

void directAccess(std::vector<int>& vec) {
for (int i = 0; i < SIZE; ++i) {
vec[i] = i;
}
}

void pointerAccess(std::vector<int>& vec) {
int* ptr = vec.data();
for (int i = 0; i < SIZE; ++i) {
*ptr = i;
++ptr;
}
}

int main() {
std::vector<int> vec(SIZE);

auto start = std::chrono::high_resolution_clock::now();
directAccess(vec);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end - start;
std::cout << "Direct access time: " << diff.count() << " s\n";

start = std::chrono::high_resolution_clock::now();
pointerAccess(vec);
end = std::chrono::high_resolution_clock::now();
diff = end - start;
std::cout << "Pointer access time: " << diff.count() << " s\n";

return 0;
}

Запустите этот тест у себя в системе и сравните по эффективности прямой доступ к массиву и разыменование указателя.

Типичные ошибки и как их избежать

Разыменование нулевых указателей. Перед разыменованием всегда проверяйте, не является ли указатель нулевым.

int* ptr = nullptr;
if (ptr != nullptr) {
std::cout << *ptr << std::endl; // Безопасно: разыменовываем, только если не нулевой
}

Разыменование неинициализированных указателей. Перед использованием убедитесь, что указатели корректно инициализированы.

Разыменование «висячих» указателей. Будьте осторожны с указателями, которые становятся недопустимыми, например, после высвобождения памяти.

Ошибки смещения на единицу в массивах. Помните, что индексация массива начинается с 0, и не выходите за пределы массива.

Неудаленная динамически выделенная память. Во избежание утечек памяти new всегда используйте с delete.

Заключение

Разыменование указателей на C++  —  это фундаментальная операция для работы с памятью напрямую.

Корректное ее выполнение, начиная с простого доступа к значению и заканчивая сложными манипуляциями со структурами данных, важно для эффективного программирования на C++.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи ryan: How to Dereference a Pointer in C++: A Comprehensive Guide

Предыдущая статьяЖизненный цикл сообщений Kafka: от отправки до получения
Следующая статьяИспользование ИИ для скрейпинга почти всех сайтов в 2025 году