写点什么

C 语言中的 sscanf 详解

作者:EquatorCoco
  • 2024-07-16
    福建
  • 本文字数:3735 字

    阅读完需:约 12 分钟

一、函数介绍


函数原型int sscanf(const char *str, const char *format, ...);

返 回 值:成功返回匹配成功的模式个数,失败返回 -1。


RETURN VALUE

  • These functions return the number of input items successfully matched and assigned, which can be fewer than provided for, or even zero in the event of an early matching failure.这些函数返回成功匹配和赋值的输入项的数目,这个数目可能比提供的要少,或者在早期匹配失败的情况下甚至为零。

  • The value EOF is returned if the end of input is reached before either the first successful conver‐sion or a matching failure occurs.如果在第一次成功转换或匹配失败之前到达输入结束,则返回 EOF 值。


举 例

iRet = sscanf("123ab", "%[0-9]%[a-z]", sz1, sz2); // iRet = 2, sz1 = "123", sz2 = "ab"iRet = sscanf("123ab", "%[0-9]%[A-Z]", sz1, sz2); // iRet = 1, sz1 = "123"iRet = sscanf("123ab", "%[a-z]%[a-z]", sz1, sz2); // iRet = 0iRet = sscanf("", "%[a-z]", sz1); 			 	  // iRet = -1
复制代码


二、sscanf 函数和正则表达式


以下内容摘抄自:sscanf函数和正则表达式 - km的小天地 - ITeye博客


备注:实验五有所纠正。


此文所有的实验都是基于下面的程序:

char str[10] = "!!!!!!!!!!"; // 10 个感叹号
复制代码


我们把 str 的每个字符都初始化为感叹号,当 str 的值发生变化时,使用 printf 打印 str 的值,对比先前的感叹号,这样就可以方便的观察 str 发生了怎样的变化。


下面我们做几个小实验,看看使用 sscanf 和正则表达式格式化输入后,str 有什么变化。


实验一

(void)sscanf("123456", "%s", str);			// str 的值变为 "123456\0!!!"
复制代码


这个实验很简单,把源字符串 "123456" 拷贝到 str 的前 6 个字符,并且把 str 的第 7 个字符设为 nil 字符,也就是 \0


实验二

(void)sscanf("123456", "%3s", str);			// str 的值变为 "123\0!!!!!!"
复制代码


看到没有,正则表达式的百分号后面多了一个 3,这告诉 sscanf 只拷贝 3 个字符给 str,然后把第 4 个字符设为 nil 字符。


实验三

(void)sscanf("abcABC", "%[a-z]", str);		// str 的值变为 "abc\0!!!!!!"
复制代码


从这个实验开始我们会使用正则表达式,中括号里面的 a-z 就是一个正则表达式,它可以表示从 a 到 z 的任意字符。


在继续讨论之前,我们先来看看百分号表示什么意思:% 表示选择,% 后面的是条件。比如:


  • 实验一的 "%s",s 是一个条件,表示任意字符,"%s" 的意思是:只要输入的东西是一个字符,就把它拷贝给 str;

  • 实验二的 "%3s" 又多了一个条件,只拷贝 3 个字符;

  • 实验三的 "%[a-z]" 的条件稍微严格一些,输入的东西不但是字符,还得是一个小写字母,所以实验三只拷贝了小写字母 "abc" 给 str,别忘了加上 nil 字符。


实验四

(void)sscanf("AAAaaaBBB", "%[^a-z]", str);	// str 的值变为 "AAA\0!!!!!!"
复制代码


符号 ^ 表示逻辑非,对于所有字符,只要不是小写字母,都满足 "%[^a-z]" 正则表达式。


前 3 个字符都不是小写字符,所以将其拷贝给 str,但最后 3 个字符也不是小写字母,为什么不拷贝给 str 呢?这是因为当碰到不满足条件的字符后,sscanf 就会停止执行,不再扫描之后的字符


符号 ^ 是排除的意思,可以理解为:一直拷贝,直到遇到我不想拷贝的字符为止,如可以通过如下模式过滤到行的结尾:%[^\r\n]


实验五

(void)sscanf("AAAaaaBBB", "%[A-Z]%[a-z]", &str[0], &str[5]); 	// "AAA\0!abc\0!"
复制代码


先把大写字母 ABC 拷贝到 str[0] 开始的内存,然后把小写字母 abc 拷贝给 str[5] 开始的内存。


实验六

(void)sscanf("AAAaaaBBB", "%*[A-Z]%[a-z]", str);				// "aaa\0!!!!!!"
复制代码


这个实验出现了一个新的符号:%*,与 % 相反,%* 表示过滤满足条件的字符。


在这个实验中,%*[A-Z] 过滤了所有大写字母,然后再使用 %[a-z] 把之后的小写字母拷贝给 str。如果只有 %* 没有 % 的话,sscanf 不会拷贝任何字符到 str,这时 sscanf 的作用仅仅是过滤字符串。


实验七

(void)sscanf("AAAaaaBBB", "%[a-z]", str);						// "!!!!!!!!!!"
复制代码


做完前面几个实验后,我们都知道 sscanf 拷贝完成后,还会在 str 的后面加上一个 nil 字符,但如果没有一个字符满足条件,sscanf 不会在 str 的后面加 nil 字符,str 的值依然是 10 个感叹号。


这个实验也说明了,如果不使用 %* 过滤掉前面不需要的字符,你永远别想取得中间的字符。


实验八

