详解C语言中的 %*s 和 %.*s

C 语言中的 %*s%.*s 详解

在 C 语言中,%*s%.*sprintfscanf 函数家族(包括 sprintfsscanf 等)中用于格式化字符串的特殊格式说明符(Format Specifiers)。它们是 %s(字符串格式)的变体,通过星号 * 和点号 . 引入动态宽度或精度控制。这些说明符允许更灵活地处理字符串输出和输入,广泛应用于格式化打印、日志记录和数据解析。

本文将从原理、用法、示例和注意事项四个方面详细解释 %*s%.*s,并区分 printf(输出)和 scanf(输入)的场景。内容基于 ANSI C 标准(C89/C99/C11),适用于 GCC/Clang 等编译器。


1. 原理概述

  • 基本格式说明符 %s:用于字符串。
  • printf:打印字符串。
  • scanf:读取字符串。
  • 星号 * 的作用
  • printf:表示宽度或精度由函数参数动态提供,而不是硬编码。
  • scanf:表示忽略该字段(不存储输入)。
  • 点号 . 的作用:指定精度(precision),控制字符串的最大长度。
  • 结合 * 时,精度由参数提供。
  • 语法结构
  • %*s:星号在宽度位置。
  • %.*s:星号在精度位置(点后)。
  • 可以组合,如 %*.*s:宽度和精度都动态。

这些机制允许运行时控制格式,提高代码灵活性。例如,在循环中动态调整输出宽度。


2. %*s 的用法

printf 中的用法

  • 原理* 替换宽度字段,宽度由额外 int 参数提供。字符串右对齐(默认),宽度不足时用空格填充。
  • 格式%*s(宽度为参数,精度无限制)。
  • 示例
  #include <stdio.h>

  int main() {
      char *str = "hello";
      printf("%*s\n", 10, str);  // 输出: "     hello"(右对齐,宽度 10)
      printf("%*s\n", 3, str);   // 输出: "hello"(宽度 3 < 长度 5,完整打印)
      return 0;
  }
  • 输出:
    hello hello
  • 解释:第一个参数(10 或 3)是宽度,第二个是字符串。如果宽度 > 字符串长度,用空格填充(默认右对齐)。加 -%-*s 左对齐。

scanf 中的用法

  • 原理* 表示“抑制赋值”(suppression),读取但不存储该字段。用于跳过输入部分。
  • 格式%*s(读取一个字符串,但丢弃)。
  • 示例
  #include <stdio.h>

  int main() {
      char buf[20];
      int num;
      // 输入: "skip 123 keep"
      sscanf("skip 123 keep", "%*s %d %s", &num, buf);
      printf("num = %d, buf = %s\n", num, buf);  // 输出: num = 123, buf = keep
      return 0;
  }
  • 解释%*s 读取 “skip” 但不存储,接下来读取 123 到 num,”keep” 到 buf。

3. %.*s 的用法

printf 中的用法

  • 原理.* 指定精度由 int 参数提供,控制打印的最大字符数(不包括 null 终止符)。
  • 格式%.*s(精度为参数,宽度无限制)。
  • 示例
  #include <stdio.h>

  int main() {
      char *str = "hello";
      printf("%.*s\n", 3, str);  // 输出: "hel"(精度 3,只打印前 3 字符)
      printf("%.*s\n", 10, str); // 输出: "hello"(精度 10 > 长度 5,完整打印)
      return 0;
  }
  • 输出:
    hel hello
  • 解释:第一个参数(3 或 10)是精度,第二个是字符串。精度限制输出长度,超出部分截断。
  • 组合 %*.*s
  printf("%*.*s\n", 10, 3, "hello");  // 输出: "       hel"(宽度 10,精度 3,右对齐)

scanf 中的用法

  • 原理. 后精度指定最大读取字符数,* 表示忽略。
  • 格式%.*s(但通常用 %ns%10s,动态精度需 * 后跟参数)。
  • 注意:scanf 的精度直接用数字如 %10s,动态用 %*s 但无 . 组合标准。实际中,%.*s 在 scanf 不标准支持,通常用固定数字。
  • 示例(固定精度):
  #include <stdio.h>

  int main() {
      char buf[10];
      // 输入: "helloworld"
      sscanf("helloworld", "%9s", buf);  // 读取前 9 字符: "helloworl"
      printf("%s\n", buf);
      return 0;
  }
  • 动态精度:scanf 不直接支持 %.*s,需用 %*s 忽略或固定数字。替代:用 fgets 手动解析。

4. 示例代码与验证

以下是一个综合示例,展示 printfscanf 中的用法:

#include <stdio.h>

int main() {
    // printf 示例
    char *str = "C Language";
    printf("宽度动态: %*s\n", 15, str);   // 输出: "    C Language"
    printf("精度动态: %.*s\n", 5, str);    // 输出: "C Lan"
    printf("组合: %*.*s\n", 10, 3, str);   // 输出: "       C L"

    // scanf 示例
    char buf1[20], buf2[20];
    int num;
    // 输入字符串: "ignore 456 keep this"
    sscanf("ignore 456 keep this", "%*s %d %s %s", &num, buf1, buf2);
    printf("scanf: num=%d, buf1=%s, buf2=%s\n", num, buf1, buf2);  // 输出: num=456, buf1=keep, buf2=this

    return 0;
}
  • 预期输出
  宽度动态:     C Language
  精度动态: C Lan
  组合:        C L
  scanf: num=456, buf1=keep, buf2=this

您可以用 Dev-C++(前文教程)编译运行验证。


5. 注意事项

  1. 缓冲区溢出
  • scanf %s%.*s 无边界检查,易溢出。推荐用 %ns%19s(n=缓冲-1)。
  • 示例:char buf[10]; scanf(“%9s”, buf); 避免溢出。
  1. null 终止符
  • printf %.*s 不添加 null,只打印指定字符。
  • scanf 总是添加 null 终止符,确保缓冲足够。
  1. 宽度和精度组合
  • 宽度 > 精度:用空格填充。
  • 精度 > 字符串长:完整打印。
  1. scanf 忽略
  • %*s 读取到空格/换行,跳过一个词。
  • 多字段:用于解析固定格式输入(如 CSV)。
  1. 兼容性
  • ANSI C 支持 %*s%.*s
  • 嵌入式系统:检查编译器(如 Keil)支持。
  • Python format 类似:”{:10s}”.format(str) 或 “{:.3s}”.format(str)。
  1. 性能
  • 动态格式适合变量场景,但固定格式更快。

总结

  • %*s:在 printf 中动态宽度;在 scanf 中忽略字段。
  • %.*s:在 printf 中动态精度;在 scanf 中限制长度(固定数字更常见)。
  • 应用:增强格式灵活性,适合日志、解析。
  • 实践:用 Dev-C++ 测试示例,结合 Wireshark 抓包(如网络日志)验证字符串格式。

如果需要更多示例(如 scanf 动态精度替代)、结合 DHCP 的网络配置或 Python 模拟,请提供细节,我可扩展!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注