Intereting Posts
Использование распознавателя имен resolv.h с IPv6 C: у меня разные результаты с pow (10,2) и pow (10, j), j = 2; Прослушивание программного обеспечения для Linux Стрит: возможно ли выполнять сериализацию с помощью библиотеки C (GLib) Thrift? Как хранить данные, генерируемые событием в X11? Динамический multidimensional array в C Программа, которая печатает себя, даже если исходный файл удален strcpy () / strncpy () падает с элементом структуры с дополнительным пространством, когда оптимизация включена в Unix? кодировать программу, используя getchar из аргумента командной строки и putchar для отправки на декодирование Введите двоичный код вместо hex Неверный код секции сценария .relocate, _srelocate, неверен (ошибка GCC?) Что является удобной базой для библиотеки bignum и алгоритма тестирования первичности? Как инициализировать структуру с помощью гибкого элемента массива strtok, когда процесс выполняется двумя строками одновременно Поместить имя параметров в прототипы функции C?

Правильно используя sscanf

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

  • Между словом 1 и словом 2 должно быть пробел.
  • Между словом 2 и словом 3 должна быть запятая.
  • Пространства не являются обязательными между словом 2 и словом 3, но возможно любое количество пробелов.

Как я могу отделить слова 1, 2 и 3 слова и поместить данные в правильные переменные?

word1 word1 word2 word1 word2 , word3 word1 word2,word3 

Я подумал о чем-то вроде:

 sscanf("string", "%s %s,%s", word1, word2, word3); 

но он, похоже, не работает.

Я использую строгий C89.

 int n = sscanf("string", "%s %[^, ]%*[, ]%s", word1, word2, word3); 

Возвращаемое значение в n указывает вам, сколько присвоений было выполнено успешно. %[^, ] – это отрицательное соответствие символьного classа, которое находит слово, не включающее запятые или пробелы (добавьте вкладки, если хотите). %*[, ] – это совпадение, которое находит запятую или пробел, но подавляет назначение.

Я не уверен, что буду использовать это на практике, но он должен работать. Это, однако, непроверено.


Возможно, более плотная спецификация:

 int n = sscanf("string", "%s %[^, ]%*[,]%s", word1, word2, word3); 

Разница заключается в том, что не назначаемый class символов принимает только запятую. sscanf() останавливается в любом пространстве (или EOS, конец строки) после word2 и пропускает пробелы перед назначением на word3 . Предыдущее издание позволяло пробелу между вторым и третьим словами вместо запятой, что вопрос не позволяет строго.

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

 char word1[20], word2[32], word3[64]; int n = sscanf("string", "%19s %31[^, ]%*[,]%63s", word1, word2, word3); 

(Kernighan & Pike предлагают форматировать строку формата динамически в своей (отличной) книге «Практика программирования» .)


Просто нашел проблему: учитывая "word1 word2 ,word3" , он не читает word3 . Есть ли лекарство?

Да, есть лекарство, и это действительно тривиально. Добавьте пробел в строку формата перед спецификацией преобразования без присваивания, соответствующей запятой. Таким образом:

 #include  static void tester(const char *data) { char word1[20], word2[32], word3[64]; int n = sscanf(data, "%19s %31[^, ] %*[,]%63s", word1, word2, word3); printf("Test data: <<%s>>\n", data); printf("n = %d; w1 = <<%s>>, w2 = <<%s>>, w3 = <<%s>>\n", n, word1, word2, word3); } int main(void) { const char *data[] = { "word1 word2 , word3", "word1 word2 ,word3", "word1 word2, word3", "word1 word2,word3", "word1 word2 , word3", }; enum { DATA_SIZE = sizeof(data)/sizeof(data[0]) }; size_t i; for (i = 0; i < DATA_SIZE; i++) tester(data[i]); return(0); } 

