有没有可能用宏代换数字9,以便在这行代码中拥有更好的代码可维护性?

scanf("%9[^\n]s", str);

我试图阅读文档,但我找不到这些操作的确切名称:

  1. "[^\n]s"

  2. "%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_LENGTHbuf的定义之间的一致性完全取决于程序员.

此外,如果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(),则会导致其他不易处理的问题.为了获得准确和一致的语义,通常需要编写定制代码.

C++相关问答推荐

Pure Win32 C(++)-除了替换控件的窗口程序之外,还有其他方法可以在输入时禁用按钮吗?

通过MQTT/蚊子发送大文件—限制在4MB

在32位处理器上优化53—32位模计算>

不同到达时间的轮询实现

数据包未从DPDK端口传输到内核端口

LibpCap禁用监视器模式(C、MacOS)

对于C中给定数组中的每个查询,如何正确编码以输出给定索引范围(1到N)中所有数字的总和?

用C语言计算文本文件中的整数个数

<;unistd.h>;和<;sys/unistd.h>;之间有什么区别?

如何在GET_STRING输入后对少数几个特定字符串进行C判断?

如何将另一个数组添加到集合中,特别是字符串?

将字符串数组传递给C中的函数:`str[dim1][str_size]`vs`*str[dim1]`

C标准关于外部常量的说明

如何逐位读取二进制文件?

通过char*访问指针的对象表示是未定义的行为吗?

未为同一文件中的函数执行DirectFunctionCall

在分配内存后使用指针是未定义的行为吗?

在C中交换字符串和数组的通用交换函数

'printf("%s", user_input)' 危险吗?

free后内存泄漏?