有没有可能用宏代换数字9
,以便在这行代码中拥有更好的代码可维护性?
scanf("%9[^\n]s", str);
我试图阅读文档,但我找不到这些操作的确切名称:
-
"[^\n]s"
个 -
"%ns"
个
我try 了这些替代方法,但Clion将第一次出现str
标记为两行中的错误:
scanf("%" str(MAX_LENGTH) "%[^\n]s", str);
scanf("%" str(MAX_LENGTH) "[^\n]%*c", str);
有没有可能用宏代换数字9
,以便在这行代码中拥有更好的代码可维护性?
scanf("%9[^\n]s", str);
我试图阅读文档,但我找不到这些操作的确切名称:
"[^\n]s"
个
"%ns"
个
我try 了这些替代方法,但Clion将第一次出现str
标记为两行中的错误:
scanf("%" str(MAX_LENGTH) "%[^\n]s", str);
scanf("%" str(MAX_LENGTH) "[^\n]%*c", str);
出现错误的原因是str
既不是内置函数,也不是预定义的宏.您可以将str
定义为宏,并使用字符串化运算符#
执行替换,但这很复杂且令人困惑:必须将str
定义为调用另一个宏xstr
的宏,而后者又用#x
将其参数串化:
#define xstr(x) #x
#define str(x) xstr(x)
但是请注意,您的两个示例都有问题:
scanf("%" str(MAX_LENGTH) "%[^\n]s", str);
在格式的末尾有一个额外的s
,这是无用的,表明%[...]
转换和%s
之间存在混淆,这两个转换都需要字符计数前缀以防止缓冲区溢出.第二个%
也是不正确的.此外,不应该对宏和目标数组使用相同的标识符str
:虽然这不是错误,但它会使代码变得不必要地混乱.代码应该写成:
char buf[MAX_LENGTH + 1];
scanf("%" str(MAX_LENGTH) "[^\n]", buf);
scanf("%" str(MAX_LENGTH) "[^\n]%*c", str);
具有正确的格式,但将无条件地使用匹配后的下一个字节,如果该行在换行符之前有超过MAX_LENGTH
个字节,则该字节不是换行符.不会向调用方返回任何有关此问题的指示.
%9[^\n]
在空的输入行上将失败,因为没有与转换规范匹配的字符.scanf()
将返回0
,并使目标数组处于未确定状态.
下面是一个简短的例子:
#include <stdio.h>
#define MAX_LENGTH 9
#define xstr(x) #x
#define str(x) xstr(x)
int main(void) {
char buf[MAX_LENGTH + 1];
if (scanf("%" str(MAX_LENGTH) "[^\n]", buf) == 1) {
printf("got |%s|\n", buf);
} else {
printf("invalid input\n");
}
return 0;
}
如果将str
定义为#define str(x) #x
,则调用str(MAX_LENGTH)
将扩展为"MAX_LENGTH"
.第二个宏调用在首先展开初始宏变元之后执行其替换,因此str(MAX_LENGTH)
扩展到xstr(9)
,其扩展到"9"
.
还要注意,MAX_LENGTH
不是目标数组的长度:您必须为空终止符添加额外的字符,并且在宏调用中没有一致性判断:MAX_LENGTH
和buf
的定义之间的一致性完全取决于程序员.
此外,如果MAX_LENGTH
的定义不是不带后缀的整数常量,则此宏扩展技巧将无法生成正确的scanf
转换说明符.
更可靠的方法是使用snprintf
构造scanf()
格式的字符串:
#include <stdio.h>
#define MAX_LENGTH 9
int main(void) {
char buf[MAX_LENGTH + 1];
char format[20];
snprintf(format, sizeof format, "%%%zu[^\n]", sizeof(buf) - 1);
if (scanf(format, buf) == 1) {
printf("got |%s|\n", buf);
} else {
printf("invalid input\n");
}
return 0;
}
这个版本工作得更好,但也有自己的缺点:它阻止编译器判断格式字符串和其余scanf()
个参数之间的一致性,这将导致建议的警告级别(-Wall -Wextra
)的警告,这种一致性判断非常有用,而构造格式字符串的格式字符串很容易出错.
归根结底,这两种方法都很麻烦,而且容易出错.根据您的目的使用fgets()
并手动删除尾随换行符要可靠得多:
#include <stdio.h>
#include <string.h>
#define MAX_LENGTH 9
int main(void) {
char buf[MAX_LENGTH + 2];
if (fgets(buf, sizeof buf, stdin)) {
buf[strcspn(buf, "\n")] = '\0';
printf("got |%s|\n", buf);
} else {
printf("no input\n");
}
return 0;
}
行为略有不同:fgets
将使用换行符,除非该行太长,这会使错误恢复变得更加困难.
总体来说,更好的解决方案似乎是使用自定义函数:
#include <stdio.h>
#define MAX_LENGTH 9
/* read a line from a stream and truncate excess characters */
int get_line(char *dest, int size, FILE *fp) {
int c;
int i = 0;
while ((c = getc(fp)) != EOF && c != '\n') {
if (i + 1 < size)
dest[i] = c;
i++;
}
if (i < size) {
dest[i] = '\0';
} else
if (size > 0) {
dest[size - 1] = '\0';
}
return (i == 0 && c == EOF) ? -1 : i;
}
int main(void) {
char buf[MAX_LENGTH + 1];
if (get_line(buf, sizeof buf, stdin) == EOF) {
printf("invalid input\n");
} else {
printf("got |%s|\n", buf);
}
return 0;
}
请注意,该行为仍与最初的scanf()
呼叫略有不同,但可能更接近您的目标:
get_line
读取整行,包括换行符,多余的字符将被丢弃.size
不是0,则get_line
始终将C字符串存储到目标数组中,即使在buf
为空字符串的文件末尾也是如此.scanf()
将在文件末尾返回EOF
,并保持buf
不变.get_line
将接受空行,而scanf()
将失败,返回0
并使buf
处于未确定的状态,这可能是您没有意识到的限制.总结:scanf()
充满了怪癖.try 使用显式字符计数来避免缓冲区溢出是个好主意,但如果字符计数为scanf()
,则会导致其他不易处理的问题.为了获得准确和一致的语义,通常需要编写定制代码.