Семейство функций *scanf()
возвращает количество успешно сопоставленных и назначенных «входных элементов» (то есть спецификаций преобразования):
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
В случае успеха эти функции возвращают количество успешно сопоставленных и назначенных входных элементов; оно может быть меньше предусмотренного или даже равно нулю в случае неудачного раннего сопоставления.
Теперь рассмотрим строку формата, которая содержит некоторые спецификации преобразования, чередующиеся с некоторыми литеральными символами и заканчивающуюся одним или несколькими литеральными символами:
// read the response to a CSI 14 t request
int r = sscanf(input, "\033[4;%d;%dt", &resp_y, &resp_x);
Рассмотрим четыре потенциальных входа для этого вызова sscanf:
"\033[4;100;200t"
"\033[4;100;200"
"\033[4;100;200foobar"
"\033[4;100;200tfoobar"
Насколько я понимаю, в этом случае возвращаемое значение (r
) будет установлено равным 2
во всех трёх случаях. Однако действительны только входные данные (1), тогда как входные данные (2) и (3) недействительны, а входные данные (4) содержат конечные данные.
Каков самый чистый/правильный способ отличить (1) от (2), (3) и от (4), т. е. гарантировать, что вся строка формата успешно сопоставляется со всем вводом?
Меня интересуют ответы на этот вопрос как для sscanf()
, так и для fscanf()
.
В вашем заголовке упоминается scanf
, но в вашем примере используется sscanf
. То, что представляет собой «весь ввод», для них различается, в том числе, хотите ли вы для scanf
считать весь ввод всем потоком до конца файла или только одной целой строкой, введенной пользователем, или чем-то еще. Для sscanf
вы можете использовать %n
в конце, чтобы получить количество прочитанных символов и сравнить его с длиной строки. Для scanf
вы можете использовать getchar
позже, чтобы увидеть, является ли следующий символ новой строкой или возвращается ли EOF
, по желанию, и, при желании, использовать ungetc
, чтобы вернуть символ обратно.
Итак, отредактируйте вопрос, чтобы уточнить, запрашиваете ли вы sscanf
из строки или scanf
из потока и что вы считаете «полным вводом».
@EricPostpischil Второе слово вопроса указывает на то, что они спрашивают о семействе scanf, а не об одной конкретной функции.
@Barmar: (a) Семья *scanf
обсуждается в качестве справочной/вводной информации. Здесь не говорится, что они спрашивают обо всей семье. (б) Для sscanf
и scanf
проблема остается иной. Вопрос следует сузить. В самом списке ОП необходимо уточнить, хотят ли они спросить об одном, о другом или вообще об обоих.
Как упомянул @Someprogrammerdude, *scanf
— неправильный инструмент. Но, возможно, вы можете просто заменить t
в конце строки формата на %c
, а затем проверить возвращаемое значение и убедиться, что оно скопировало t
в цель. (Тогда окончание строки 200toobar
также будет совпадать, поэтому вам нужно добавить больше логики, и в конце концов вы все равно перестанете использовать scanf
.)
@EricPostpischil Хороший улов. Мой непосредственный вопрос касался sscanf(), но мне также был бы интересен ответ на аналогичный вопрос для fscanf().
«Самый чистый/правильный способ отличить» такие входные данные — избегать семейства функций scanf
.
Вместо этого напишите собственный синтаксический анализатор, реализующий все необходимые проверки.
Использовать:
int n = 0;
int r = sscanf(input, "\033[4;%d;%dt%n", &resp_y, &resp_x, &n);
if (r != 2 || n == 0)
{
…scanning failed…
}
Обратите внимание, что присвоение n
не считается «конверсией», поэтому оно не учитывается в возвращаемом значении.
Если вы хотите убедиться, что с sscanf()
использовалась вся строка, вы можете проверить:
if (r == 2 && n != 0 && input[n] == '\0')
{
…whole string was scanned…
}
Этот метод с использованием %n
можно использовать с любой функцией семейства scanf() . Хотя я дал ссылку на документацию POSIX, стандарт C говорит то же самое. Если вы читаете из файла (fscanf()
или scanf()
, а не sscanf()
), вы, вероятно, не захотите проверять значение n
больше, чем показано в первом фрагменте. Если вы хотите проверить наличие новой строки (или другого пробела), вы не сможете сделать это так легко с помощью scanf()
— см. Каков эффект конечных пробелов в строке формата scanf()?.
Разве так не должно быть n != strlen(input)
?
Спасибо, я лишь смутно знал о %n
и не думал использовать его таким образом. Однако меня также интересует часть вопроса «весь ввод».
@Barmar: это зависит от того, чего ты хочешь. Если t
отсутствует, %n
не будет обрабатываться, поэтому для него останется 0. Если вы не хотите, чтобы с sscanf()
и др. ничего не оставалось, то n != strlen(input)
проверит, что все символы в строке обработаны. Конечно, вы также можете использовать if (input[n] != '\0')
. С файловыми функциями scanf()
вы, вероятно, не захотите выполнять такую проверку длины; после сканирования в файле могло быть больше данных.
Это то, чего ОП говорит, что они хотят. Предполагается, что пример №3 не удался, потому что в конце входных данных стоит foobar
.
Ах, кажется, я неправильно понял, потому что не увидел t
в их строке формата.
@Barmar Тоже хороший улов, меня также интересует обнаружение конечных данных (согласно заголовку). Я отредактировал вопрос и этот ответ, чтобы добавить свое понимание эквивалента fscanf().
@intelfx: Я откатил ваши изменения, потому что они отменили мои изменения, направленные на те же проблемы. Я не планирую снова редактировать какое-то время, поэтому, если вы все еще хотите включить некоторую часть своего редактирования в мой текущий ответ, вы можете сделать это безопасно.
@JonathanLeffler Не могли бы вы уточнить, что именно вы подразумеваете под «проверкой конечных пробелов»?
@intelfx: см. связанный вопрос (о концевых пробелах). Помещать пробел, табуляцию или новую строку в конце scanf()
— в отличие от sscanf()
— строки формата — плохая идея (особенно, если ввод поступает от пользователя, печатающего на терминале). С sscanf()
это менее проблематично, но все равно не то, что обычно хочется. Семейство функций scanf()
сложно использовать правильно — есть некоторая мудрость избегать их, как предлагается в комментариях к вопросу. См. также Руководство для начинающих по использованию scanf().
@JonathanLeffler Я прочитал связанный вопрос, прежде чем задать. Вопрос остается в силе. Понятно, что если мы говорим о «всем вводе», то мы не имеем дело с интерактивным вводом.
sscanf
(и его братья и сестры) — очень простой инструмент для анализа и проверки строк. Если вам нужна более продвинутая проверка, вам нужно создать собственный синтаксический анализатор для анализа конкретной строки.