蒙蒙plus
蒙蒙plus
Published on 2025-11-20 / 15 Visits
0
0

scanf_参考笔记

scanf 系列函数完整参考笔记

目录

  1. 函数族概览
  2. 格式说明符详解
  3. 修饰符和宽度控制
  4. 字符集匹配
  5. 实际应用示例
  6. 常见问题和注意事项

函数族概览

标准函数

函数 功能 输入源
scanf() 从标准输入读取 stdin
sscanf() 从字符串读取 const char *str
fscanf() 从文件流读取 FILE *stream
vscanf() 可变参数版本 stdin
vsscanf() 可变参数版本 const char *str
vfscanf() 可变参数版本 FILE *stream

RT-Thread 封装

函数 对应标准函数
rt_sscanf() sscanf()
rt_vsscanf() vsscanf()

函数原型

int scanf(const char *format, ...);
int sscanf(const char *str, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap);

返回值:成功匹配并赋值的输入项数量,失败返回 EOF 或小于期望值的数字。


格式说明符详解

基本格式:%[修饰符][宽度][长度]类型

类型说明符

整数类型

说明符 类型 示例输入 说明
%d int "123" 十进制整数(可带正负号)
%i int "123", "0xFF", "0755" 自动识别进制(十进制/八进制/十六进制)
%u unsigned int "123" 无符号十进制整数
%o unsigned int "0755" 八进制整数(可带或不带前导0)
%x / %X unsigned int "FF", "0xFF" 十六进制整数(大小写不敏感)

示例

int a, b;
unsigned int c;
sscanf("123 -456 0xFF", "%d %d %x", &a, &b, &c);
// a = 123, b = -456, c = 0xFF

浮点类型

说明符 类型 示例输入 说明
%f float "123.45" 浮点数
%lf double "123.45" 双精度浮点数
%e / %E float "1.23e2" 科学计数法
%g / %G float "123.45" 自动选择最短格式

示例

float f;
double d;
sscanf("123.45 6.78e2", "%f %lf", &f, &d);
// f = 123.45, d = 678.0

字符和字符串

说明符 类型 示例输入 说明
%c char "A" 单个字符(不跳过空白)
%s char[] "Hello" 字符串(遇到空白停止)
%[字符集] char[] 见下方 字符集匹配

示例

char c, str[20];
sscanf("A Hello", "%c %s", &c, str);
// c = 'A', str = "Hello"

指针类型

说明符 类型 示例输入 说明
%p void* "0x12345678" 指针地址

示例

void *ptr;
sscanf("0x12345678", "%p", &ptr);
// ptr = (void*)0x12345678

特殊说明符

说明符 功能 说明
%n 记录已读取字符数 不消耗输入,将已读取字符数写入 int*
%% 匹配字面量 % 匹配一个 % 字符

示例

int pos, value;
sscanf("12345", "%d%n", &value, &pos);
// value = 12345, pos = 5(已读取5个字符)

修饰符和宽度控制

宽度修饰符

格式 说明 示例
%5d 最多读取5个字符 "12345" → 读取 12345
%10s 最多读取10个字符 "HelloWorld" → 读取 HelloWorld
%3c 读取3个字符 "ABC" → 读取 ABC

注意:宽度限制可以防止缓冲区溢出。

示例

char buf[10];
sscanf("1234567890123", "%9s", buf);  // 安全:最多读取9个字符+'\0'
// buf = "123456789"

长度修饰符

修饰符 适用类型 说明
hh %hhd, %hhu char (signed/unsigned)
h %hd, %hu short (signed/unsigned)
l %ld, %lu, %lx long (signed/unsigned)
ll %lld, %llu long long (signed/unsigned)
L %Lf, %Le long double
z %zd, %zu size_t
t %td, %tu ptrdiff_t

示例

short s;
long l;
long long ll;
sscanf("123 456789 987654321", "%hd %ld %lld", &s, &l, &ll);

赋值抑制符 *

