如何允许使用scanf输入空格?


129

使用以下代码:

char *name = malloc(sizeof(char) + 256); 

printf("What is your name? ");
scanf("%s", name);

printf("Hello %s. Nice to meet you.\n", name);

用户可以输入他们的名字,但是当他们输入带有空格的名字时Lucas Aardvarkscanf()后面的所有内容都会被切断Lucas。我如何留出scanf()空间


9
请注意,更惯用的是'malloc(sizeof(char)* 256 + 1)'或'malloc(256 + 1)',甚至更好(假设'name'将在本地严格使用)'char name [256 + 1 ]”。“ +1”可以充当空终止符的提示音,需要将其包括在分配中。
巴里·凯利

@Barry-我怀疑sizeof(char) + 256是错字。
克里斯·卢茨

Answers:


186

人们(尤其是初学者)从不应该使用scanf("%s")gets()没有缓冲区溢出保护的任何其他功能,除非您确定输入将始终为特定格式(甚至可能不是这样)。

请记住,比起scanf“扫描格式”代表更重要的是,与用户输入的数据相比,格式化的内容少之又少。如果您完全控制输入数据格式,但通常不适合用户输入,则是理想的选择。

使用fgets()具有缓冲区溢出保护)将您的输入输入字符串并sscanf()进行评估。由于您只需要用户输入而无需解析,因此sscanf()在这种情况下,您实际上并不需要:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Maximum name size + 1. */

#define MAX_NAME_SZ 256

int main(int argC, char *argV[]) {
    /* Allocate memory and check if okay. */

    char *name = malloc(MAX_NAME_SZ);
    if (name == NULL) {
        printf("No memory\n");
        return 1;
    }

    /* Ask user for name. */

    printf("What is your name? ");

    /* Get the name, with size limit. */

    fgets(name, MAX_NAME_SZ, stdin);

    /* Remove trailing newline, if there. */

    if ((strlen(name) > 0) && (name[strlen (name) - 1] == '\n'))
        name[strlen (name) - 1] = '\0';

    /* Say hello. */

    printf("Hello %s. Nice to meet you.\n", name);

    /* Free memory and exit. */

    free (name);
    return 0;
}

1
我不知道fgets()。然后看起来实际上更容易使用scanf()。+1
克雷登斯(Kredns)2009年

7
如果您只想从用户那里获得一条电话,那会更容易。由于可以避免缓冲区溢出,因此它也更安全。scanf家族对于将字符串转换为不同的东西确实很有用(例如,四个字符和一个int,例如带有“%c%c%c%c%d”的整数),但是即使那样,您也应该使用fgets和sscanf, scanf,以避免缓冲区溢出的可能性。
paxdiablo

4
您可以将最大缓冲区大小设置为scanf格式,而不能在运行时不构建该格式的情况下就放置运行时计算的缓冲区(不等于*对于printf,*是有效的scanf修饰符,具有另一种行为:抑制分配)。
AProgrammer

还要注意的是scanf有不确定的行为,如果数字转换溢出(N1570 7.21.6.2p10,最后一句,因为措辞不变C89),这意味着没有scanf功能,可安全地用于不可信输入的数值转换。
zwol

@JonathanKomar和以后的其他任何人阅读的文章:如果您的教授告诉您您必须scanf在作业中使用,他们这样做是错误的,您可能会告诉他们我这么说,并且如果他们想与我争论,可以从我的个人资料中轻松找到我的电子邮件地址。
zwol

124

尝试

char str[11];
scanf("%10[0-9a-zA-Z ]", str);

希望有帮助。


10
(1)显然要接受空格,您需要在字符类中放置一个空格。(2)注意10是将读取的最大字符数,因此str必须至少指向大小为11的缓冲区。(3)这里的final s不是格式指令,但是scanf会在这里尝试完全匹配它。该效果将在像1234567890s这样的条目上可见,在该条目上将消耗s,但不放置任何位置。另一个字母将不会被使用。如果您在s后面加上其他格式,则只有在要匹配s的情况下才会读取该格式。
AProgrammer

另一个潜在的问题是,在第一个或最后一个位置之外的其他位置使用-是定义的实现。通常,它用于范围,但是范围的指定取决于字符集。EBCDIC在字母范围内有漏洞,即使假设采用ASCII衍生的字符集,也很幼稚地认为所有小写字母都在az范围内……
AProgrammer,2009年

