Как удалить новую строку из динамических строк

У меня есть этот сегмент кода, который вводит различные строки. В конце концов, у них не обязательно должна быть новая строка, они мне нужны друг за другом, потому что я пишу их в файле CSV. Я положил это на некоторое время

    do {
        printf("Put the ingredient:\n");
        fgets(recipe.ingredients[j], 30, stdin);
        len = strlen(recipe.ingredients[j]) + 1;
        recipe.ingredients[len] == "\0";
        fprintf(fbr, "%s,", recipe.ingredients[j]);
        j++;
        counter++;
        printf("Do you want to continue? (yes/no)");
        fgets(sentinel, 4, stdin);
    } while(strcmp("yes", sentinel) == 0);

Проблема в том, что первая строка, которую я ввожу, не имеет символа новой строки, так как я поставил это условие. Я пробовал как добавлять, так и вычитать 1 по длине, но в обоих случаях у меня есть только первая строка без новой строки, а в остальных она все равно есть. Я думал, что, заменив символ новой строки нулевым ограничителем, я решу свою проблему, но, возможно, я что-то упускаю. Любой намек на решение этой проблемы? Я немного запуталась ...

Не совсем то, что вы просили, но эти две строчки: len = strlen(recipe.ingredients[j]) + 1; recipe.ingredients[len] == "\0"; не делают то, что вы думаете.

Steve Summit 06.07.2018 23:19

Другая возможность - strtok(recipe.ingredients[j], "\n");

Steve Summit 06.07.2018 23:21

Является ли recipe.ingredients массивом char, указателем на char, массивом массивов или массивом указателей? Это не совсем понятно, исходя из того, как вы его используете.

Steve Summit 06.07.2018 23:23

@SteveSummit это тип char**

Goner 06.07.2018 23:28

@SteveSummit В любом случае я решил это с помощью strtok, большое спасибо

Goner 06.07.2018 23:38

важный намек, который дал вам @SteveSummit, заключается в том, что у вас есть дополнительный знак = в предполагаемом операторе присваивания.

bruceg 06.07.2018 23:47

@bruceg О боже, я только что заметил, спасибо тебе тоже

Goner 06.07.2018 23:48

Но это еще не все: если назначение '\0' должно было перезаписать новую строку, и, учитывая, что мы только что вычислили len = strlen(recipe.ingredients[j]), тогда нам нужно назначить что-то вроде recipe.ingredients[j][len] = "\0". Простое исправление == и замена его на recipe.ingredients[len] = "\0" приводит к совершенно другим результатам. (Если, то есть, recipe.ingredients "двумерен", как это звучит.)

Steve Summit 07.07.2018 00:19

@Goner, если recipe.ingredients - это char **, важно будет настроить его довольно осторожно, чтобы указать на правильно назначенные указатели на несколько различных ингредиентов (проиндексированных j). Если этот код неправильный, он усугубит любые проблемы, с которыми вы сталкиваетесь, когда пытаетесь читать строки, находить новые строки и подавлять их.

Steve Summit 07.07.2018 00:22

@SteveSummit Я уже распределил их динамически, у меня просто была проблема с новой строкой в ​​конце, на самом деле, когда я не распределил их правильно, у меня было много ошибок сегментации

Goner 07.07.2018 00:30
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
10
96
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

В опубликованном фрагменте кода много проблем:

  • вы не проверяете возвращаемое значение fgets(), вызывая неопределенное поведение при доступе к содержимому буфера в случае сбоя fgets().
  • вызов fgets(sentinel,4,stdin); будет читать только до 3 байтов в sentinel, чтобы оставить место для конечного нулевого терминатора. Следовательно, перевод строки, введенный пользователем после yes, останется во входном потоке и приведет к немедленному возврату следующего вызова fgets() с содержимым буфера "\n".
  • len = strlen(recipe.ingredients[j]) + 1; слишком велик: смещение новой строки будет strlen(recipe.ingredients[j]) - 1, если он присутствует и если recipe.ingredients[j] не является пустой строкой.
  • recipe.ingredients[len] == "\0"; полностью подделка: это просто сравнение, а не назначение, и он сравнивает яблоки и апельсины: char и const char *.

Более простой способ удалить новую строку, если она есть:

char *p = recipe.ingredients[j];
p[strcspn(p, "\n")] = '\0';   // overwrite the newline, if present

Вот модифицированная версия:

