Зачем мне нужно использовать тип ** для указания типа *?

Я читал Learn C The Hard Way в течение нескольких дней, но здесь кое-что, что я действительно хочу понять. Zed, автор, написал, что char ** предназначен для "указателя на (указатель на char)" и говорит, что это необходимо, потому что я пытаюсь указать на что-то двумерное.

Вот что написано на веб-странице

A char * уже является "указателем на char", так что просто строка. Однако вам нужны 2 уровня, так как имена 2-мерные, это означает, что вам нужен char ** для указателя "указатель на char)".

Означает ли это, что я должен использовать переменную, которая может указывать на что-то двумерное, поэтому мне нужны два **?

Просто небольшое продолжение, применимо ли это для n измерения?

Здесь соответствующий код

char *names[] = { "Alan", "Frank", "Mary", "John", "Lisa" };
char **cur_name = names;

Ответ 1

Нет, этот учебник имеет сомнительное качество. Я бы не рекомендовал продолжать его читать.

A char** является указателем на указатель. Это не 2D-массив. Это не указатель на массив. Это не указатель на 2D-массив.

Автор учебника, скорее всего, запутался, потому что существует широко распространенная неправильная и неправильная практика, говорящая, что вам следует выделять динамические 2D-массивы следующим образом:

// BAD! Do not do like this!
int** heap_fiasco;
heap_fiasco = malloc(X * sizeof(int*));
for(int x=0; x<X; x++)
{
  heap_fiasco[x] = malloc(Y * sizeof(int));
}

Это, однако, не 2D-массив, это медленная, фрагментированная таблица поиска, распределенная по всей куче. Синтаксис доступа к одному элементу в таблице поиска heap_fiasco[x][y] выглядит так же, как синтаксис индексирования массива, поэтому многие люди почему-то считают, что это то, как вы выделяете 2D-массивы.

Правильный способ размещения двумерного массива динамически:

// correct
int (*array2d)[Y] = malloc(sizeof(int[X][Y]));

Вы можете сказать, что первый - это не массив, потому что если вы делаете memcpy(heap_fiasco, heap_fiasco2, sizeof(int[X][Y])), код будет сбой и сбой. Элементы не выделяются в соседней памяти.

Аналогично memcpy(heap_fiasco, heap_fiasco2, sizeof(*heap_fiasco)) также будет разбиваться и гореть, но по другим причинам: вы получаете размер указателя, а не массив.

Пока memcpy(array2d, array2d_2, sizeof(*array2d)) будет работать, потому что это 2D-массив.

Ответ 2

Указатели поняли, что я понял. Я настоятельно рекомендую рисовать диаграммы.

Пожалуйста, прочитайте и поймите эту часть учебника на С++ (по крайней мере, в отношении указателей диаграммы действительно помогли мне).

Сообщая вам, что вам нужен указатель на указатель на char для двумерного массива - это ложь. Вам это не нужно, но это один из способов сделать это.

Память последовательна. Если вы хотите поместить 5 символов (букв) подряд, как в слове "привет", вы можете определить 5 переменных и всегда помнить, в каком порядке их использовать, но что происходит, когда вы хотите сохранить слово с 6 буквами? Вы определяете больше переменных? Было бы проще, если бы вы просто сохранили их в памяти в последовательности?

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

Проблема с указателями заключается в том, что они являются просто адресами. Откуда вы знаете, что хранится по этому адресу? Это 5 символов или это большой двоичный номер, который равен 8 байтам? Или это часть файла, который вы загрузили? Откуда вы знаете?

Здесь язык программирования, такой как C, пытается помочь вам, предоставив вам типы. Тип говорит вам, что переменная хранит, а указатели тоже имеют типы, но их типы сообщают вам, на что указывает указатель. Следовательно, char * является указателем на ячейку памяти, которая содержит либо один char, либо последовательность chars. К сожалению, часть о том, сколько char, вам нужно будет запомнить. Обычно вы храните эту информацию в переменной, которую вы храните, чтобы напомнить вам, сколько там символов.

Итак, если вы хотите иметь 2-мерную структуру данных, как вы это представляете?

Это лучше всего объяснить с помощью примера. Пусть сделаем матрицу:

1  2  3  4
5  6  7  8
9 10 11 12

Он имеет 4 столбца и 3 строки. Как это сохранить?

Ну, мы можем сделать 3 последовательности по 4 числа каждый. Первая последовательность 1 2 3 4, вторая - 5 6 7 8, а третья и последняя - 9 10 11 12. Поэтому, если мы хотим сохранить 4 номера, мы попросим систему зарезервировать 4 номера для нас и дать нам указатель на них. Это будут указатели на числа. Однако, поскольку нам нужно иметь 3 из них, мы попросим систему дать нам 3 указателя на номера указателей.

И как вы в конечном итоге предложите решение...

Другой способ сделать это - понять, что вам нужно 4 раза 3 числа, и просто попросите систему сохранить 12 чисел в последовательности. Но тогда как вы получаете доступ к номеру в строке 2 и столбце 3? Здесь вы найдете математику, но попробуйте ее на нашем примере:

1  2  3  4
5  6  7  8
9 10 11 12

Если мы сохраним их рядом друг с другом, они будут выглядеть следующим образом:

offset from start:  0  1  2  3    4  5  6  7    8   9  10  11   
numbers in memory: [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]

Итак, наше отображение выглядит так:

row | column | offset | value
 1  |   1    |   0    |   1
 1  |   2    |   1    |   2
 1  |   3    |   2    |   3
 1  |   4    |   3    |   4
 2  |   1    |   4    |   5
 2  |   2    |   5    |   6
 2  |   3    |   6    |   7
 2  |   4    |   7    |   8
 3  |   1    |   8    |   9
 3  |   2    |   9    |  10
 3  |   3    |  10    |  11
 3  |   4    |  11    |  12

И теперь нам нужно разработать приятную и легкую формулу для преобразования строки и столбца в смещение... Я вернусь к нему, когда у меня будет больше времени... Прямо сейчас мне нужно вернуться домой ( извините)...

Изменить: Я немного опаздываю, но позвольте мне продолжить. Чтобы найти смещение каждого из чисел из строки и столбца, вы можете использовать следующую формулу:

offset = (row - 1) * 4 + (column - 1)

Если вы заметили два -1 здесь и подумаете об этом, вы поймете, что это потому, что наши номера строк и столбцов начинаются с 1, что мы должны сделать это, и поэтому учёные предпочитают нулевые смещения ( из-за этой формулы). Однако с указателями на языке C сам язык применяет эту формулу для вас, когда вы используете многомерный массив. И, следовательно, это другой способ сделать это.

Ответ 3

Из вашего вопроса я понимаю, что вы спрашиваете, зачем вам char ** для переменной, которая объявлена ​​как * names []. Таким образом, ответ заключается в том, что вы просто пишете имена [], чем это синтаксис массива и массива в основном является указателем.

Поэтому, когда вы пишете * names [], это означает, что вы указываете на массив. И поскольку массив в основном является указателем, поэтому означает, что у вас есть указатель на указатель, и поэтому компилятор не будет жаловаться, если вы пишете

char ** cur_name = names;

В приведенной выше строке вы объявляете указатель на указатель на символ, а затем инициализируете его указателем на массив (помните, что массив также является указателем).