C语言入门(字符串格式化)

C语言入门(字符串格式化) 目录一、sprintf把数据 “格式化写入” 字符串一函数原型与参数说明二基础使用场景示例 1数值转字符串基础示例 2字符串拼接替代 strcat示例 3进制转换实用技巧二、sscanf从字符串 “格式化读取” 数据一函数原型与参数说明二基础使用场景示例 1提取数值基础示例 2复杂格式解析实战示例 3提取指定长度字符串避坑三、核心避坑指南必看一缓冲区溢出最常见问题解决方案使用snprintf替代sprintf二格式化字符串不匹配三sscanf 的 “贪婪匹配” 问题四空指针与无效地址四、实战场景配置文件解析五、sprintf/sscanf vs 其他方法总结在 C 语言编程中字符串与数值类型的转换、复杂格式字符串的解析是高频场景。sprintf和sscanf作为printf/scanf的字符串版本为我们提供了灵活的格式化输入输出能力 ——sprintf能将各种数据类型按指定格式写入字符串sscanf能从字符串中解析出指定格式的数据。本文将从基础用法、进阶技巧、避坑指南到实战场景全面讲解这两个函数的使用。一、sprintf把数据 “格式化写入” 字符串sprintf的核心作用是将数值、字符等数据按自定义格式拼接成字符串替代繁琐的手动字符串拼接操作。一函数原型与参数说明#include stdio.h int sprintf(char *str, const char *format, ...);参数作用str目标字符串缓冲区用于存储格式化后的结果format格式化字符串与printf完全一致如%d整数、%.2f保留 2 位小数...可变参数列表对应格式化字符串中待替换的变量返回值成功返回写入的字符数不含终止符\0失败返回负数二基础使用场景示例 1数值转字符串基础将整数、浮点数按指定格式写入字符串替代手动拼接#include stdio.h int main() { char buffer[50]; // 目标缓冲区 int num 10; float pi 3.1415926; // 格式化写入整数保留2位小数的浮点数 int len sprintf(buffer, 整数: %d, 圆周率: %.2f, num, pi); printf(格式化结果%s\n, buffer); // 输出整数: 10, 圆周率: 3.14 printf(写入字符数%d\n, len); // 输出22不含\0 return 0; }示例 2字符串拼接替代 strcat传统strcat只能简单拼接sprintf可自定义拼接格式更灵活#include stdio.h int main() { char str1[20] Hello; char str2[20] World; char result[50]; // 确保缓冲区足够大 // 自定义拼接格式添加逗号、感叹号 sprintf(result, %s, %s! 今天是个好天气, str1, str2); printf(%s\n, result); // 输出Hello, World! 今天是个好天气 return 0; }示例 3进制转换实用技巧利用格式化符实现十进制转十六进制、八进制无需手动计算#include stdio.h int main() { char hex_buf[20], oct_buf[20]; int num 255; // 十进制转十六进制大写 sprintf(hex_buf, 0x%X, num); // 十进制转八进制 sprintf(oct_buf, 0%o, num); printf(十进制255 → 十六进制%s\n, hex_buf); // 输出0xFF printf(十进制255 → 八进制%s\n, oct_buf); // 输出0377 return 0; }二、sscanf从字符串 “格式化读取” 数据sscanf是scanf的字符串版本核心作用是从指定字符串中解析出符合格式要求的数据常用于日志解析、协议解析、配置文件读取等场景。一函数原型与参数说明#include stdio.h int sscanf(const char *str, const char *format, ...);参数作用str源字符串待解析的原始数据format格式化字符串与scanf一致指定要提取的数据格式...可变参数列表必须传地址用于存储解析出的数据返回值成功解析的参数个数失败返回 - 1EOF二基础使用场景示例 1提取数值基础从混合字符串中提取整数、浮点数#include stdio.h int main() { char buffer[50] 学号:202401 成绩:98.5; int id; float score; // 按格式提取跳过学号:提取整数跳过成绩:提取浮点数 int count sscanf(buffer, 学号:%d 成绩:%f, id, score); printf(解析出的参数个数%d\n, count); // 输出2 printf(学号%d成绩%.1f\n, id, score); // 输出学号202401成绩98.5 return 0; }示例 2复杂格式解析实战解析带分隔符、固定前缀的字符串如日志、配置项#include stdio.h int main() { // 模拟网络请求日志时间|IP|状态码|响应时间 char log[100] 2026-03-17 10:30:00|192.168.1.1|200|50ms; char time[20], ip[20]; int status; int response_time; // 按分隔符解析| 作为分隔符跳过ms后缀 sscanf(log, %s|%s|%d|%dms, time, ip, status, response_time); printf(时间%s\n, time); // 输出2026-03-17 printf(IP%s\n, ip); // 输出192.168.1.1 printf(状态码%d\n, status); // 输出200 printf(响应时间%dms\n, response_time); // 输出50 return 0; }示例 3提取指定长度字符串避坑使用%ns限定提取字符数避免缓冲区溢出#include stdio.h int main() { char str[100] 姓名张三 年龄28 地址北京市海淀区; char name[5]; // 仅能存4个字符终止符 // %4s最多提取4个字符避免超出name缓冲区 sscanf(str, 姓名%4s, name); printf(提取的姓名%s\n, name); // 输出张三 return 0; }三、核心避坑指南必看一缓冲区溢出最常见问题sprintf不会检查目标缓冲区大小若格式化后的字符串长度超过缓冲区会导致缓冲区溢出内存越界引发程序崩溃或安全问题。解决方案使用snprintf替代sprintfsnprintf增加了size参数限定最大写入字符数自动保留终止符\0#include stdio.h int main() { char buffer[10]; // 小缓冲区仅9个有效字符1个\0 int num 123456; // snprintf最多写入9个字符size-1避免溢出 int len snprintf(buffer, sizeof(buffer), 数字%d, num); printf(缓冲区内容%s\n, buffer); // 输出数字12345 printf(实际需要长度%d\n, len); // 输出8实际需要8个字符未溢出 return 0; }注意snprintf返回值是 “格式化后的总字符数不含 \0”而非实际写入的字符数。若返回值≥size说明缓冲区不足数据被截断。二格式化字符串不匹配格式化符与变量类型 / 数量不匹配会导致解析 / 写入错误甚至程序崩溃// 错误示例1格式符与类型不匹配 char buf[20]; float f 3.14; sprintf(buf, %d, f); // %d整数匹配float未定义行为 // 错误示例2参数数量不匹配 char info[50] name:Tom age:20; char name[20]; sscanf(info, name:%s age:%d, name); // 少传age内存越界三sscanf 的 “贪婪匹配” 问题%s会匹配到空格 / 制表符 / 换行符为止若目标字符串含空格需用%[]精准匹配#include stdio.h int main() { char str[100] 姓名张三 李四 年龄28; char name[20]; int age; // 错误%s匹配到空格停止只能提取张三 sscanf(str, 姓名%s 年龄%d, name, age); printf(错误提取%s\n, name); // 输出张三 // 正确%[^ ] 匹配非空格字符直到空格为止 sscanf(str, 姓名%[^ ] 年龄%d, name, age); printf(正确提取%s\n, name); // 输出张三 李四注实际仍只提取张三需用更精准的格式 // 精准匹配%[^年] 匹配到年为止 sscanf(str, 姓名%[^年]龄%d, name, age); printf(精准提取%s年龄%d\n, name, age); // 输出张三 李四 年龄28 return 0; }%[]是sscanf的高级用法%[0-9]匹配数字、%[^a-z]匹配非小写字母、%[^\n]匹配整行不含换行符。四空指针与无效地址传递NULL或无效地址给sprintf/sscanf的缓冲区参数会直接导致程序崩溃// 错误示例空指针 sprintf(NULL, test); // 直接崩溃 sscanf(123, %d, NULL); // 直接崩溃四、实战场景配置文件解析结合sprintf和sscanf实现简单的配置文件读取与写入体现其实际价值#include stdio.h #include string.h // 配置项结构体 typedef struct { char ip[20]; int port; int timeout; } Config; // 写入配置文件 void writeConfig(const char *filename, Config *cfg) { FILE *fp fopen(filename, w); if (!fp) { perror(打开文件失败); return; } char line[100]; // 格式化写入配置项 sprintf(line, ip%s\nport%d\ntimeout%d, cfg-ip, cfg-port, cfg-timeout); fputs(line, fp); fclose(fp); } // 读取配置文件 void readConfig(const char *filename, Config *cfg) { FILE *fp fopen(filename, r); if (!fp) { perror(打开文件失败); return; } char line[100]; // 逐行解析配置项 while (fgets(line, sizeof(line), fp)) { if (strstr(line, ip)) { sscanf(line, ip%s, cfg-ip); } else if (strstr(line, port)) { sscanf(line, port%d, cfg-port); } else if (strstr(line, timeout)) { sscanf(line, timeout%d, cfg-timeout); } } fclose(fp); } int main() { Config cfg {127.0.0.1, 8080, 30}; // 写入配置 writeConfig(app.cfg, cfg); printf(配置已写入app.cfg\n); // 读取配置 Config cfg_read {0}; readConfig(app.cfg, cfg_read); printf(读取配置\n); printf(IP%s\n, cfg_read.ip); // 输出127.0.0.1 printf(端口%d\n, cfg_read.port); // 输出8080 printf(超时%d\n, cfg_read.timeout); // 输出30 return 0; }五、sprintf/sscanf vs 其他方法方法优势劣势适用场景sprintf/sscanf格式灵活、代码简洁、功能强大存在缓冲区溢出风险、性能一般复杂格式转换、配置解析手动拼接 / 解析性能高、可控性强代码繁琐、易出错简单格式、高性能场景strtol/atof专门处理数值转换、更安全仅支持基础数值转换纯数值字符串转数值总结sprintf用于将数据按格式写入字符串sscanf用于从字符串按格式解析数据二者是 C 语言处理格式化字符串的核心工具核心避坑点用snprintf替代sprintf防止缓冲区溢出确保格式化符与变量类型 / 数量匹配利用%[]解决sscanf贪婪匹配问题实战价值常用于日志解析、配置文件读写、协议解析等场景能大幅简化字符串与数据类型的转换逻辑。熟练掌握这两个函数能让你在处理 C 语言字符串格式化问题时事半功倍既保证代码的可读性又提升开发效率。