1
“%[^ \ n]”与gets()有相同的问题,缓冲区溢出。还有一个额外的收获,即\ n final不被读取;大多数格式都以跳过空格开头,但[这不是其中一种。我不了解使用scanf读取字符串的实例。
AProgrammer

1
s从输入字符串的末尾删除,因为在某些情况下它既多余又不正确(如前面的注释中所指出)。[是它自己的格式说明符,而不是它的某种变体s
paxdiablo

53

这个例子使用了反向的扫描集,所以scanf一直取值直到遇到'\ n'-换行符,所以空格也被保存了

#include <stdio.h>

int main (int argc, char const *argv[])
{
    char name[20];
    scanf("%[^\n]s",name);
    printf("%s\n", name);
    return 0;
}

1
小心缓冲区溢出。如果用户用50个字符写一个“名称”,该程序可能会崩溃。
brunoais 2013年

3
如您所知,缓冲区大小可用%20[^\n]s来防止缓冲区溢出
osvein

45分,没有人指出在s那里有明显的货运崇拜!
Antti Haapala,

22

你可以用这个

char name[20];
scanf("%20[^\n]", name);

或这个

void getText(char *message, char *variable, int size){
    printf("\n %s: ", message);
    fgets(variable, sizeof(char) * size, stdin);
    sscanf(variable, "%[^\n]", variable);
}

char name[20];
getText("Your name", name, 20);

演示


1
我没有进行测试,但是基于此页的其他答案,我相信您示例中scanf的正确缓冲区大小将是:(scanf("%19[^\n]", name);简洁答案仍为+1)
Beco博士

1
顺便提一句,sizeof(char)根据定义,它始终为 1,因此无需乘以。
paxdiablo

8

在不scanf()指定字段宽度的情况下,请勿使用它来读取字符串。您还应该检查返回值是否有错误:

#include <stdio.h>

#define NAME_MAX    80
#define NAME_MAX_S "80"

int main(void)
{
    static char name[NAME_MAX + 1]; // + 1 because of null
    if(scanf("%" NAME_MAX_S "[^\n]", name) != 1)
    {
        fputs("io error or premature end of line\n", stderr);
        return 1;
    }

    printf("Hello %s. Nice to meet you.\n", name);
}

或者,使用fgets()

#include <stdio.h>

#define NAME_MAX 80

int main(void)
{
    static char name[NAME_MAX + 2]; // + 2 because of newline and null
    if(!fgets(name, sizeof(name), stdin))
    {
        fputs("io error\n", stderr);
        return 1;
    }

    // don't print newline
    printf("Hello %.*s. Nice to meet you.\n", strlen(name) - 1, name);
}


5

getline()

仍然是POSIX的一部分。

尽管您必须照顾free内存,但它也可以解决您先前询问的缓冲区分配问题。


标准?在引用中,您引用:“ getline()和getdelim()都是GNU扩展。”
AProgrammer

1
POSIX 2008添加了getline。因此,GNU向前推进并在2.9版左右更改了glibc的标头,这给许多项目带来了麻烦。不是确定的链接,而是在这里查看:bugzilla.redhat.com/show_bug.cgi?id=493941。至于在线手册页,我抓住了第一个谷歌。
dmckee ---前主持人小猫,

3

如果有人还在看,这对我有用-读取任意长度的字符串(包括空格)。

感谢网络上的许多张贴者分享了这个简单而优雅的解决方案。如果可行,功劳归功于他们,但任何错误都是我的。

char *name;
scanf ("%m[^\n]s",&name);
printf ("%s\n",name);

2
值得注意的是,这是POSIX扩展,在ISO标准中不存在。为了完整性,您可能还应该检查errno并清理分配的内存。
paxdiablo

s在扫描集之后不属于那里
Antti Haapala

1

scanf为此,您可以使用一些技巧。实际上,您应该允许用户输入,直到用户点击Enter(\n)。这将考虑每个字符,包括空格。这是示例:

int main()
{
  char string[100], c;
  int i;
  printf("Enter the string: ");
  scanf("%s", string);
  i = strlen(string);      // length of user input till first space
  do
  {
    scanf("%c", &c);
    string[i++] = c;       // reading characters after first space (including it)
  } while (c != '\n');     // until user hits Enter
  string[i - 1] = 0;       // string terminating
return 0;
}

如何运作?当用户从标准输入中输入字符时,它们将存储在字符串变量中,直到第一个空格为止。之后,其余条目将保留在输入流中,并等待下一个scanf。接下来,我们有一个for循环,从输入流(till \n)中逐个字符地获取char 并将其附加到字符串变量的末尾,从而形成与用户从键盘输入相同的完整字符串。

希望这会对某人有所帮助!


受缓冲区溢出的影响。
paxdiablo

0

虽然您实际上不应该使用scanf()这种方法,但是因为有更好的调用,例如gets()getline(),它可以完成:

#include <stdio.h>

char* scan_line(char* buffer, int buffer_size);

char* scan_line(char* buffer, int buffer_size) {
   char* p = buffer;
   int count = 0;
   do {
       char c;
       scanf("%c", &c); // scan a single character
       // break on end of line, string terminating NUL, or end of file
       if (c == '\r' || c == '\n' || c == 0 || c == EOF) {
           *p = 0;
           break;
       }
       *p++ = c; // add the valid character into the buffer
   } while (count < buffer_size - 1);  // don't overrun the buffer
   // ensure the string is null terminated
   buffer[buffer_size - 1] = 0;
   return buffer;
}

#define MAX_SCAN_LENGTH 1024

int main()
{
   char s[MAX_SCAN_LENGTH];
   printf("Enter a string: ");
   scan_line(s, MAX_SCAN_LENGTH);
   printf("got: \"%s\"\n\n", s);
   return 0;
}

2
有一个原因为什么gets不推荐使用该标准并将其从标准中删除(stackoverflow.com/questions/30890696/why-gets-is-deprecated)。更糟糕的是,scanf因为至少后者有办法使其安全。
paxdiablo

-1
/*reading string which contains spaces*/
#include<stdio.h>
int main()
{
   char *c,*p;
   scanf("%[^\n]s",c);
   p=c;                /*since after reading then pointer points to another 
                       location iam using a second pointer to store the base 
                       address*/ 
   printf("%s",p);
   return 0;
 }

4
您能解释为什么这是正确的答案吗?请不要发布仅代码的答案。
西奥,

s在扫描集之后不属于那里
Antti Haapala
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.