在scanf()、sscanf()或fscanf()中,%[]或%[^]格式说明符不会将输入存储在以空字符结尾的字符数组中吗?

5
以下是 Beez C 指南中关于 %[] 格式说明符的内容:(链接) 它允许您指定要存储的字符集(可能是 char 数组)。当匹配到不在该集合中的字符时,转换就会停止。
以下是基于此前提产生的一些基本问题,请予以澄清:
1)这两个格式说明符获取的输入是否以字符数组或带有 \0 结尾字符的字符数组(字符串)的形式存储在参数(char* 类型)中?如果不是字符串,则如何使其以字符串的形式存储,在下面的程序中,我们想要将一系列字符作为字符串获取,并在遇到特定字符(在否定字符集中)时停止?
2)我的程序似乎表明,当否定字符 | 被匹配到时,%[^|] 格式说明符的处理会停止。但是,当它开始处理下一个格式说明符时,它会从之前停止的否定字符处重新开始吗?在我的程序中,我打算忽略 |,因此我使用了 %*c。但是我进行了测试,发现如果我使用 %c 和额外的 char 类型参数,则字符 | 确实存储在该参数中。
3)最后但对我来说至关重要的是,printf() 中传递字符数组和字符串(NULL 结尾字符数组)的 %s 格式说明符有什么区别?在我的另一个名为 character array vs string 的程序中,我传递了一个字符数组(未 NULL 结尾)作为 printf() 中的 %s 格式说明符,并且它被打印出来就像字符串一样。这有什么区别吗?
//以下是演示 %[^] 格式说明符的程序
#include<stdio.h>

int main()
{
char *ptr="fruit|apple|lemon",type[10],fruit1[10],fruit2[10];

sscanf(ptr, "%[^|]%*c%[^|]%*c%s", type,fruit1, fruit2);
printf("%s,%s,%s",type,fruit1,fruit2);
}

//character array vs string

#include<stdio.h>

int main()
{
char test[10]={'J','O','N'};
printf("%s",test);
}

输出 JON

//使用%c代替%*c

#include<stdio.h>

int main()
{
char *ptr="fruit|apple|lemon",type[10],fruit1[10],fruit2[10],char_var;

sscanf(ptr, "%[^|]%c%[^|]%*c%s", type,&char_var,fruit1, fruit2);
printf("%s,%s,%s,and the character is %c",type,fruit1,fruit2,char_var);

}

输出 水果,苹果,柠檬,字符是 |

2个回答

6
  1. It is null terminated. From sscanf():

    The conversion specifiers s and [ always store the null terminator in addition to the matched characters. The size of the destination array must be at least one greater than the specified field width.

  2. The excluded characters are unconsumed by the scan set and remain to be processed. An alternative format specifier:

    if (sscanf(ptr, "%9[^|]|%9[^|]|%9s", type,fruit1, fruit2) == 3)
    
  3. The array is actually null terminated as remaining elements will be zero initialized:

    char test[10]={'J','O','N' /*,0,0,0,0,0,0,0*/ };
    

如果它没有以null结尾,它将一直打印直到在内存中找到null字符,可能越过数组的末尾并导致未定义的行为。可以打印非null结尾的数组:

    char buf[] = { 'a', 'b', 'c' };
    printf("%.*s", 3, buf);

在另一个答案中,你能否清楚地表明Tony在第二部分的声明是错误的,他声称%*c不应该消耗被排除的字符? - Rüppell's Vulture
我直觉上同意你的看法,即应该继续处理被排除的字符,但是Tony也是一位老手,不能就这样忽略他…… - Rüppell's Vulture
@Rüppell'sVulture,它尚未被扫描集处理。 - hmjd
你的 2) 的替代格式说明符是什么意思?它只指定格式说明符最多获取9个字符。但既然我们知道它会在否定字符处停止,为什么要使用它呢? - Rüppell's Vulture
更新了我的回答,澄清了Tony的意思。 - hmjd
显示剩余5条评论

1

1) 这两种格式说明符获取的输入是否存储在参数中(类型为char*)作为字符数组或带有\0终止字符的字符数组(字符串)?如果不是字符串,如下面的程序所示,我们要如何将其存储为字符串,以便在遇到特定字符(在否定字符集中)时停止获取一系列字符作为字符串?

它们以ASCIIZ格式存储-带有NUL / '\0'终止符。

2) 我的程序似乎表明当遇到否定字符|时,%[^|]说明符的处理会停止。但是当它再次开始下一个格式说明符时,它会从先前停止的否定字符处重新开始吗?在我的程序中,我打算忽略|,因此我使用了%*c。但我测试发现,如果我使用%c和类型为char的附加参数,则确实将字符|存储在该参数中。

它不应消耗下一个字符。请向我们展示您的代码,否则就没有发生;-P。

3) 最后但对我来说至关重要的是,printf()中将字符数组作为%s格式说明符传递和字符串(空字符结尾的字符数组)有什么区别?在我的另一个程序中,标题为“字符数组与字符串”,我传递了一个字符数组(非空字符结尾)作为printf()中的%s格式说明符,并且它被打印出来就像一个字符串一样。有什么区别吗?

(编辑:以下解答上面的问题,该问题涉及到数组行为一般而言比问题中具体提出的情况更广泛,即char[10] = "abcd";这种情况是安全的)

%s必须传递一个指向ASCIIZ文本的指针...即使该文本明确在char数组中,也必须强制存在NUL终止符来定义文本内容,而不是数组长度。您必须NUL终止字符数组,否则行为未定义。有时可能会得逞 - 例如,如果有足够的空间,strncpy将NUL终止数组,并且静态数组以所有0内容开头,因此如果您仅覆盖最终字符之前的内容,则会有一个NUL,您的char [10]示例恰好具有未指定值的元素填充为NUL,但通常应负责确保某些内容确保NUL终止。


你说:“它不应该消耗下一个字符。” - Rüppell's Vulture
你的第三个回答与hmjd的回答有些冲突。他明确表示,由于初始化的本质,该字符是以空终止的。因此,在他看来不存在未定义行为的问题。你怎么看? - Rüppell's Vulture
你还应该考虑在 scanf 转换中使用最大宽度规格,以确保不会将数据写入超出提供的缓冲区末尾的位置。 - Tony Delroy
关于重新消费的问题...我说"%[^|]"不会消费'|';接下来的"%*c"会消费它。你声称你的测试没有看到这一点...我认为你编码或解释你的测试时出现了错误。 - Tony Delroy
@TonyD 不,那不是问题。因为我是这个问题的 OP,所以任何关于它的活动都应该提醒我。通常会提醒我,但有时会失败。 - Rüppell's Vulture
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接