C декларация и инициализация символа двойного символа
Я всегда, хотя объявление
char *c = "line";
был таким же, как
char c[] = "line";
и поэтому я сделал
char **choices = { "New Game", "Continue Game", "Exit" };
Что дает мне несовместимый тип указателя, где
char *choices[] = { "New Game", "Continue Game", "Exit" };
нет. Любая помощь в понимании этого?
Ответы
Ответ 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
.