Как объявить функцию, которая возвращает char[][]?

У меня есть программа, которая пытается ввести файл и прочитать его в ОЗУ с помощью 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 часа, просто глядя на это и не приближаясь.

Не можете, так как такого типа нет.

user2357112 28.07.2024 05:59

«Я считаю, что все происходит из-за одной и той же ошибки». Вы правы. «Похоже, он думает, что я не закончил объявление «char[][]»?» Вы снова правы. Вы этого не сделали. "объявление двумерного массива символов в куче с помощью: char[][] swapKey;" Нет, это ничего не делает с кучей и не является правильным определением массива, поскольку в нем отсутствуют необходимые размеры массива, и таким образом вы не можете возвращать массивы. Вы можете получить хотя бы часть того, что хотите, используя VLA, но не сможете сделать это для типа возвращаемого значения функции.

Avi Berger 28.07.2024 06:04
char[changes][1] swapKey; должно быть char swapKey[changes][2];. Но вы не сможете использовать ее в качестве возвращаемого значения функции, поскольку вы 1) не можете возвращать массивы из функции и 2) это локальная переменная и не продолжает существовать после выхода из функции. Есть несколько способов подойти к этому, но похоже, что в ваших учебных материалах есть серьезные недостатки, и вам отчаянно нужно заменить их чем-то лучшим.
Avi Berger 28.07.2024 06:17

1) sizeof(line, file) то же, что sizeof(file). 2) Попробуйте fgets(line, sizeof(line, file)) --> fgets(line, sizeof line, file).

chux - Reinstate Monica 28.07.2024 06:17

Обратите внимание: я удалил свой ответ, заметив, что он не полностью отвечает на ваш вопрос. Тем временем я расширил свой ответ и теперь восстановил его.

Andreas Wenzel 28.07.2024 06:45

@AviBerger Да, он сломан по другим причинам, но разве это не так? Мне нужен массив «изменения» на 2, и, поскольку он находится в C, 0 считается индексом.

Gbotdays 28.07.2024 19:30

Да, 0 считается индексом. Определение дает не максимальное значение индекса, а количество. Таким образом, char swapKey[changes][2]; будет массивом изменений на 2 с действительными значениями первого индекса от 0 до изменений - 1 и допустимыми значениями второго индекса от 0 до 1. ``char swapKey[changes][1];` изменяется на 1 и имеет только 0 как действительный второй индекс, поэтому swapKey[i][1] будет доступом за пределами границ, поскольку он пытается получить доступ ко второму только одному элементу.

Avi Berger 28.07.2024 19:52

@AviBerger Есть ли причина, по которой в большинстве других случаев он начинается с нуля, но в объявлении предполагается буквальное количество?

Gbotdays 30.07.2024 03:49
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
106
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Возврат массива из функции

В 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).

Поскольку отбрасывается только внешнее измерение многомерного массива, вы можете не указывать только размер внешнего массива, но не размер внутреннего массива. Вот почему ваше объявление функции неверно.

Хорошо, но... Рекомендую избегать слова "размер", так как новичкам зачастую сложно понять разницу между "количеством элементов" и "количеством байтов" при работе с массивом... "Размер" - это слишком общий, и, по моему мнению, его следует избегать...

Fe2O3 28.07.2024 06:51

«автоматически преобразует тип данных параметра в указатель на элемент массива» -> «автоматически преобразует тип данных параметра в указатель на первый элемент массива» было бы более точным, ИМХО.

babon 28.07.2024 06:54

@babon: Все элементы массива имеют один и тот же тип данных. Поэтому в данном контексте это не имеет значения. Однако вы правы в том, что при фактической передаче массива функции массив распадается на указатель на первый элемент массива.

Andreas Wenzel 28.07.2024 07:02

@AndreasWenzel Согласен, тип данных будет таким же. Просто хотел указать, чего ожидать при разыменовании.

babon 28.07.2024 07:05

@Fe2O3: Я согласен, что это проблема. Однако «размер» — такое красивое короткое слово, и его использование не является технически неправильным («размер в элементах» или «размер в байтах»). Я мог бы изменить идентификатор arr_size на arr_num_elements, но, на мой взгляд, это сделало бы его слишком длинным. А если я использую идентификатор arr_size, то для последовательности я хочу использовать термин «размер» в остальной части текста. Мне бы хотелось использовать другой термин, который был бы менее двусмысленным, но при этом красивым и коротким. Однако боюсь, что термин «длина» не намного лучше, чем «размер», поскольку он тоже неоднозначен.

Andreas Wenzel 28.07.2024 07:16

@AndreasWenzel Каждый выбирает свои собственные соглашения. Я понимаю, что вы написали, но у меня есть небольшой опыт. Если/когда я осознаю необходимость/желание прояснить различие, я использую варианты nElem и nBytes (или что-то подобное). Все хорошо... Просто комментирую... Приветствую! :-)... (PS: Обычно возникает в функциях, где время жизни переменных мимолетно и локально... Краткость (с комментариями, если желательно) помогает быстро "сканировать и искать" :-)...

Fe2O3 28.07.2024 07:20

@AndreasWenzel Только что проверил мой документ C. fread() использует имена переменных count и size, а calloc() использует num и size... Мой вывод из этих двух случаев заключается в том, что size следует использовать как «количество байтов». Для функций, получающих массив (из элементов известного «размера»), следует использовать другое имя, чтобы указать, сколько элементов находится в массиве... Просто провожу время... :-) Ура!

Fe2O3 28.07.2024 07:36

@Fe2O3: Я только что заметил, что в стандартной библиотеке C++ std::vector::size возвращает размер вектора в элементах, а не в байтах. Поэтому я боюсь, что эта война уже проиграна.

Andreas Wenzel 28.07.2024 12:07

Другие вопросы по теме