Указатель на указатель - что это?

Рейтинг: 8Ответов: 3Опубликовано: 25.11.2014

Часто встречаю вот такую конструкцию:

int ** p;

Я так понимаю, что это указатель на указатель. Зачем такая двойственность нужна и где применяется?

Ответы

▲ 14Принят

Указатель в C — не семантика, а механизм. Он сам по себе не несёт смысла, но может использоваться для выражения того или иного смысла. То же относится и к двойному указателю: он может использоваться для разных вещей. Вот несколько примеров.

  1. В C параметры передаются по значению, то есть из коробки нету передачи параметров «по ссылке» (они же &-параметры C++, они же ref-/out-параметры C#). Для того, чтобы объявить такой параметр, пользуются указателем на фактический параметр (то есть, передают в функцию адрес параметра). Если тип самого параметра — указатель, получается двойной указатель. Пример:

    void split_half(char* input, char** first_half, char** second_half)
    {
        var len = strlen(input);
        var halflen = len / 2;
        *first_half = malloc(halflen + 1);
        strncpy(*first_half, input, halflen);
        (*firsthalf)[halflen] = 0;
        *second_half = strdup(&input[halflen]);
    }
    
  2. В C указатель может обозначать массив. Если тип элемента массива — указатель, получается двойной указатель. Классический пример:

    int main(int argc, char** argv) { ... }
    
  3. Двойной указатель можно использовать для массива массивов. Например, квадратная матрица:

    struct matrix
    {
        int** data;
        int width;
        int height;
    }
    
    void init_matrix(int width, int height, struct matrix* matrix)
    {
        matrix->width = width;
        matrix->height = height;
        matrix->data = malloc(height * sizeof(int*));
        for (int y = 0; y < height; y++)
            matrix->data[y] = malloc(width * sizeof(int));
    }
    

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

▲ 4

Ни в языке С++, ни в языке С, нет такого понятия, как "указатель на указатель" в виде самостоятельной сущности с какими-то новыми качественными свойствами. Поэтому в строгом смысле слова, вопрос о том "зачем нужен указатель на указатель" не имеет никакого смысла.

В языках С и С++ есть такое понятие, как указатель. Просто указатель. Т.е. если у вас есть некий тип T, то вы можете объявить указатель p на этот тип

T *p;

и заставить этот указатель указывать на объект t типа T

T t;
p = &t;

После этого выражения t и *p будут обозначать один и тот же объект. Т.е. если вы, например, поменяете значение объекта *p, то тем самым вы поменяете и объект t (и наоборот). Вы можете также завести еще сколько угодно указателей на один и от же объект.

Это - элементарные основы идеи указателя.

Ну так а далее можно просто заметить, что тип T сам по себе может быть указательным типом. Но это совершенно ничего не меняет. Нет ничего принципиально разного между ситуацией, когда T - это int, и ситуацией, когда T - это double *. Все вышесказанное относится к обоим случаям в одинаковой мере.

Вот, собственно и все. Т.е. нет никакого смысла вводить в рассмотрение такую сущность, как "указатель на указатель", и устраивать вокруг нее какие-то обсуждения. Все, что нам нужно - это обычный указатель, который может просто-напросто указывать и на другой указатель. Но эти два уровня указательности (три, четыре, пять уровней...) совершенно отдельны, ничего о друг друге не знают и знать не хотят.

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

▲ 2

Когда-то объяснял это на примере холодильника.

int **fridge; // Холодильник с полочками на которых еда
*fridge; // Полочка в холодильнике
**fridge; // Колбаса