У меня есть этот сегмент кода, который вводит различные строки. В конце концов, у них не обязательно должна быть новая строка, они мне нужны друг за другом, потому что я пишу их в файле 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 по длине, но в обоих случаях у меня есть только первая строка без новой строки, а в остальных она все равно есть. Я думал, что, заменив символ новой строки нулевым ограничителем, я решу свою проблему, но, возможно, я что-то упускаю. Любой намек на решение этой проблемы? Я немного запуталась ...
Другая возможность - strtok(recipe.ingredients[j], "\n");
Является ли recipe.ingredients массивом char, указателем на char, массивом массивов или массивом указателей? Это не совсем понятно, исходя из того, как вы его используете.
@SteveSummit это тип char**
@SteveSummit В любом случае я решил это с помощью strtok, большое спасибо
важный намек, который дал вам @SteveSummit, заключается в том, что у вас есть дополнительный знак = в предполагаемом операторе присваивания.
@bruceg О боже, я только что заметил, спасибо тебе тоже
Но это еще не все: если назначение '\0' должно было перезаписать новую строку, и, учитывая, что мы только что вычислили len = strlen(recipe.ingredients[j]), тогда нам нужно назначить что-то вроде recipe.ingredients[j][len] = "\0". Простое исправление == и замена его на recipe.ingredients[len] = "\0" приводит к совершенно другим результатам. (Если, то есть, recipe.ingredients "двумерен", как это звучит.)
@Goner, если recipe.ingredients - это char **, важно будет настроить его довольно осторожно, чтобы указать на правильно назначенные указатели на несколько различных ингредиентов (проиндексированных j). Если этот код неправильный, он усугубит любые проблемы, с которыми вы сталкиваетесь, когда пытаетесь читать строки, находить новые строки и подавлять их.
@SteveSummit Я уже распределил их динамически, у меня просто была проблема с новой строкой в конце, на самом деле, когда я не распределил их правильно, у меня было много ошибок сегментации





В опубликованном фрагменте кода много проблем:
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, но только потому, что это школьный проект, и он не позволит нам использовать перерыв для этого
Вы ломка вне цикла чтения в любом из этих условий, поэтому break подходит. Вы также можете использовать вместо него goto. например goto readdone;, а затем поместите readdone:; после закрывающей фигурной скобки в цикл чтения. break подходит для выхода из 1-цикла, goto - это обязательный для выхода из вложенного цикла за один вызов.
Вдобавок к тому, что написал @ DavidC.Rankin выше, разделение «подзадач» на отдельные функции дает вам больше свободы при прерывании / выходе из цикла, потому что вы можете возвращаться из функции в любой момент. Итак, если у вас был двойной, тройной или даже более сложный цикл, который что-то делает, помещение его в отдельную функцию позволяет вам прервать весь вложенный цикл (в этой функции), просто вернувшись из функции в любой нужный вам момент. Разделение работы на такие функции упрощает их понимание и для нас, людей. [...]
[...] Итак, @Goner, причина неодобрения goto, а иногда и break, заключается не в том, что они плохие или что-то в этом роде, а в том, что почти всегда есть лучший способ: более читаемый, легкий для понимания и поэтому ремонтопригодный, что очень важно на практике. Обычно путем разделения выполняемой работы на отдельные функции. (Однако для этого требуется немного опыта, потому что не менее важно именно как для разделения работы на функции.)
Слишком много учителей осуждают такие инструкции управления потоком, как break, continue, несколько операторов return и, конечно же, goto. Некоторые руководства по стилю кодирования даже полностью их запрещают. Важно понимать различные возможности, которые предлагает C, чтобы вы могли читать чужой код. Циклы do / while иногда выдвигаются как способ избежать появления break из бесконечных циклов, но, по моему опыту, они почти всегда содержат логические ошибки, которые представляют собой гораздо более серьезные проблемы. Ваш фрагмент кода - еще один такой пример. Есть 3 причины для выхода из цикла. Явные операторы break подходят.
Это всего лишь расширенный комментарий, который может быть кому-то полезен.
ответ от 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);
Эту последнюю форму легче всего читать и понимать, а значит, и поддерживать, но программисты обычно предпочитают предыдущую, потому что она короче. Надеюсь, этого расширенного комментария достаточно, чтобы показать, почему короче не всегда лучше. :)
Спасибо за терпение, я ценю это
Не совсем то, что вы просили, но эти две строчки:
len = strlen(recipe.ingredients[j]) + 1; recipe.ingredients[len] == "\0";не делают то, что вы думаете.