Пример вывода:

 Test data: <> n = 3; w1 = <>, w2 = <>, w3 = <> Test data: <> n = 3; w1 = <>, w2 = <>, w3 = <> Test data: <> n = 3; w1 = <>, w2 = <>, w3 = <> Test data: <> n = 3; w1 = <>, w2 = <>, w3 = <> Test data: <> n = 3; w1 = <>, w2 = <>, w3 = <> 

Как только «не назначаемый class символов» принимает только запятую, вы можете сокращать это до литеральной запятой в строке формата:

 int n = sscanf(data, "%19s %31[^, ] , %63s", word1, word2, word3); 

Включение в тестовую проводку приводит к такому же результату, как и раньше. Обратите внимание, что весь код выигрывает от проверки; он может часто (по существу всегда) улучшаться даже после его работы.

 #include  #include  int main () { char str[] ="word1 word2,word3"; char* pch; printf ("Splitting string \"%s\" into tokens:\n",str); pch = strtok(str," ,"); while (pch != NULL) { printf ("%s\n",pch); pch = strtok (NULL, " ,.-"); } return 0; } 

Аннотация: Ответ разделен на три части. Первая часть отвечает на общий вопрос «правильно использовать sscanf», описывая преимущества использования sscanf, и когда предпочтительнее использовать sscanf. Вторая часть отвечает на конкретную часть вопроса. Третья часть имеет решающее значение для общей и конкретной частей вопроса и описывает как можно полнее, насколько я могла, внутреннюю работу sscanf.

часть 1 Преимущество использования sscanf: Использование sscanf – деление большой проблемы (исходная строка ввода) на меньшие задачи (выходные маркеры) сразу.

Если правила строки хорошо определены (например, правила строки в вопросе четко определены: между словом 1 и словом должно быть пробел. Должно быть запятая между словом 2 и словом 3. Пространства не являются обязательными между словами 2 и слово 3 – но возможно любое количество пробелов.), чем sscanf может дать ответ «Да / Нет» на вопрос «означает ли текущая строка чтения в правилах строки?» (не пытаясь проанализировать и понять, что напечатано во входном файле, или то, что должно было там набираться), и оно может также выдавать маркеры вывода строки; оба сразу.

Для этой цели, для разделения входной строки на токены, удобно использовать% c. Мы должны помнить, что по умолчанию sscanf пропускает пробельные символы (пробелы, табуляции и новые строки), но не в случае% c, где sscanf читает пробел и назначает его как значение соответствующей символьной переменной.

Использование strtok вместо этого является действительно более общим и гибким, но у него нет преимуществ сразу читать целую строку и использовать богатый лексический анализ (т.е.% d,% f,% c *, ^ и весь лексикон sscanf). И в случае, если правила строки хорошо определены и ответ «Да / Нет», на вопрос «действительно ли текущая строка чтения стоит в строковых правилах?», Достаточно, чтобы эти преимущества могли быть использованы.

Часть 2 отвечает на конкретный вопрос: вот строка кода sscanf, которая, кажется, работает, а ниже – объяснение строки кода. (Предполагается, что число 100 больше максимального размера строки ввода).

Вызов:

 n = sscanf(" sssfdf wret , 123 fdsgs fdgsdfg", "%100[^ ]%c%100[^,] %c %100[^\0]", s1, &ch1, s2, &ch2, s3); 

приведет к:

 s1 = ""sssfdf"; ch1=' '; s2=""wret "; ch2=','; s3=""123 fdsgs fdgsdfg"; 
  1. Прочитайте минимум 100 символов или все символы до первого места до s1. (Помните, что условие состоит в том, что между первым словом должно быть ровно одно пространство).

  2. Прочитайте следующий символ в ch1 (позже мы можем проверить, что ch1 имеет значение пробела).

  3. Прочитайте минимум 100 символов или все символы до тех пор, пока первая запятая на s2, s2 не сможет содержать пробелы, которые будут удалены позже. (Должно быть запятая между вторым словом третьего слова, с дополнительным пространством до и после запятой).