(void)sscanf("AAAaaaBC=a", "%*[A-Z]%*[a-z]%[^a-z=]", str);		// "BC\0!!!!!!!"(void)sscanf("AAAaaaBCa=", "%*[A-Z]%*[a-z]%[^a-z=]", str);		// "BC\0!!!!!!!"
复制代码


这是一个综合实验,但这个实验的目的不是帮我们复习前面所学的知识,而是展示两个值得注意的地方:


  1. %* 可以使用多次,比如在这个实验里面,先用 %*[A-Z] 过滤大写字母,然后用 %*[a-z] 过滤小写字母。

  2. ^ 后面可以带多个条件,且这些条件都受 ^ 的作用,比如 %[^a-z=] 表示 ^a-z 且 ^= (既不是小写字母,也不是等于号)。


实验九

int k;(void)sscanf("AAA123BBB456", "%*[^0-9]%i", &k);					// k = 123
复制代码


首先,%*[^0-9] 过滤掉前面非数字的字符,然后用 %i 把数字字符转换成 int 型的整数,拷贝到变量 k,注意参数必须使用 k 的地址。


三、避免 sscanf 写内存越界


如上实验一和实验二,如果不对长度进行限制,则默认将匹配成功的字符都写入到 str 中,思考这么一个问题:如果源数据的长度 > 接收数组的长度,会不会写越界呢?


实验十


/** * | str | 低地址 eb80 * | tmp | 高地址 eb90 */char tmp[16] = "!!!!!!!!!!!!!!!!"; // 16 个感叹号char str[16] = {0};
// tmp = "!!!!!!!!!!!!!!!!"(void)sscanf("0123456789abcdef-123", "%s", str);// tmp = "-123\0!!!!!!!!!!!"
复制代码


在本实验中,我们申请了两块长度均为 16 的字符串 tmp 和 str,其中 tmp 用感叹号初始化。


尝试用 str 去读取长度为 20 的源字符串 "0123456789abcdef-123",由于没有限制 sscanf 的处理长度,所以 sscanf 会写越界,将多出来的 "-123" 写入到了 tmp 中。


内存读写越界严重时会导致程序崩溃,所以我们要尽可能去避免,而对于 sscanf 来说,可以通过限制处理的长度来保证不会写越界:

// tmp = "!!!!!!!!!!!!!!!!"(void)sscanf("0123456789abcdef-123", "%15s", str); // str[15] 保存结束符 '\0'// tmp = "!!!!!!!!!!!!!!!!", str = "0123456789abcde"
复制代码


但是这么写有个弊端,那就是如果 str 的长度发生变化,sscanf 中也需要同步修改,这对于程序维护而言肯定是不方便的。


目前还没有想到一个很好的方法可以解决这个问题,如果大家有更好的方法,请不吝赐教。


最后,附上实验的 Demo


#include <stdio.h>
void Print(const char *pcStr, size_t ulStrLen){ for (int i = 0; i < ulStrLen; i++) { if (pcStr[i] == 0) printf("\\0"); else printf("%c", pcStr[i]); } puts(""); return;}
void Test1(){ char str[10] = "!!!!!!!!!!"; (void)sscanf("123456", "%s", str);
Print(str, sizeof(str));}
void Test2(){ char str[10] = "!!!!!!!!!!"; (void)sscanf("123456", "%3s", str);
Print(str, sizeof(str));}
void Test3(){ char str[10] = "!!!!!!!!!!"; (void)sscanf("abcABC", "%[a-z]", str);
Print(str, sizeof(str));}
void Test4(){ char str[10] = "!!!!!!!!!!"; (void)sscanf("AAAaaaBBB", "%[^a-z]", str);
Print(str, sizeof(str));}
void Test5(){ char str[10] = "!!!!!!!!!!"; (void)sscanf("AAAaaaBBB", "%[A-Z]%[a-z]", &str[0], &str[5]);
Print(str, sizeof(str));}
void Test6(){ char str[10] = "!!!!!!!!!!"; (void)sscanf("AAAaaaBBB", "%*[A-Z]%[a-z]", str);
Print(str, sizeof(str));}
void Test7(){ char str[10] = "!!!!!!!!!!"; (void)sscanf("AAAaaaBBB", "%[a-z]", str);
Print(str, sizeof(str));}
void Test8(){ char str[10] = "!!!!!!!!!!"; (void)sscanf("AAAaaaBC=", "%*[A-Z]%*[a-z]%[^a-z=]", str); Print(str, sizeof(str));}
void Test9(){ int k; (void)sscanf("AAA123BBB456", "%*[^0-9]%i", &k); printf("%d\n", k);}
void Test10(){ /** * | str | 低地址 eb80 * | tmp | 高地址 eb90 */ char tmp[16] = "!!!!!!!!!!!!!!!!"; char str[16] = {0};
Print(tmp, sizeof(tmp)); // tmp = "!!!!!!!!!!!!!!!!" (void)sscanf("0123456789abcdef-123", "%s", str); Print(tmp, sizeof(tmp)); // tmp = "-123\0!!!!!!!!!!!"
return;}
void Test11(){#define LEN_8 8#define TO_STR(x) #x#define SSCANF_LEN_LIMIT(len) TO_STR(%len)
char szBuf[LEN_8 + 1]; (void)sscanf("12345abcde", SSCANF_LEN_LIMIT(LEN_8) "[0-9a-z]", szBuf); // "%8" "s" puts(szBuf);
return;}
int main(){ Test11(); return 0;}
复制代码


文章转载自:MElephant

原文链接:https://www.cnblogs.com/hyacinthLJP/p/18199868

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
C 语言中的 sscanf 详解_MySQL_EquatorCoco_InfoQ写作社区