%* 表示读取但不赋值(跳过该字段)。

示例

int second;
sscanf("123 456 789", "%*d %d %*d", &second);
// 跳过第一个和第三个数字,只读取第二个
// second = 456

实际应用(跳过前缀):

// 从 "+CWLAP:(3,"HONOR 20",-51,...)" 中提取数据
sscanf(line, "%*[^(](%d,\"%[^\"]\",%d,...)", &ecn, ssid, &rssi, ...);
// %*[^(] 跳过到 '(' 之前的所有字符

字符集匹配

字符集语法:%[字符集]

基本用法

格式 说明 示例
%[abc] 匹配 a、b、c 中的任意字符 "abc123""abc"
%[a-z] 匹配小写字母 "hello123""hello"
%[A-Z] 匹配大写字母 "HELLO123""HELLO"
%[0-9] 匹配数字 "123abc""123"
%[a-zA-Z0-9] 匹配字母数字 "Hello123!""Hello123"

取反字符集:%[^字符集]

匹配不在字符集中的字符。

格式 说明 示例
%[^,] 匹配到逗号之前的所有字符 "Hello,World""Hello"
%[^\"] 匹配到引号之前的所有字符 "Hello\"World""Hello"
%[^\n] 匹配到换行符之前的所有字符 "Line1\nLine2""Line1"
%[^0-9] 匹配非数字字符 "abc123""abc"

特殊字符转义

在字符集中,某些字符需要特殊处理:

字符 处理方式 示例
] 必须放在第一个位置 %[]abc]%[^]abc]
- 放在开头或结尾,或转义 %[-abc]%[abc-]
^ 放在非第一个位置表示字面量 %[a^bc]

实际应用示例(解析 AT 响应):

// 解析: +CIPDNS:0,"208.67.222.222","8.8.8.8"
char dns1[32], dns2[32];
sscanf(line, "%*[^\"]\"%[^\"]\",\"%[^\"]\"", dns1, dns2);
// %*[^\"] 跳过到第一个引号
// \" 匹配引号
// %[^\"] 读取引号内的内容到 dns1
// \" 匹配引号
// ,\" 匹配逗号和引号
// %[^\"] 读取引号内的内容到 dns2
// 解析: +CWLAP:(3,"HONOR 20",-51,"22:a6:f3:40:da:3d",1)
int ecn, rssi, channel;
char ssid[32], mac[32];
sscanf(line, "%*[^(](%d,\"%[^\"]\",%d,\"%[^\"]\",%d)",
       &ecn, ssid, &rssi, mac, &channel);
// %*[^(] 跳过到 '(' 之前的所有字符
// ( 匹配左括号
// %d 读取加密方式
// ,\" 匹配逗号和引号
// %[^\"] 读取 SSID
// \" 匹配引号
// ,%d 读取 RSSI(可能是负数)
// ,\" 匹配逗号和引号
// %[^\"] 读取 MAC 地址
// \" 匹配引号
// ,%d 读取频道
// ) 匹配右括号

实际应用示例

示例1:解析 IP 地址和 MAC 地址

// 输入: "IP:192.168.1.100 MAC:00:11:22:33:44:55"
char ip_str[16];
unsigned char mac[6];
sscanf(input, "IP:%15[0-9.] MAC:%x:%x:%x:%x:%x:%x",
       ip_str,
       &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);

示例2:解析带引号的字符串

// 输入: Name="John Doe" Age=30
char name[32];
int age;
sscanf(input, "Name=\"%[^\"]\" Age=%d", name, &age);
// name = "John Doe", age = 30

示例3:跳过不需要的字段

// 输入: "Date:2024-01-15 Time:14:30:00"
int year, month, day, hour, min, sec;
sscanf(input, "Date:%d-%d-%d Time:%d:%d:%d",
       &year, &month, &day, &hour, &min, &sec);

示例4:解析可变格式的数值

// 输入可能是: "123" 或 "0xFF" 或 "0755"
int value;
sscanf(input, "%i", &value);  // %i 自动识别进制

