我可以用什么来代替scanf进行输入转换?

我可以用什么来代替scanf进行输入转换?,c,scanf,C,Scanf,我经常看到人们劝阻其他人使用scanf,并说有更好的选择。然而,我最后看到的不是“不要使用scanf”就是“这里有一个正确的格式字符串”,而且从来没有提到过任何“更好的替代方案”的例子 例如,让我们以这段代码为例: scanf("%c", &c); 这将读取上次转换后输入流中留下的空白。通常建议的解决方案是使用: scanf(" %c", &c); 或者不使用scanf 既然scanf不好,那么在转换scanf通常不使用scanf就能处理的输入格式(如整数、浮点数和字符串)时

我经常看到人们劝阻其他人使用scanf,并说有更好的选择。然而,我最后看到的不是“不要使用
scanf
”就是“这里有一个正确的格式字符串”,而且从来没有提到过任何“更好的替代方案”的例子

例如,让我们以这段代码为例:

scanf("%c", &c);
这将读取上次转换后输入流中留下的空白。通常建议的解决方案是使用:

scanf(" %c", &c);
或者不使用
scanf


既然
scanf
不好,那么在转换
scanf
通常不使用
scanf
就能处理的输入格式(如整数、浮点数和字符串)时,有哪些ANSI C选项?

读取输入的最常见方法是:

  • 通常建议使用固定大小的
    fgets
    ,以及

  • 使用
    fgetc
    ,如果您只读取单个
    char
    ,这可能很有用

要转换输入,可以使用多种功能:

  • strtoll
    ,将字符串转换为整数

  • strof
    /
    d
    /
    ld
    ,将字符串转换为浮点数

  • sscanf
    ,它没有简单地使用
    scanf
    那么糟糕,尽管它确实有下面提到的大多数缺点

  • 在纯ANSI C中没有解析分隔符分隔输入的好方法。可以使用POSIX中的
    strtok_r
    ,也可以使用
    strtok
    ,这不是线程安全的。您还可以使用
    strcspn
    strspn
    实现线程安全变体,因为
    strtok\u r
    不涉及任何特殊的操作系统支持

  • 这可能有些过分,但您可以使用lexer和parser(
    flex
    bison
    是最常见的示例)

  • 无需转换,只需使用字符串即可


由于我没有在问题中详细说明为什么
scanf
不好,我将详细说明:

  • 使用转换说明符
    %[…]
    %c
    scanf
    不会占用空白。这一点显然并不广为人知,这一点可以从许多复制品中得到证明

  • 在引用
    scanf
    的参数(特别是字符串)时,对于何时使用一元
    &
    运算符存在一些混淆

  • 很容易忽略
    scanf
    的返回值。这很容易导致读取未初始化的变量时出现未定义的行为

  • scanf
    中很容易忘记防止缓冲区溢出
    scanf(“%s”,str)
    的性能一样差,甚至比
    的性能差

  • 使用
    scanf
    转换整数时无法检测溢出事实上,溢出会导致这些函数中出现溢出。


当你知道你的输入总是结构良好、表现良好时,scanf就棒极了。否则

国际海事组织,以下是scanf的最大问题:

  • 缓冲区溢出的风险-如果不为
    %s
    %[
    转换说明符指定字段宽度,则存在缓冲区溢出的风险(尝试读取的输入超过缓冲区的大小)。不幸的是,没有好的方法将其指定为参数(与
    printf
    一样)-您必须将其硬编码为转换说明符的一部分,或者进行一些宏操作

  • 接受应该拒绝的输入-如果您正在使用
    %d
    转换说明符读取输入,并且键入类似
    12w4
    的内容,您可能希望
    scanf
    拒绝该输入,但它没有-它成功地转换并分配
    12
    ,将
    w4
    保留在输入流将阻塞下一次读取

那么,你应该用什么来代替呢

我通常建议使用
fgets
将所有交互式输入作为文本读取-它允许您指定一次读取的最大字符数,因此可以轻松防止缓冲区溢出:

char input[100];
if ( !fgets( input, sizeof input, stdin ) )
{
  // error reading from input stream, handle as appropriate
}
else
{
  // process input buffer
}
fgets
的一个怪癖是,如果有空间,它将在缓冲区中存储尾随的换行符,因此您可以轻松检查是否有人输入了超出预期的输入:

char *newline = strchr( input, '\n' );
if ( !newline )
{
  // input longer than we expected
}
如何处理这一点取决于您自己-您可以立即拒绝整个输入,并使用
getchar
清除任何剩余的输入:

while ( getchar() != '\n' ) 
  ; // empty loop
或者,你也可以处理到目前为止得到的输入,然后重新阅读。这取决于你试图解决的问题

要对输入进行标记化(根据一个或多个分隔符将其拆分),可以使用
strtok
,但要注意-
strtok
修改其输入(它用字符串终止符覆盖分隔符),并且不能保留其状态(也就是说,您不能部分标记一个字符串,然后开始标记另一个字符串,然后从原始字符串中的中断处开始)。有一个变体,
strtok_s
,它保留标记器的状态,但它的实现是可选的(您需要检查
\uuuuu STDC\u LIB\u EXT1\uuuu
是否已定义,以查看它是否可用)

将输入标记化后,如果需要将字符串转换为数字(即,
“1234”
=>
1234
),您可以选择。
strtol
strtod
将整数和实数的字符串表示形式转换为各自的类型。它们还允许您捕获上面提到的
12w4
问题-它们的参数之一是指向字符串中未转换的第一个字符的指针:

char *text = "12w4";
char *chk;
long val;
long tmp = strtol( text, &chk, 10 );
if ( !isspace( *chk ) && *chk != 0 )
  // input is not a valid integer string, reject the entire input
else
  val = tmp;
太长,读不下去了
fgets
用于获取输入。
sscanf
用于
#define bsize 100

void error_function(const char *buffer, int no_conversions) {
        fprintf(stderr, "An error occurred. You entered:\n%s\n", buffer);
        fprintf(stderr, "%d successful conversions", no_conversions);
        exit(EXIT_FAILURE);
}

char c, buffer[bsize];
int x,y;
float f, g;
int r;

printf("Enter two integers: ");
fflush(stdout); // Make sure that the printf is executed before reading
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) error_function(buffer, r);

