← Назад к вопросам

Зачем нужен указатель?

1.0 Junior🔥 242 комментариев
#Умные указатели и управление памятью#Язык C++

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Зачем нужен указатель в C++?

Быстрый ответ

Указатель — это переменная, которая хранит адрес памяти другой переменной. Это позволяет:

  • Обращаться к значению по адресу
  • Передавать переменные по ссылке (изменять оригинал)
  • Работать с динамической памятью
  • Строить сложные структуры данных
  • Реализовать полиморфизм

Основы

int x = 42;
int* ptr = &x;  // ptr содержит адрес x

std::cout << *ptr << std::endl;  // 42 (разыменование)
std::cout << ptr << std::endl;   // 0x7fff... (адрес)

*ptr = 100;     // Изменяем x через ptr
std::cout << x << std::endl;     // 100

Причина 1: Динамическое выделение памяти

Размер массива известен только в runtime:

int size;
std::cin >> size;

// Статический массив — размер должен быть константой
// int arr[size];  // ❌ Ошибка компиляции

// Но с указателем — можем
int* arr = new int[size];  // Выделяем в heap

// Работаем с массивом
for (int i = 0; i < size; i++) {
    arr[i] = i * 2;
}

delete[] arr;  // Освобождаем когда закончили

Без указателей нет динамического выделения памяти.

Причина 2: Модификация переменной через функцию

Если передать по значению — оригинал не изменится:

void increment(int x) {
    x++;  // Изменяем копию, не оригинал
}

int a = 5;
increment(a);
std::cout << a << std::endl;  // 5 (не изменилась)

С указателем — можем изменить оригинал:

void increment(int* x) {
    (*x)++;  // Изменяем оригинал
}

int a = 5;
increment(&a);
std::cout << a << std::endl;  // 6

В modern C++ используют ссылки вместо указателей для этого, но указатели тоже работают:

void increment(int& x) {
    x++;  // Чище синтаксис
}

Причина 3: Полиморфизм

Динамический полиморфизм работает только с указателями или ссылками:

class Shape {
public:
    virtual void draw() = 0;
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Circle\n"; }
};

class Square : public Shape {
public:
    void draw() override { std::cout << "Square\n"; }
};

void render(Shape* shape) {  // указатель на базовый класс
    shape->draw();  // Какая реализация? Узнаём в runtime
}

int main() {
    Circle c;
    Square s;
    
    render(&c);  // Рисует Circle
    render(&s);  // Рисует Square
}

Без указателей или ссылок невозможен полиморфизм.

Причина 4: Сложные структуры данных

Связный список:

struct Node {
    int data;
    Node* next;  // Указатель на следующий узел
};

class LinkedList {
private:
    Node* head;
    
public:
    void insert(int data) {
        Node* newNode = new Node{data, head};
        head = newNode;
    }
    
    ~LinkedList() {
        while (head) {
            Node* temp = head;
            head = head->next;
            delete temp;
        }
    }
};

Без указателей нельзя построить список переменной длины.

Бинарное дерево:

struct TreeNode {
    int value;
    TreeNode* left;
    TreeNode* right;
};

class BinaryTree {
private:
    TreeNode* root;
    
    void insertHelper(TreeNode*& node, int value) {
        if (!node) {
            node = new TreeNode{value, nullptr, nullptr};
            return;
        }
        if (value < node->value) {
            insertHelper(node->left, value);
        } else {
            insertHelper(node->right, value);
        }
    }
};

Причина 5: Возврат из функции

Если нужно вернуть адрес динамически выделенной памяти:

int* allocateArray(int size) {
    int* arr = new int[size];  // Выделили
    
    // Инициализируем
    for (int i = 0; i < size; i++) {
        arr[i] = i * 2;
    }
    
    return arr;  // Возвращаем указатель
}

int main() {
    int* data = allocateArray(100);
    
    // Используем data
    std::cout << data[0] << std::endl;
    
    delete[] data;  // Освобождаем
}

Причина 6: Матрица (2D массив)

int** allocateMatrix(int rows, int cols) {
    int** matrix = new int*[rows];  // Массив указателей
    
    for (int i = 0; i < rows; i++) {
        matrix[i] = new int[cols];  // Каждая строка
    }
    
    return matrix;
}

void freeMatrix(int** matrix, int rows) {
    for (int i = 0; i < rows; i++) {
        delete[] matrix[i];
    }
    delete[] matrix;
}

int main() {
    int** m = allocateMatrix(3, 4);
    
    m[0][0] = 42;  // Доступ как m[i][j]
    
    freeMatrix(m, 3);
}

Без указателей сложно работать с матрицами переменного размера.

Причина 7: Функции как параметры (callbacks)

void forEach(int* arr, int size, void (*callback)(int)) {
    for (int i = 0; i < size; i++) {
        callback(arr[i]);
    }
}

void print(int x) {
    std::cout << x << " ";
}

int main() {
    int arr[] = {1, 2, 3, 4};
    forEach(arr, 4, &print);  // Передаём функцию
}

Это предок современных std::function и lambda.

Причина 8: Реализация умных указателей

template <typename T>
class UniquePtr {
private:
    T* ptr;
    
public:
    UniquePtr(T* p) : ptr(p) {}
    
    ~UniquePtr() {
        delete ptr;  // Автоматическая очистка
    }
    
    T& operator*() { return *ptr; }
    T* operator->() { return ptr; }
};

// std::unique_ptr — это обёртка вокруг raw pointer

Умные указатели — это обёртки вокруг raw указателей.

Современный подход: минимизировать raw указатели

В modern C++ используем умные указатели вместо raw:

// ❌ OLD (опасно, утечки памяти)
int* arr = new int[100];
// ... код
delete[] arr;  // Если забыть — утечка

// ✅ NEW (безопасно)
std::unique_ptr<int[]> arr(new int[100]);
// При выходе из scope — автоматически delete

// ✅ BEST (ещё проще)
auto arr = std::make_unique<int[]>(100);

Примеры, где указатели нужны:

Да:

  • Динамическое выделение (new/delete)
  • Полиморфизм (Shape* shape)
  • Связные структуры (Node* next)
  • Callbacks (void (*func)(int))
  • Массивы переменного размера

Нет (используй ссылки или значения):

  • Передача параметров: void foo(int&) вместо void foo(int*)
  • Хранение ссылок на локальные: ссылки безопаснее
  • Простое копирование: передавай по значению

Заключение

Указатель — это низкоуровневый инструмент для работы с адресами памяти. Он нужен для:

  • Динамического выделения памяти
  • Полиморфизма
  • Сложных структур данных
  • Управления памятью

В modern C++ используй умные указатели (unique_ptr, shared_ptr) и ссылки, избегая raw указателей насколько возможно. Но понимание указателей — это фундамент для понимания всех этих концепций.