示例5:解析固定宽度字段

// 输入: "20240115" (YYYYMMDD)
int year, month, day;
sscanf(input, "%4d%2d%2d", &year, &month, &day);
// year = 2024, month = 1, day = 15

示例6:解析带空格的字符串(使用字符集)

// 输入: "Hello World 123"
char text[32];
int num;
sscanf(input, "%[^0-9] %d", text, &num);
// text = "Hello World ", num = 123
// 注意:text 末尾可能有空格

示例7:记录解析位置

// 输入: "123 456 789"
int a, b, pos1, pos2;
sscanf(input, "%d%n %d%n", &a, &pos1, &b, &pos2);
// a = 123, pos1 = 3 (第一个数字结束位置)
// b = 456, pos2 = 7 (第二个数字结束位置)

示例8:解析浮点数数组

// 输入: "1.5,2.3,3.7,4.9"
float values[4];
int count = 0;
const char *p = input;
for (int i = 0; i < 4; i++) {
    int n;
    if (sscanf(p, "%f%n", &values[i], &n) == 1) {
        count++;
        p += n;
        if (*p == ',') p++;  // 跳过逗号
    } else {
        break;
    }
}

常见问题和注意事项

1. 缓冲区溢出

错误示例

char buf[10];
sscanf("This is a very long string", "%s", buf);  // 危险!

正确做法

char buf[10];
sscanf("This is a very long string", "%9s", buf);  // 安全:限制宽度

2. 字符串读取遇到空格停止

%s 会在遇到空白字符(空格、制表符、换行等)时停止。

char str[32];
sscanf("Hello World", "%s", str);
// str = "Hello"(不是 "Hello World")

解决方案:使用字符集匹配

sscanf("Hello World", "%[^\n]", str);  // 读取到换行符
// 或
sscanf("Hello World", "%31[^\n]", str);  // 带宽度限制

3. 字符读取不跳过空白

%c 会读取任何字符,包括空白字符。

char c1, c2;
sscanf("A B", "%c %c", &c1, &c2);
// c1 = 'A', c2 = 'B'(空格被格式字符串中的空格跳过)

sscanf("AB", "%c%c", &c1, &c2);
// c1 = 'A', c2 = 'B'

4. 返回值检查

重要:始终检查返回值!

int a, b;
int ret = sscanf("123", "%d %d", &a, &b);
if (ret == 2) {
    // 成功读取两个值
} else if (ret == 1) {
    // 只读取了第一个值
} else {
    // 读取失败
}

5. 格式字符串中的空白字符

格式字符串中的空白字符(空格、制表符、换行)会匹配输入中的任意数量的空白字符。

int a, b;
sscanf("123   456", "%d %d", &a, &b);  // 多个空格也能匹配
sscanf("123\n456", "%d %d", &a, &b);   // 换行也能匹配

6. 字面量匹配

格式字符串中非 % 开头的字符必须与输入完全匹配

int value;
sscanf("Value:123", "Value:%d", &value);  // 成功
sscanf("value:123", "Value:%d", &value);  // 失败(大小写不匹配)

7. 十六进制和八进制前缀

  • %x / %X:可以识别 0x0X 前缀,也可以没有前缀
  • %o:可以识别 0 前缀,也可以没有前缀
  • %i:自动识别 0x(十六进制)、0(八进制)或无前缀(十进制)
unsigned int val;
sscanf("FF", "%x", &val);      // val = 0xFF
sscanf("0xFF", "%x", &val);    // val = 0xFF
sscanf("0755", "%o", &val);    // val = 0755 (八进制)
sscanf("755", "%o", &val);     // val = 0755 (八进制)

8. 负数处理

  • %d:可以读取负数
  • %u:不能读取负数(会出错或产生意外结果)
  • %x / %o:通常用于无符号数
int a;
unsigned int b;
sscanf("-123", "%d", &a);   // a = -123
sscanf("-123", "%u", &b);   // 错误:未定义行为