for (;;) {
    char sentinel[100];
    char *p;

    printf("Put the ingredient: ");
    if (!fgets(recipe.ingredients[j], 30, stdin))
        break;
    p = recipe.ingredients[j];
    p[strcspn(p, "\n")] = '\0';   // overwrite the newline, if present
    fprintf(fbr, "%s,", recipe.ingredients[j]);
    j++;
    counter++;
    printf("Do you want to continue? (yes/no): ");
    if (!fgets(sentinel, sizeof sentinel, stdin))
        break;
    if (strcmp(sentinel, "yes") != 0)
        break;
}

Обратите внимание, что вы также должны убедиться, что j не увеличивается сверх размера массива recipe.ingredient.

Прежде всего, я благодарю вас за терпение и ваше время. Я бродил, есть ли другие альтернативы, кроме перерыва, поэтому я предпочел поставить do while вместо for, но только потому, что это школьный проект, и он не позволит нам использовать перерыв для этого

Goner 07.07.2018 00:50

Вы ломка вне цикла чтения в любом из этих условий, поэтому break подходит. Вы также можете использовать вместо него goto. например goto readdone;, а затем поместите readdone:; после закрывающей фигурной скобки в цикл чтения. break подходит для выхода из 1-цикла, goto - это обязательный для выхода из вложенного цикла за один вызов.

David C. Rankin 07.07.2018 00:56

Вдобавок к тому, что написал @ DavidC.Rankin выше, разделение «подзадач» на отдельные функции дает вам больше свободы при прерывании / выходе из цикла, потому что вы можете возвращаться из функции в любой момент. Итак, если у вас был двойной, тройной или даже более сложный цикл, который что-то делает, помещение его в отдельную функцию позволяет вам прервать весь вложенный цикл (в этой функции), просто вернувшись из функции в любой нужный вам момент. Разделение работы на такие функции упрощает их понимание и для нас, людей. [...]

Nominal Animal 07.07.2018 02:01

[...] Итак, @Goner, причина неодобрения goto, а иногда и break, заключается не в том, что они плохие или что-то в этом роде, а в том, что почти всегда есть лучший способ: более читаемый, легкий для понимания и поэтому ремонтопригодный, что очень важно на практике. Обычно путем разделения выполняемой работы на отдельные функции. (Однако для этого требуется немного опыта, потому что не менее важно именно как для разделения работы на функции.)

Nominal Animal 07.07.2018 02:03

Слишком много учителей осуждают такие инструкции управления потоком, как break, continue, несколько операторов return и, конечно же, goto. Некоторые руководства по стилю кодирования даже полностью их запрещают. Важно понимать различные возможности, которые предлагает C, чтобы вы могли читать чужой код. Циклы do / while иногда выдвигаются как способ избежать появления break из бесконечных циклов, но, по моему опыту, они почти всегда содержат логические ошибки, которые представляют собой гораздо более серьезные проблемы. Ваш фрагмент кода - еще один такой пример. Есть 3 причины для выхода из цикла. Явные операторы break подходят.

chqrlie 07.07.2018 19:37

Это всего лишь расширенный комментарий, который может быть кому-то полезен.

ответ от chqrlie соответствует заданному вопросу.

Я просто хотел немного расширить эту проблему и показать альтернативный способ, который можно легко расширить до обрезки начальных и конечных пробелов.

Основная логика проста. Мы используем два индекса в строке: i и o. i - это индекс следующего проверяемого символа, а o - это индекс, в котором мы сохраняем следующий символ, который хотим сохранить.

Теперь я предпочитаю использовать указатели, а не индексы для массива символов. Это не имеет большого значения, но для меня эту форму (по сравнению с формой индексации массива) легче читать и писать. Мне это нравится еще и потому, что обычно достаточно сбить с толку тех, кто берет код из Интернета, чтобы выдать их как домашнее задание, совершенно не понимая, что делает код. Мне тоже нравится сбивать с толку этих читеров.


Функция, удаляющая все начальные пробелы из строки, часто называется ltrim(). Вот как это можно реализовать. Этот также удаляет все управляющие символы:

#include <stdlib.h>
#include <ctype.h>

char *ltrim(char *s)
{
    if (s != NULL) {
        char *in = s;
        char *out = s;

        /* Skip leading whitespace and control characters. */
        while (isspace(*in) || iscntrl(*in))
            in++;

        /* If there was no leading whitespace,
           we can simply return the original string. */
        if (in == out)
            return s;

        /* Move the rest of the string to start at s. */
        while (*in)
            *(in++) = *(out++);

        /* Terminate the string. */
        *out = '\0';
    }

    /* The function always returns the argument. */
    return s;
}

Обратите внимание, как in указывает на следующий символ в строке, который нужно исследовать, а out указывает на следующую позицию для сохранения сохраненного символа. У нас всегда есть in >= out, то есть мы не будем пытаться прочитать позицию, которую мы уже перезаписали, потому что всякий раз, когда мы увеличиваем out, мы также увеличиваем in.

