У меня есть программа, которая пытается ввести файл и прочитать его в ОЗУ с помощью sscanf. Однако я даже не смог проверить свою логику, потому что мой компилятор выдает некоторые ошибки, которые, как я полагаю, происходят из-за одной и той же ошибки (либо повторяющейся в нескольких местах, либо сбоя в одном месте, вызывающего эффект домино).
Проблема связана с объявлением двумерного массива символов в куче с помощью:
char[][] swapKey;
(Кстати, я использую терминал Cygwin)
Я пытаюсь объявить функцию, имеющую одинаковый тип возвращаемого значения:
char[][] getKey() {
Это вызывает ошибку:
swapCode.c:13:5: error: expected identifier or ‘(’ before ‘[’ token
13 | char[][] getKey() {
| ^
Похоже, он думает, что я не закончил объявление «char[][]»?
Есть пара ошибок того же жанра, поэтому я собираюсь включить весь файл (всего около 50 строк), потому что я точно не знаю, откуда возникла проблема. Я также включу именно то, что сказал компилятор в ответ на попытку скомпилировать:
#include <stdio.h>
#define SWAP_CODE_KEY "testSwapCodeKey.txt"
int changes;
void printSwapKey (char[][] swapKey) {
for (int i = 0; i < changes; i++) {
printf("%c=%c\n", swapKey[0], swapKey[1]);
}
}
char[][] getKey() {
FILE *file = fopen(SWAP_CODE_KEY, "r");
if (file == NULL) {
return 0;
}
char line[5];
int lines = 0;
if (sscanf(fgets(line, sizeof(line, file)), "changes=%d", changes) == 1) {
printf("Succesfully retrieved number of changes from file: %d\n", changes);
} else {
printf("Failed to retrieve number of changes from file\n");
exit(0);
}
char[changes][1] swapKey;
for (int i = 0; i < changes; i++) {
swapKey[i][0] = "";
swapKey[i][1] = "";
}
printf("\n");
while(fgets(line, sizeof line, file) || lines >= changes) {
lines++;
char originalCharacter = "";
char swappedCharacter = "";
if (sscanf(line, "%c=%c", originalCharacter, swappedCharacter) == 2) {
printf("Trying to add line to swapKey at line %d\n", lines);
swapKey[lines][0] = originalCharacter;
swapKey[lines][1] = swappedCharacter;
printf("Added line to swapKey at line %d\n", lines);
} else {
printf("Failed to scan line at line %d", lines);
exit(0);
}
}
return swapKey;
}
int main(int argc,char *argv[]) {
char[][] swapKey = getKey();
printSwapKey (swapKey);
}
И компилятор:
<myName> <myName>Laptop ~
$ cc swapCode.c -Wall
swapCode.c:7:24: error: array type has incomplete element type ‘char[]’
7 | void printSwapKey (char[][] swapKey) {
| ^
swapCode.c:7:24: note: declaration of multidimensional array must have bounds for all dimensions except the first
swapCode.c:7:29: error: expected ‘;’, ‘,’ or ‘)’ before ‘swapKey’
7 | void printSwapKey (char[][] swapKey) {
| ^~~~~~~
swapCode.c:13:5: error: expected identifier or ‘(’ before ‘[’ token
13 | char[][] getKey() {
| ^
swapCode.c: In function ‘main’:
swapCode.c:55:9: error: expected identifier or ‘(’ before ‘[’ token
55 | char[][] swapKey = getKey();
| ^
swapCode.c:56:5: warning: implicit declaration of function ‘printSwapKey’ [-Wimplicit-function-declaration]
56 | printSwapKey (swapKey);
| ^~~~~~~~~~~~
swapCode.c:56:19: error: ‘swapKey’ undeclared (first use in this function)
56 | printSwapKey (swapKey);
| ^~~~~~~
swapCode.c:56:19: note: each undeclared identifier is reported only once for each function it appears in
<myName> <myName>Laptop ~
$
Извините за расплывчатый вопрос, но я провел около 1,3 часа, просто глядя на это и не приближаясь.
«Я считаю, что все происходит из-за одной и той же ошибки». Вы правы. «Похоже, он думает, что я не закончил объявление «char[][]»?» Вы снова правы. Вы этого не сделали. "объявление двумерного массива символов в куче с помощью: char[][] swapKey;" Нет, это ничего не делает с кучей и не является правильным определением массива, поскольку в нем отсутствуют необходимые размеры массива, и таким образом вы не можете возвращать массивы. Вы можете получить хотя бы часть того, что хотите, используя VLA, но не сможете сделать это для типа возвращаемого значения функции.
char[changes][1] swapKey;
должно быть char swapKey[changes][2];
. Но вы не сможете использовать ее в качестве возвращаемого значения функции, поскольку вы 1) не можете возвращать массивы из функции и 2) это локальная переменная и не продолжает существовать после выхода из функции. Есть несколько способов подойти к этому, но похоже, что в ваших учебных материалах есть серьезные недостатки, и вам отчаянно нужно заменить их чем-то лучшим.
1) sizeof(line, file)
то же, что sizeof(file)
. 2) Попробуйте fgets(line, sizeof(line, file))
--> fgets(line, sizeof line, file)
.
Обратите внимание: я удалил свой ответ, заметив, что он не полностью отвечает на ваш вопрос. Тем временем я расширил свой ответ и теперь восстановил его.
@AviBerger Да, он сломан по другим причинам, но разве это не так? Мне нужен массив «изменения» на 2, и, поскольку он находится в C, 0 считается индексом.
Да, 0 считается индексом. Определение дает не максимальное значение индекса, а количество. Таким образом, char swapKey[changes][2];
будет массивом изменений на 2 с действительными значениями первого индекса от 0 до изменений - 1 и допустимыми значениями второго индекса от 0 до 1. ``char swapKey[changes][1];` изменяется на 1 и имеет только 0 как действительный второй индекс, поэтому swapKey[i][1]
будет доступом за пределами границ, поскольку он пытается получить доступ ко второму только одному элементу.
@AviBerger Есть ли причина, по которой в большинстве других случаев он начинается с нуля, но в объявлении предполагается буквальное количество?
В C невозможно вернуть массив из функции. Поэтому объявление функции
char[][] getKey() {
неправильно.
Однако вызывающая функция может передать указатель на начало массива, а вызываемая функция может изменить содержимое массива с помощью этого указателя, чтобы вызывающая функция могла затем получить доступ к новому содержимому массива. Например:
#include <stdio.h>
void write_array( char *arr, size_t arr_size )
{
snprintf( arr, arr_size, "%s", "banana" );
}
int main( void )
{
// make array large enough to store both "apple"
// and "banana"
char text[7] = "apple";
printf( "Text before function call: %s\n", text );
write_array( text, sizeof text );
printf( "Text after function call: %s\n", text );
}
Эта программа напечатает следующее:
Text before function call: apple
Text after function call: banana
Линия
char[][] swapKey = getKey();
это неверно.
В языке C размер массива нельзя изменить после его создания, поэтому при его создании необходимо указать его размер.
Однако допустимо не указывать размер массива, если его размер подразумевается инициализатором. Например, если вы напишете
int arr[] = { 5, 6, 7 };
тогда размер массива равен 3
, поскольку вы инициализировали его тремя элементами.
Но при инициализации двумерного массива может подразумеваться только размер внешнего массива; размер внутреннего массива должен быть указан явно. Например:
int arr[][3] =
{
{ 5, 6, 7 },
{ 8, 9, 10 },
{ 11, 12, 13 },
{ 14, 15, 16 }
};
В этом случае подразумевается, что размер внешнего массива равен 4
, но размер внутреннего массива должен быть явно указан как 3
.
Объявление функции
void printSwapKey (char[][] swapKey) {
неправильно.
В C невозможно напрямую передать массив в функцию. Когда объявляется функция, принимающая массив в качестве параметра, компилятор автоматически преобразует тип данных параметра в указатель на элемент массива.
Например, в одном из предыдущих разделов я использовал следующее объявление функции:
void write_array( char *arr, size_t size )
Я мог бы также написать следующее:
void write_array( char arr[], size_t size )
В этом случае компилятор автоматически преобразовал бы параметр char []
в char *
. Размер массива указывать не обязательно, поскольку эта информация все равно отбрасывается.
Однако это не относится к многомерным массивам. Многомерный массив — это не что иное, как массив массивов. Если вы объявите функцию, принимающую многомерный массив в качестве параметра, то, как упоминалось выше, компилятор автоматически преобразует параметр в указатель на элемент (внешнего) массива. Поэтому компилятор преобразует параметр в указатель на (внутренний) массив. Например, если вы укажете int arr[4][3]
в качестве параметра, то компилятор преобразует этот параметр в int (*arr)[3]
(указатель на массив из 3 элементов типа int
).
Поскольку отбрасывается только внешнее измерение многомерного массива, вы можете не указывать только размер внешнего массива, но не размер внутреннего массива. Вот почему ваше объявление функции неверно.
Хорошо, но... Рекомендую избегать слова "размер", так как новичкам зачастую сложно понять разницу между "количеством элементов" и "количеством байтов" при работе с массивом... "Размер" - это слишком общий, и, по моему мнению, его следует избегать...
«автоматически преобразует тип данных параметра в указатель на элемент массива» -> «автоматически преобразует тип данных параметра в указатель на первый элемент массива» было бы более точным, ИМХО.
@babon: Все элементы массива имеют один и тот же тип данных. Поэтому в данном контексте это не имеет значения. Однако вы правы в том, что при фактической передаче массива функции массив распадается на указатель на первый элемент массива.
@AndreasWenzel Согласен, тип данных будет таким же. Просто хотел указать, чего ожидать при разыменовании.
@Fe2O3: Я согласен, что это проблема. Однако «размер» — такое красивое короткое слово, и его использование не является технически неправильным («размер в элементах» или «размер в байтах»). Я мог бы изменить идентификатор arr_size
на arr_num_elements
, но, на мой взгляд, это сделало бы его слишком длинным. А если я использую идентификатор arr_size
, то для последовательности я хочу использовать термин «размер» в остальной части текста. Мне бы хотелось использовать другой термин, который был бы менее двусмысленным, но при этом красивым и коротким. Однако боюсь, что термин «длина» не намного лучше, чем «размер», поскольку он тоже неоднозначен.
@AndreasWenzel Каждый выбирает свои собственные соглашения. Я понимаю, что вы написали, но у меня есть небольшой опыт. Если/когда я осознаю необходимость/желание прояснить различие, я использую варианты nElem
и nBytes
(или что-то подобное). Все хорошо... Просто комментирую... Приветствую! :-)
... (PS: Обычно возникает в функциях, где время жизни переменных мимолетно и локально... Краткость (с комментариями, если желательно) помогает быстро "сканировать и искать" :-)
...
@AndreasWenzel Только что проверил мой документ C. fread()
использует имена переменных count
и size
, а calloc()
использует num
и size
... Мой вывод из этих двух случаев заключается в том, что size
следует использовать как «количество байтов». Для функций, получающих массив (из элементов известного «размера»), следует использовать другое имя, чтобы указать, сколько элементов находится в массиве... Просто провожу время... :-)
Ура!
@Fe2O3: Я только что заметил, что в стандартной библиотеке C++ std::vector::size возвращает размер вектора в элементах, а не в байтах. Поэтому я боюсь, что эта война уже проиграна.
Не можете, так как такого типа нет.