Правильное использование sscanf - PullRequest
4 голосов
/ 02 марта 2012

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

  • Между словом 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.

Ответы [ 4 ]

23 голосов
/ 02 марта 2012
int n = sscanf("string", "%s %[^, ]%*[, ]%s", word1, word2, word3);

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

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


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

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

Разница в том, что класс не присваивающих символов принимает только запятую.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 предлагает динамически форматировать строку формата в их (превосходной) книге 'Практика программирования' или Amazon Практика программирования 1999.)


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

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

#include <stdio.h>

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: <<word1 word2 , word3>>
n = 3; w1 = <<word1>>, w2 = <<word2>>, w3 = <<word3>>
Test data: <<word1 word2 ,word3>>
n = 3; w1 = <<word1>>, w2 = <<word2>>, w3 = <<word3>>
Test data: <<word1 word2, word3>>
n = 3; w1 = <<word1>>, w2 = <<word2>>, w3 = <<word3>>
Test data: <<word1 word2,word3>>
n = 3; w1 = <<word1>>, w2 = <<word2>>, w3 = <<word3>>
Test data: <<word1 word2       ,       word3>>
n = 3; w1 = <<word1>>, w2 = <<word2>>, w3 = <<word3>>

После того, как «класс неназванного символа» принимает только запятую, вы можете сократить его до буквальной запятой встрока формата:

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

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

4 голосов
/ 02 марта 2012
#include <stdio.h>
#include <string.h>

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;
}
3 голосов
/ 03 июля 2016

Аннотация: Ответ разделен на три части.Первая часть отвечает на общий вопрос о «правильном использовании 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 [^,] включит более одного пробела перед первым словом и вторым словом.

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

Считать оставшуюся часть входной строки в 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 () помещает нецифровое число обратно во входные данные и присваивает считываемое значение переменной с плавающей запятой.Пример 1:

    n = sscanf ("2 22,456", "2% f", & FloatArg);// FloatArg - 22,456

    Пример 2:

    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 Обратите внимание, что% [символы] в ссылке выше, используется в ответе на частный вопрос, и позволяет гибко манипулировать строками.

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

0 голосов
/ 02 марта 2012

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...