Давайте поговорим о выражений и типы, как они относятся к массивам в С.
Массивы
Когда вы объявляете массив как
char line[256];
выражение line
имеет тип "256-элементный arr ay от char
"; кроме случаев, когда это выражение является операндом операторов sizeof
или унарных &
, оно будет преобразовано («распад») в выражение типа «указатель на char
», а значение выражения будет адресом первого элемента массив.С учетом вышеизложенной декларацией, все из следующих условий:
Expression Type Decays to Equivalent value
---------- ---- --------- ----------------
line char [256] char * &line[0]
&line char (*)[256] n/a &line[0]
*line char n/a line[0]
line[i] char n/a n/a
&line[0] char * n/a n/a
sizeof line size_t n/a Total number of bytes
in array (256)
Обратите внимание, что выражения line
, &line
и &line[0]
все дают то же значения (адрес первого элемента массива является таким же, как адрес самого массива), это просто, что типы разные. В выражении &line
выражение массива является операндом оператора &
, поэтому приведенное выше правило преобразования не применяется; вместо указателя на char
, мы получаем указатель на массив из 256 элементов из char
. Тип вопросов; если вы пишете что-то вроде следующего:
char line[256];
char *linep = line;
char (*linearrp)[256] = &line;
printf("linep + 1 = %p\n", (void *) (linep + 1));
printf("linearrp + 1 = %p\n", (void *) (linearrp + 1));
вы получите разные выходные данные для каждой строки; linep + 1
дало бы адрес следующего char
следующего line
, в то время как linearrp + 1
дало бы адрес следующего 256-элементного массива char
следующего line
.
Выражение line
не является модифицируемое значение; вы не можете назначить ему, так что-то вроде
char temp[256];
...
line = temp;
было бы незаконным. Для переменной line
хранения не выделяется отдельно от line[0]
до line[256]
; нет ничего, чтобы назначить на.
Из-за этого, когда вы передаете выражение массива функции, то, что получает функция, является значением указателя, а не массивом. В контексте объявления параметра функции T a[N]
и T a[]
интерпретируются как T *a
; все три объявляют a
в качестве указателя на T
. «Массивность» параметра была потеряна в ходе вызова.
Все обращения к массиву выполняются с точки зрения арифметики указателя; выражение a[i]
оценивается как *(a + i)
. Выражение массива a
сначала преобразуется в выражение типа указателя в соответствии с приведенным выше правилом, затем мы смещаем i
элементов с этого адреса и разыскиваем результат.
В отличие от Java, C не отложите память для указателя на массив отдельно от элементов массива сами: все, что отведены является следующее:
+---+
| | line[0]
+---+
| | line[1]
+---+
...
+---+
| | line[255]
+---+
также не C выделяет память для массивов из куча (для любого определения кучи). Если массив объявлен auto
(то есть локальный для блока и без ключевого слова static
), то память будет выделяться из того места, где реализация получает память для локальных переменных (большинство из нас называют стек). Если массив объявлен в области файла или с ключевым словом static
, память будет выделена из другого сегмента памяти, и он будет выделен при запуске программы и удерживаться до тех пор, пока программа не завершится.
В отличие от Java, массивы C не содержат метаданных об их длине; C предполагает, что вы знали, насколько большой массив был, когда вы его выделили, чтобы вы могли отслеживать эту информацию самостоятельно.
Указатели
При объявлении указатель как
char *line;
выражение line
имеет тип "указатель на char
" (Дух). Для хранения адреса объекта char
зарезервировано достаточное хранилище. Если вы не объявите его в области файлов или с помощью ключевого слова static
, он не будет инициализирован и будет содержать некоторый случайный шаблон бита, который может соответствовать или не соответствовать действительному адресу. Учитывая вышеуказанное заявление, то все следующие условия:
Expression Type Decays to Equivalent value
---------- ---- --------- ----------------
line char * n/a n/a
&line char ** n/a n/a
*line char n/a line[0]
line[i] char n/a n/a
&line[0] char * n/a n/a
sizeof line size_t n/a Total number of bytes
in a char pointer
(anywhere from 2 to
8 depending on the
platform)
В этом случае line
и &line
действительно дают нам разные значения, а также различные типы; line
- простой скалярный объект, поэтому &line
дает нам адрес этого объекта. Опять же, обращения к массиву выполняются в терминах арифметики указателя, поэтому line[i]
работает так же, как строка объявляется как массив или как указатель.
Так что, когда вы пишете
char *line = malloc(sizeof *line * 256); // note no cast, sizeof expression
это так, что работает как Java; Вы имеете отдельный переменный указатель, который ссылается на хранилище, направляемое из кучи, как так:
+---+
| | line -------+
+---+ |
... |
+---+ |
| | line[0] <---+
+---+
| | line[1]
+---+
...
+---+
| | line[255]
+---+
В отличии от Java, C не будет автоматически вышлет эту память, когда нет больше ссылок на него. Вы должны явно освободить его, когда вы закончите с ним, используя функцию free
библиотеки:
free(line);
Что касается ваших конкретных вопросов:
fgets(*line, sizeof(line), stdin);
Когда используется символ указателя ' * ', а когда нет? В приведенном выше примере включается «*» в fgets, необходимо или правильно?
Неточная информация; fgets
ожидает, что первый аргумент имеет тип «указатель на char
»; выражение *line
имеет тип char
. Это следует из декларации:
char *line;
Во-вторых, sizeof(line)
только дает вам размер указателя, а не размер того, что указатель не на; если вы не хотите читать точно sizeof (char *)
байт, вы должны будете использовать другое выражение, чтобы указать количество символов для чтения:
fgets(line, 256, stdin);
Теперь я хотел бы создать массив строк, или, вернее, массив указателей, указывающих на строки. Сделаю ли я это следующим образом?
char *arr[20]; // Declares an array of strings with 20 elements
C не имеет отдельный "строка" тип данных так, как C++ или Java делать; в C, строка - это просто последовательность символов, заканчивающихся на 0. Они хранятся как массивы char
. Обратите внимание, что все, что вы указали выше, представляет собой 20-элементный массив указателей на char
; эти указатели могут указывать на вещи, которые не являются строками.
Если все струны будет иметь ту же максимальную длину, вы можете объявить 2D массив char
так:
char arr[NUM_STRINGS][MAX_STRING_LENGTH + 1]; // +1 for 0 terminator
, а затем вы бы назначить каждую строку как
strcpy(arr[i], "some string");
strcpy(arr[j], some_other_variable);
strncpy(arr[k], MAX_STRING_LENGTH, another_string_variable);
, хотя остерегайтесь strncpy
; он не будет автоматически добавлять терминатор 0 в строку назначения, если исходная строка была длиннее адресата. Вы должны убедиться, что терминатор присутствует, прежде чем пытаться использовать его с остальной библиотекой строк.
Если вы хотите, чтобы выделить место для каждой строки отдельно, вы можете объявить массив указателей, а затем выделить каждый указатель:
char *arr[NUM_STRINGS];
...
arr[i] = malloc(strlen("some string") + 1);
strcpy(arr[i], "some string");
...
arr[j] = strdup("some string"); // not available in all implementations, calls
// malloc under the hood
...
arr[k] = "some string"; // arr[k] contains the address of the *string literal*
// "some string"; note that you may not modify the contents
// of a string literal (the behavior is undefined), so
// arr[k] should not be used as an argument to any function
// that tries to modify the input parameter.
Обратите внимание, что каждый элемент arr
является значение указателя; указывают ли эти указатели на строки (0-концевые последовательности char
) или не зависит от вас.
Теперь еще хуже, мне нужен массив массивов строк (например, если бы я хотел хранить несколько векторов аргументов, чтобы выполнить несколько команд в последовательности труб). Будет ли оно объявлено следующим образом?
char **vector_arr[20]; // An array of arrays of strings
Что вы объявили массив указателей на указатели на символ; обратите внимание, что это совершенно верно, если вы не знаете, сколько указателей на char
вам нужно сохранить в каждом элементе. Тем не менее, если вы знаете, максимальное количество аргументов в элементе, может быть понятнее написать
char *vector_arr[20][N];
В противном случае, вам придется выделить каждый массив char *
динамически:
char **vector_arr[20] = { NULL }; // initialize all the pointers to NULL
for (i = 0; i < 20; i++)
{
// the type of the expression vector_arr is 20-element array of char **, so
// the type of the expression vector_arr[i] is char **, so
// the type of the expression *vector_arr[i] is char *, so
// the type of the expression vector[i][j] is char *, so
// the type of the expression *vector_arr[i][j] is char
vector_arr[i] = malloc(sizeof *vector_arr[i] * num_args_for_this_element);
if (vector_arr[i])
{
for (j = 0; j < num_args_for_this_element)
{
vector_arr[i][j] = malloc(sizeof *vector_arr[i][j] * (size_of_this_element + 1));
// assign the argument
strcpy(vector_arr[i][j], argument_for_this_element);
}
}
}
Таким образом, каждый Элемент vector_arr
представляет собой N-элементный массив указателей на массивы M-элементов от char
.
Слишком много вопросов здесь. Предложите прочитать хорошую книгу C. – OldProgrammer
На самом деле, я думаю, что OP удалось ударить по большинству всех вопросов с массивом/строкой в одном сообщении и сделать это довольно логично и красноречиво. Должна ли почта следить за сайтом и диктовать его? Может быть. Но хороший ответ будет полезной ссылкой на одной странице. – Duck
Это может сделать приятную ссылку, но я не уверен, что StackOverflow пишет о ссылках/учебниках? – keshlam