一、函数介绍

函数原型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 = 0
iRet = 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;
}

C 语言中的 sscanf 详解的更多相关文章

  1. php中关于引用(&)详解

    php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...

  2. 【转载】C/C++中extern关键字详解

    1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.此外extern也可用来进行链接指定. 也就是说extern ...

  3. C语言解决约瑟夫问题详解的代码

    将开发过程中比较重要的一些内容做个收藏,下面的内容是关于C语言解决约瑟夫问题详解的内容,希望能对码农有帮助. #pragma once #include<vector> class PRO ...

  4. C#中的Attribute详解(下)

    原文地址:https://blog.csdn.net/xiaouncle/article/details/70229119 C#中的Attribute详解(下) 一.Attribute本质 从上篇里我 ...

  5. Linux C 语言之 Hello World 详解

    目录 Linux C 语言之 Hello World 详解 第一个 C 语言程序 程序运行原理 编译,链接 运行时 链接库 编译器优化 Hello World 打印原理 stdout, stdin 和 ...

  6. Javascript中prototype属性详解 (存)

    Javascript中prototype属性详解   在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例.但是在Javascript语言体系中,是不 ...

  7. “全栈2019”Java第八十四章:接口中嵌套接口详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  8. 「翻译」Unity中的AssetBundle详解(二)

    为AssetBundles准备资源 使用AssetBundles时,您可以随意将任何Asset分配给所需的任何Bundle.但是,在设置Bundles时,需要考虑一些策略.这些分组策略可以使用到任何你 ...

  9. [转]C语言字节对齐问题详解

    C语言字节对齐问题详解 转载:https://www.cnblogs.com/clover-toeic/p/3853132.html 引言 考虑下面的结构体定义: typedef struct{ ch ...

  10. js插件---videojs中文文档详解

    js插件---videojs中文文档详解 一.总结 一句话总结: js插件网上都有很多参考资料,使用起来也非常简单 二.lavarel中使用实例 <video id="example_ ...

随机推荐

  1. Python 集合(Sets)1

    集合 集合用于在单个变量中存储多个项.集合是 Python 中的 4 种内置数据类型之一,用于存储数据集合,其他 3 种是列表(List).元组(Tuple)和字典(Dictionary),它们都具有 ...

  2. 前端使用 Konva 实现可视化设计器(3)

    github/gitee Star 终于有几个了! 从这章开始,难度算是(或者说细节较多)升级,是不是值得更多的 Star 呢?! 继续求 Star ,希望大家多多一键三连,十分感谢大家的支持~ 创作 ...

  3. np.squeeze()

    np.squeeze() 是 NumPy 库中的一个函数,用于从数组中删除单维度的条目.它返回一个在输入数组中删除了尺寸为 1 的维度的新数组. 下面是使用 np.squeeze() 的示例代码: 点 ...

  4. linux下升级openssh参考[不建议采用此法安装]

    本人不见一采用这种方法安装,只是当遇到问题时候有一定的参考意义,所以贴了上来.建议使用yum方式安装,详见在下另一篇博文: http://blog.csdn.net/lqzixi/article/de ...

  5. 数据库PolarDB开源之路该如何走?听听他们怎么说

    简介: 10月25日,由阿里云开发者社区.阿里云PolarDB开源社区.InfoQ联合举办的「开源人说」数据库PolarDB专场线下沙龙在杭州召开,5位阿里云数据库超级大咖.10位阿里云数据库开源生态 ...

  6. ModelScope初探:一行代码调用成熟AI模型。

    简介: 如何用一行代码调用成熟AI模型?试试ModelScope,让AI开发者解放生产力! ModelScope是阿里推出的下一代开源的模型即服务共享平台,为泛AI开发者提供灵活.易用.低成本的一站式 ...

  7. 使用 Arthas 排查 SpringBoot 诡异耗时的 Bug

    简介: 公司有个渠道系统,专门对接三方渠道使用,没有什么业务逻辑,主要是转换报文和参数校验之类的工作,起着一个承上启下的作用.最近,在优化接口的响应时间,优化了代码之后,但是时间还是达不到要求:有一个 ...

  8. 5G 和云原生时代的技术下半场,视频化是最大最新的确定性

    ------------恢复内容开始------------ null ------------恢复内容结束------------

  9. Serverless 场景排查问题利器 : 函数实例命令行操作

    ​简介:实例命令行功能的推出希望能消除用户使用 Serverless 的"最后一公里",直接将真实的函数运行环境展现给用户. 背景介绍 全托管的 Serverless 计算平台能给 ...

  10. AI和大数据结合,智能运维平台助力流利说提升核心竞争力

    简介: 简介:本文整理自数智创新行--智能运维专场(上海站),流利说最佳实践演讲:<基于SLS千万级在线教育平台统一监控运营实践> 作者:孙文杰 流利说运维总监元乙 阿里云智能技术专家 优 ...