Обратите внимание, что% 100 [^]% c% 100 [^,] не содержит пробелов, потому что пробел перед первым% c вызовет символ после того, как пространство будет уничтожено до ch1, пробел до% 100 [^,] будет включать более одного пространства перед первым словом и вторым словом.

  1. Прочитайте следующий символ ch2 (позже мы можем проверить, что ch2 имеет значение запятой).

  2. Прочтите оставшуюся строку ввода на s3 (сначала прочитайте пробел до символа ограничителя строки).

Остается проверить правильность s1, s2 и s3 (и проверить значения ch1 и ch2, чтобы они были быстрыми и запятыми).

Часть 3 – внутренняя работа функции sscanf: sscanf (), начинает считывать ее строку формата за один раз. Существует 3 возможных значения этого символа, пробел, «%» или иначе.

  1. Если следующий символ не является пробелом, а не «%», то он начинает считывать входную строку 1.1. Если следующий символ во входной строке не является символом в строке формата, sscanf останавливает его работу и возвращает вызывающему количество параметров, которые он читал до сих пор. пример:

    n = sscanf (“2 22.456”, “2% f”, & FloatArg); / * n равно 0 * /

    1.2 Если следующий символ во входной строке является символом в строке формата, то sscanf продолжает читать следующий символ из строки формата.

    n = sscanf (“2 22.456”, “2% f”, & FloatArg); // n равно 1 FloatArg = 22.456

  2. Если следующий символ в строке формата%, чем sscanf, пропускает пробелы и ждет, чтобы прочитать строку в формате%. Например, для% f он ждет чтения и ввода в формате: [+/-] [IntDigiT1] … [IntDigiTn] <....>. примеры: 31.25, 32., 3 2.1 Если sscanf не нашел этот формат, он возвращается с количеством аргументов, которые он прочитал до сих пор. Пример:

    n = sscanf (“aaa”, “% f”, & FloatArg); // n = 0

    2.2 Если sscanf читает хотя бы одну цифру или цифру, за которой следует символ «.», Чем когда он встречает недигит, он затем приходит к выводу, что он достиг конца поплавка. sscanf () помещает недигит обратно на вход и присваивает значение, считанное переменной с плавающей запятой. Example1:

    n = sscanf (“2 22.456”, “2% f”, & FloatArg); // FloatArg – 22.456

    Example2:

    n = sscanf (“22.456”, “2% f”, & FloatArg); // FloatArg – 2.456

  3. Если следующий символ в строке формата представляет собой пробел, это означает пропустить все пробелы перед следующим символом ввода.

A. Чтение символов (% c): Если следующий входной символ является пробелом (например, пробелом), пространство присваивается указанной переменной.

B. Чтение строк (% s): Любой символ, отличный от пробела, допустим, поэтому scanf () пропускает пробел к первому символу без пробелов, а затем сохраняет небелые символы до тех пор, пока не ударит пробел снова. sscanf добавляет ‘\ 0’, ограничитель строки в конец назначенной строковой переменной.

C. Ответ не вписывается в формат% вариаций. [=% [*] [Ширина] [модификаторы] = тип]. Хорошее описание этой части можно найти на странице http://docs.roxen.com/(en)/pike/7.0/tutorial/strings/sscanf.xml. Обратите внимание, что% [characters] в приведенной выше ссылке используется в ответе к частному вопросу и позволяет гибкие манипуляции с строками.

D. Выше было то, что я нашел во время поиска в Интернете и тестирования в Dev-C ++ 5.11, различные строки, он не обещал быть полными, конструктивными комментариями, будет принят с благодарностью и поможет мне улучшить ответ.

Это выходит за frameworks scanf и друзей, чтобы быть абсолютно честным; в дополнение к ответам «напишите свой собственный простой парсер», вы можете инвестировать в yacc для анализа грамматика (лексер оставлен в качестве упражнения для читателя):

 line: oneword | twowords | threewords; oneword: word; twowords: word word; threewords: word word word; word: STRING; 

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