Есть несколько способов реализовать rtrim(), функцию, которая удаляет все конечные пробелы, включая новую строку. Вот один из подходов. Он удаляет как завершающие пробелы, так и управляющие символы.

char *rtrim(char *s)
{
    if (s) {
        char *oug = s;

        /* This just implements out = s + strlen(s). */
        while (*out != '\0')
            out++;

        /* Back over trailing whitespace and controls. */
        while (out > s && (isspace(out[-1]) || iscntrl(out[-1])))
            out--;

        /* Terminate the string here. */
        *out = '\0';
    }

    /* This function also always returns the argument. */
    return s;
}

На этот раз, после того как мы скопировали (или проверили) все символы, мы «делаем резервную копию» завершающих символов, которые хотим удалить. Поскольку out указывает на позицию следующий, предыдущий символ - out[-1] (эквивалент *(out-1) в C). Однако мы должны быть осторожны, чтобы не перевернуть начало строки.

Вместо того, чтобы просто реализовать trim() как вызов обеих вышеуказанных функций, более эффективно объединить их в одну функцию:

char *trim(char *s)
{
    if (s != NULL) {
        char *in = s;
        char *out = s;

        /* Skip leading whitespace and control characters. */
        while (isspace(*in) || iscntrl(*in))
            in++;

        /* Move the rest of the string to start at s. */
        while (*in)
            *(in++) = *(out++);

        /* Back up over trailing whitespace and control characters. */
        while (out > s && (isspace(out[-1]) || iscntrl(out[-1])))
            out--;

        /* Terminate the string. */
        *out = '\0';
    }

    /* Always return the argument. */
    return s;
}

Я обычно использую только trim().

В некоторых случаях вам может потребоваться функция, которая удаляет как начальные, так и конечные пробелы и управляющие символы, но также преобразует все последовательные пробелы и управляющие символы в один пробел, например, для очистки некоторого ввода. Это только немного сложнее:

char *clean(char *s)
{
    if (s != NULL) {
        char *in = s;
        char *out = s;

        /* Skip leading whitespace and control characters. */
        while (isspace(*in) || iscntrl(*in))
            in++;

        /* Move the rest of the string to start at s,
           combining consecutive whitespaces and control
           characters to a single space. */
        while (*in)
            if (isspace(*in) || iscntrl(*in)) {
                /* Skip all consecutive whitespaces and
                   control characters first. */
                while (isspace(*in) || iscntrl(*in));
                    in++;
                /* "Replace" them with a single space. */
                *(out++) = ' ';
            } else
                *(in++) = *(out++);

        /* Back up over the one trailing space we might have copied. */
        if (out > s && out[-1] == ' ')
            out--;

        /* Terminate the string. */
        *out = '\0';
    }

    /* Always return the argument. */
    return s;
}

По сути, единственное отличие от trim() состоит в том, что в цикле копирования, если мы встречаем пробел или управляющий символ, мы пропускаем их все (то есть последовательные), а затем сохраняем только один пробел, чтобы «заменить» их этим единственным космос.


Поскольку указанные выше функции изменяют данную строку, вы не можете использовать их в строковых литералах. То есть что-то вроде

char *mystring = clean(" This Will Not Work ");

или же

char *oldstring = " Thiss Will Not Work Either ";
char *mystring = clean(oldstring);

не будет работать, потому что вы не должны пытаться изменять строковые литералы. Помните, что в приведенных выше формах oldstring и mystring являются указателями на строковые литералы.

Вместо этого создайте массив символов, который инициализируется строковым литералом. Впоследствии вы можете изменить массив символов:

char  mybuffer[] = " This will work just Fine.\n";
clean(mybuffer);

или же

char  line[256] = "This will work, too.\n";
printf("Read '%s'.\n", trim(line));

Обратите внимание на то, что возвращаемое значение (указатель на строку) не используется в первом примере, а предоставляется как строка для печати во втором примере. Если вы используете форму, использованную в последнем примере, помните, что она изменяет line[]; строка все еще "обрезана" после вызова printf. Проще говоря, последнее в точности эквивалентно

char  line[256] = "This will work, too.\n";
trim(line);
printf("Read '%s'.\n", line);

Эту последнюю форму легче всего читать и понимать, а значит, и поддерживать, но программисты обычно предпочитают предыдущую, потому что она короче. Надеюсь, этого расширенного комментария достаточно, чтобы показать, почему короче не всегда лучше. :)

Спасибо за терпение, я ценю это

Goner 08.07.2018 01:05

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