Прежде всего, тип возврата для main
должен быть int
, а не void
.void main()
четко определен только в том случае, если в документации вашего компилятора явно указан его как легальная подпись.В противном случае вы вызываете неопределенное поведение.Вместо этого используйте int main(void)
.
Во-вторых, пришло время быстрого ускоренного курса для строк, массивов и указателей.
В отличие от Java, C не имеет выделенного типа данных строки;скорее строки представлены в виде последовательностей char
значений, оканчивающихся на 0. Они хранятся в виде массивов char
.Строковый литерал "hello" хранится в виде 6-элементного массива char
(const char
в C ++).Этот массив имеет статический экстент, что означает, что он выделяется при запуске программы и удерживается до ее завершения.Попытка изменить содержимое строкового литерала вызывает неопределенное поведение;Лучше всего вести себя так, как будто они не написаны.
Когда выражение массива появляется в большинстве контекстов, тип выражения преобразуется из «N-элементного массива T» в «указатель на T», а значением выражения является адрес первогоэлемент массива.Это одна из причин, по которой оператор string = "hello";
не работает;в этом контексте тип выражения "hello"
преобразуется из «6-элементного массива char
» в «указатель на char
», что несовместимо с целевым типом (который, будучи char
, не являетсяв любом случае это неправильный тип).Единственными исключениями из этого правила являются случаи, когда выражение массива является операндом операторов sizeof
или унарных &
или если это строковый литерал, используемый для инициализации другого массива в объявлении.
Например, объявление
char foo[] = "hello";
выделяет foo
как массив из 6 элементов char
и копирует содержимое строкового литерала в неготогда как
char *bar = "hello";
выделяет bar
в качестве указателя на char
и копирует адрес строкового литерала в него.
Если вы хотите скопировать содержимое одного массива в другой, вам нужно использовать библиотечную функцию, такую как strcpy
или memcpy
.Для строк вы должны использовать strcpy
следующим образом:
char string[MAX_LENGTH];
strcpy(string, "hello");
Вам нужно убедиться, что цель достаточно велика, чтобы хранить содержимое строки источника вместе с завершающим 0.В противном случае вы получите переполнение буфера.Массивы в C не знают, насколько они велики, и запуск после конца массива не вызовет исключения, как в Java.
Если вы хотите защититься от возможности переполнения буфера,вы бы использовали strncpy
, который принимает значение в качестве дополнительного параметра, так что копируется не более N символов:
strncpy(string, "hello", MAX_LEN - 1);
Проблема в том, что strncpy
не будет добавлять терминатор 0к цели, если источник длиннее пункта назначения;вам придется сделать это самостоятельно.
Если вы хотите напечатать содержимое строки, вы должны использовать спецификатор преобразования %s
и передать выражение, которое оценивает адрес первого элемента строки, например:
char string[10] = "hello";
char *p = string;
printf("%s\n", "hello"); // "hello" is an array expression that decays to a pointer
printf("%s\n", string); // string is an array expression that decays to a pointer
printf("%s\n", p); // p is a pointer to the beginning of the string
Опять же, оба типа "hello"
и string
преобразованы из "N-элементного массива char
" в "указатель на char
";все, что видит printf
- это значение указателя.
Вот удобная таблица, показывающая типы различных выражений, включающих массивы:
Declaration: T a[M];
Expression Type Decays to
---------- ---- ---------
a T [M] T *
&a T (*)[M]
*a T
a[i] T
&a[i] T *
Declaration: T a[M][N];
Expression Type Decays to
---------- ---- ---------
a T [M][N] T (*)[N]
&a T (*)[M][N]
*a T [N] T *
a[i] T [N] T *
&a[i] T (*)[N]
*a[i] T
a[i][j] T
&a[i][j] T *
Помните, что унарный оператор &
выдаст адрес своего операнда (при условии, что операнд является lvalue).Вот почему ваша char fnamn[] = &fname;
декларация вызвала ошибку «неверный инициализатор»;вы пытаетесь инициализировать содержимое массива char
значением указателя.
Унарный оператор *
выдаст значение любого операнда , указывающего на .Если операнд не указывает ни на что значащее (либо NULL, либо не соответствует действительному адресу), поведение не определено.Если вам повезет, вы получите прямую ошибку.Если вам не повезет, вы получите странное поведение во время выполнения.
Обратите внимание, что выражения a
и &a
дают одинаковое значение (адрес первого элемента в массиве), но их типы различны. Первая выдает простой указатель на T, а вторая - указатель на массив T. Это важно, когда вы выполняете арифметику указателей. Например, предположим следующий код:
int a[5] = {0,1,2,3,4};
int *p = a;
int (*pa)[5] = &a;
printf("p = %p, pa = %p\n", (void *) p, (void *) pa);
p++;
pa++;
printf("p = %p, pa = %p\n", (void *) p, (void *) pa);
Для первого printf
два значения указателя идентичны. Затем мы продвигаем оба указателя. p
будет продвигаться на sizeof int
байт (то есть будет указывать на второй элемент массива). pa
, OTOH, будет расширен на sizeof int [5]
байтов, так что он будет указывать на первый байт после конца массива.