Подтвердить что ты не робот

C декларация и инициализация символа двойного символа

Я всегда, хотя объявление

char *c = "line";

был таким же, как

char c[] = "line";

и поэтому я сделал

char **choices = { "New Game", "Continue Game", "Exit" };

Что дает мне несовместимый тип указателя, где

char *choices[] = { "New Game", "Continue Game", "Exit" };

нет. Любая помощь в понимании этого?

4b9b3361

Ответ 1

Ну, они не то же самое. Для большинства людей просто легче думать о них как о том же, так что каждый начинает думать так, пока не столкнутся с проблемой, подобной выше: -)

Я собирался написать что-то длинное и извитое, но потом я подумал... Кто-то еще должен был это сделать уже. И они есть. Это довольно хорошее объяснение:

http://www.lysator.liu.se/c/c-faq/c-2.html

Самый простой способ подумать о том, что когда вы делаете что-то вроде:

  char *foo = "something";

Вы действительно делаете что-то вроде:

  char randomblob[] = "something";
  char *foo = randomblob;

Теперь... это не совсем точная картина (хотя я не специалист по компилятору). Это по крайней мере позволяет вам думать о вещах несколько более правильным образом.

Итак, вернемся к вашей проблеме, если я правильно понимаю вещи (что никогда не гарантируется), вы не можете выполнить свою строку с номером 3 в C. Вы правы, что кто-то может написать компилятор, который бы сделал правильный вещь здесь, но gcc нет. 4-й пример, однако, делает "правильную вещь" и дает вам "массив указателей, каждый из которых указывает на массив const char".

Я однажды наткнулся на веб-страницу, которая перевела бы сложный тип C на английский. Вероятно, это было в начале 90-х годов, но я уверен, что если вы достаточно Google, это даст вам более точное описание, чем тот, который я только что взбесил.

Ответ 2

char *c = "line";

не совпадает с

char c[] = "line";

он действительно такой же, как

static char hidden_C0[] = "line";
char *c = hidden_C0;

за исключением того, что переменная hidden_C0 недоступна напрямую. Но вы увидите это, если вы сбросите сгенерированный язык ассемблера (у него обычно будет имя, которое не является допустимым идентификатором C, например .LC0). И в примере с массивом строки-константы происходит то же самое:

char *choices[] = { "New Game", "Continue Game", "Exit" };

становится

char hidden_C0[] = "New Game";
char hidden_C1[] = "Continue Game";
char hidden_C2[] = "Exit";

char *choices[] = { hidden_C0, hidden_C1, hidden_C2 };

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

int *numbers = { 1, 2, 3 };

вы должны написать

int numbers[] = { 1, 2, 3 };

и почему вы не можете писать

char **choices = { "a", "b", "c" };

либо.

(Ваше замешательство является частным случаем распространенного заблуждения, что массивы "такие же, как" указатели на C. "Это не так. Массивы представляют собой массивы. Переменные с типами массивов переносят разложение типов на тип указателя, когда они используются ( почти в каждом контексте), но не тогда, когда они определены.)

Ответ 3

Это нормально, просто напишите

char **choices = (char *[]){ "New Game", "Continue Game", "Exit" };

Однако choices может использоваться только для линейной адресации. Например:

printf ("%s", &(*choices)[0]); выходы: New Game
printf ("%s", &(*choices)[1]); выходы: ew Game
printf ("%s", &(*choices)[9]); выходы: Continue Game

Так что это не шутка, это действительная инициализация. Просто другой вид использования.


Вы также можете найти очень близкий пример здесь, объясняя понятие Compound Literals.

Ответ 4

Стандарт онлайн C (черновик n1256):

6.7.8 Инициализация
...
11 Инициализатор для скалярного должен быть единственным выражением, необязательно заключенным в фигурные скобки. Начальное значение объекта - это выражение (после преобразования); применяются те же ограничения типа и преобразования, что и для простого присваивания, при этом тип скаляра является неквалифицированной версией его объявленного типа.
...
16 Иначе инициализатор для объекта, который имеет aggregate или union type, должен быть заключенным в скобки списком инициализаторов для элементов или именованных членов.

Добавлен акцент.

char ** является скалярным типом, а не агрегатом, и поэтому несовместим с инициализатором {"New Game", "Continue Game", "Exit"}. Напротив, char *[] - это совокупный (массив) тип.

Аналогично, вы не могли бы написать что-то вроде

int *foo = {1, 2, 3};

потому что int * не является типом массива.

Ваше понимание

char *c = "line";

и

char c[] = "line";

слегка выключен; Они не одинаковы. Первая форма копирует адрес строкового литерала в значение указателя c. Вторая форма копирует содержимое выражения массива "line" в буфер, обозначенный c.