C 语言中的 %*s 和 %.*s 详解
在 C 语言中,%*s 和 %.*s 是 printf 和 scanf 函数家族(包括 sprintf、sscanf 等)中用于格式化字符串的特殊格式说明符(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. 示例代码与验证
以下是一个综合示例,展示 printf 和 scanf 中的用法:
#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. 注意事项
- 缓冲区溢出:
- scanf
%s或%.*s无边界检查,易溢出。推荐用%ns如%19s(n=缓冲-1)。 - 示例:char buf[10]; scanf(“%9s”, buf); 避免溢出。
- null 终止符:
- printf
%.*s不添加 null,只打印指定字符。 - scanf 总是添加 null 终止符,确保缓冲足够。
- 宽度和精度组合:
- 宽度 > 精度:用空格填充。
- 精度 > 字符串长:完整打印。
- scanf 忽略:
%*s读取到空格/换行,跳过一个词。- 多字段:用于解析固定格式输入(如 CSV)。
- 兼容性:
- ANSI C 支持
%*s和%.*s。 - 嵌入式系统:检查编译器(如 Keil)支持。
- Python format 类似:”{:10s}”.format(str) 或 “{:.3s}”.format(str)。
- 性能:
- 动态格式适合变量场景,但固定格式更快。
总结
%*s:在 printf 中动态宽度;在 scanf 中忽略字段。%.*s:在 printf 中动态精度;在 scanf 中限制长度(固定数字更常见)。- 应用:增强格式灵活性,适合日志、解析。
- 实践:用 Dev-C++ 测试示例,结合 Wireshark 抓包(如网络日志)验证字符串格式。
如果需要更多示例(如 scanf 动态精度替代)、结合 DHCP 的网络配置或 Python 模拟,请提供细节,我可扩展!