9. 浮点数精度

%f 读取 float%lf 读取 double

float f;
double d;
sscanf("123.45", "%f", &f);   // 正确
sscanf("123.45", "%lf", &d);  // 正确
sscanf("123.45", "%f", &d);   // 错误:可能导致精度丢失

10. 字符集匹配的边界情况

char buf[32];
// 空字符集:匹配失败
sscanf("abc", "%[]", buf);  // 返回 0

// 取反空字符集:匹配所有字符
sscanf("abc", "%[^]", buf);  // buf = "abc"

// 包含 ']' 的字符集
sscanf("a]b", "%[]abc]", buf);  // buf = "a]b"(如果 ']' 在开头)

调试技巧

1. 打印解析结果

int ret = sscanf(input, format, ...);
printf("Parsed %d items\n", ret);

2. 逐步解析

const char *p = input;
int pos;
int value;
while (sscanf(p, "%d%n", &value, &pos) == 1) {
    printf("Found: %d at position %d\n", value, (int)(p - input));
    p += pos;
    // 跳过分隔符
    while (*p == ' ' || *p == ',') p++;
}

3. 验证格式字符串

使用简单的测试用例验证格式字符串:

// 测试用例
const char *test_cases[] = {
    "+CWLAP:(3,\"SSID\",-50,\"aa:bb:cc:dd:ee:ff\",1)",
    "+CWLAP:(0,\"Test\",-80,\"11:22:33:44:55:66\",6)",
    NULL
};

for (int i = 0; test_cases[i]; i++) {
    int ecn, rssi, channel;
    char ssid[32], mac[32];
    int ret = sscanf(test_cases[i],
                     "%*[^(](%d,\"%[^\"]\",%d,\"%[^\"]\",%d)",
                     &ecn, ssid, &rssi, mac, &channel);
    if (ret == 5) {
        printf("OK: ecn=%d, ssid=%s, rssi=%d, mac=%s, channel=%d\n",
               ecn, ssid, rssi, mac, channel);
    } else {
        printf("FAIL: parsed %d items\n", ret);
    }
}

快速参考表

常用格式字符串模式

需求 格式字符串 示例输入 输出
跳过到某个字符 %*[^X] "Prefix:123" 跳过到 :
读取到某个字符 %[^X] "Hello,World" "Hello"
读取引号内容 \"%[^\"]\" "Name=\"John\"" "John"
读取括号内容 (%[^)]) "(123)" "123"
读取固定宽度 %4d "12345" 1234
跳过字段 %*d "123 456" 跳过 123
读取十六进制 %x "FF" 255
读取 MAC 地址 %x:%x:%x:%x:%x:%x "AA:BB:CC:DD:EE:FF" 6个字节

格式化字符串解析

对于响应行:+CIPDNS:0,“208.67.222.222”,“8.8.8.8”

格式化字符串:“%*[^"]"%[^"]","%[^"]"”

分解说明:

格式 含义 匹配内容
%*[^"] 跳过非引号字符(* 表示不保存) +CIPDNS:0,
" 匹配一个引号 "
%[^"] 读取到引号前的所有字符(保存到 dns_server1) 208.67.222.222
" 匹配引号 "
," 匹配逗号和引号 ,"
%[^"] 读取到引号前的所有字符(保存到 dns_server2) 8.8.8.8
" 匹配最后一个引号 "

示例匹配过程

响应行:  +CIPDNS:0,"208.67.222.222","8.8.8.8"
         ↓
格式串:  %*[^"]"  %[^"]  ","  %[^"]  "
         ↓        ↓              ↓
        跳过    提取         提取
        0,      dns_server1  dns_server2

关键点

  1. %[^"]: 表示丢弃匹配内容,[^"] 表示匹配除引号外的任意字符

  2. %[^"]:提取引号内的内容(IP 地址)

  3. vsscanf:使用可变参数列表,支持多个输出参数


最后更新:2025-01-XX
适用标准:C99/C11


Comment