// Unless the input buffer was to small we can be sure that stdin is empty
// when we come here.
printf("Enter two floats: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%f%f", &f, &g)) != 2) error_function(buffer, r);

// Reading single characters can be especially tricky if the input buffer
// is not emptied before. But since we're using fgets, we're safe.
printf("Enter a char: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%c", &c)) != 1) error_function(buffer, r);

printf("You entered %d %d %f %c\n", x, y, f, c);
int printfflush (const char *format, ...)
{
   va_list arg;
   int done;
   va_start (arg, format);
   done = vfprintf (stdout, format, arg);
   fflush(stdout);
   va_end (arg);
   return done;
}
char *convertStringToInteger(int *outValue, char *string, int minValue, int maxValue) {
    return "Code not implemented yet!";
}

int main(int argc, char *argv[]) {
    char *errorString;
    int value;

    if(argc < 2) {
        printf("ERROR: No command line argument.\n");
        return EXIT_FAILURE;
    }
    errorString = convertStringToInteger(&value, argv[1], -10, 2000);
    if(errorString != NULL) {
        printf("ERROR: %s\n", errorString);
        return EXIT_FAILURE;
    }
    printf("SUCCESS: Your number is %d\n", value);
    return EXIT_SUCCESS;
}
char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);
printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);
printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);
hello world!
fgets(line, 512, stdin);
#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);
fgets(line, sizeof(line), stdin);
printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
    printf("Well, never mind, then.\n");
    exit(1);
}
printf("you typed: \"%s\"\n", line);
you typed: "Steve
"
char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';
strtok(line, "\n");
line[strcspn(line, "\n")] = '\0';
// scanf("%d %f fred", &some_int, &some_float);
#define EXPECTED_LINE_MAX 100
char buffer[EXPECTED_LINE_MAX * 2];  // Suggest 2x, no real need to be stingy.

if (fgets(buffer, sizeof buffer, stdin)) {
  int n = 0;
  // add ------------->    " %n" 
  sscanf(buffer, "%d %f fred %n", &some_int, &some_float, &n);
  // Did scan complete, and to the end?
  if (n > 0 && buffer[n] == '\0') {
    // success, use `some_int, some_float`
  } else {
    ; // Report bad input and handle desired.
  }
/* This scanner reads a file of numbers, expecting one number per line.  It  */
/* allows for the use of European-style comma as decimal point.              */

%{
  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>
  #ifdef WINDOWS
    #include <io.h>
  #endif
  #include "Point.h"

  #define YY_NO_UNPUT
  #define YY_DECL int f_lex (double *val)

  double atofEuro (char *);
%}

%option prefix="f_"
%option nounput
%option noinput

EURONUM [-+]?[0-9]*[,]?[0-9]+([eE][+-]?[0-9]+)?
NUMBER  [-+]?[0-9]*[\.]?[0-9]+([eE][+-]?[0-9]+)?
WS      [ \t\x0d]

%%

[!@#%&*/].*\n

^{WS}*{EURONUM}{WS}*  { *val = atofEuro (yytext); return (1); }
^{WS}*{NUMBER}{WS}*   { *val = atof (yytext); return (1); }

[\n]
.


%%

/*------------------------------------------------------------------------*/

int scan_f (FILE *in, double *vals, int max)
{
  double *val;
  int npts, rc;

  f_in = in;
  val  = vals;
  npts = 0;
  while (npts < max)
  {
    rc = f_lex (val);

    if (rc == 0)
      break;
    npts++;
    val++;
  }

  return (npts);
}

/*------------------------------------------------------------------------*/

int f_wrap ()
{
  return (1);
}