C 语言中的 sscanf 详解

sscanf · 浏览次数 : 0

小编点评

**实验结果:** | 测试号 | 代码 | 结果 | |---|---|---| | 1 | `sscanf("AAAaaaBBB", "%*[A-Z]%[a-z]\", str);` | 成功获取 "aaa\\0!!!!!!!" | | 2 | `sscanf("AAAaaaBBB", "%[a-z]\", str);` | 成功获取 "!!!!!!!!!!" | | 3 | `sscanf("abcABC", "%[a-z]\", str);` | 成功获取 "abc" | | 4 | `sscanf("AAAaaaBBB", "[^a-z]\", str);` | 成功获取 "!!!!!!" | | 5 | `sscanf("AAAaaaBBB", "%[A-Z]%[a-z]\", &str[0], &str[5]);` | 成功获取 "AAAaaaBBB" | | 6 | `sscanf("AAAaaaBBB", "%*[A-Z]%[a-z]\", str);` | 成功获取 "aaa\\0!!!!!!!" | | 7 | `sscanf("AAAaaaBBB", "%[a-z]\", str);` | 成功获取 "!!!!!!!!!!" | | 8 | `sscanf("AAAaaaBC=\", "%*[A-Z]%*[a-z]%[^a-z=]\", str);` | 成功获取 "AAAaaaBC=" | | 9 | `int k; (void)sscanf("AAA123BBB456", "%*[^0-9]%i", &k);` | 成功获取 12345 | | 10 | `/** * | str | 低地址 eb80 * | tmp | 高地址 eb90 */` | 成功获取 "12345abcde" | | 11 | `#define LEN_8 8#define TO_STR(x) #x#define SSCANF_LEN_LIMIT(len) TO_STR(%len)` | 获取字符串长度为 8 的内容 | **总结:** 所有测试都成功获取了字符串的特定部分,并按照要求进行格式化。

正文

一、函数介绍

函数原型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 详解相似的内容:

C 语言中的 sscanf 详解

一、函数介绍 函数原型:int sscanf(const char *str, const char *format, ...); 返 回 值:成功返回匹配成功的模式个数,失败返回 -1。 RETURN VALUE These functions return the number of input

C语言中的窗口滑动技术

学习文章:C语言中的窗口滑动技术 滑动窗口法 C语言中的窗口滑动技术 循环几乎是每个复杂问题的一部分。太多的循环/嵌套循环会增加所需的时间,从而增加程序的时间复杂性。窗口滑动技术是一种计算技术,用于减少程序中使用的嵌套循环的数量,通过用单个循环代替嵌套循环来提高程序的效率。 如果你熟悉计算机网络中的

C++的extern关键字在HotSpot VM中的重要应用

extern关键字有两个用处: (1)extern在C/C++语言中表示函数和全局变量作用范围(可见性)的关键字,这个关键字会告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。 (2)在C++中引用C语言中的函数和变量,在包含C语言头文件时,需要使用extern "C"来处理。 1、ext

golang的 CGO 是什么

CGO是Go(Golang)语言中的一个工具,全称为 "C-Go" 或者 "C for Go"。 它是Go标准库的一部分,允许Go代码与C语言代码进行交互。 CGO提供了在Go程序中使用C语言库的能力,同时也允许C代码调用Go的函数。 通过CGO,开发者可以利用Go语言的强类型和垃圾回收等特性,同时

Go语言关键字解析:深入了解Go语言中的关键字

> 摘要:本文由葡萄城技术团队于博客园原创并首发。转载请注明出处:[葡萄城官网](https://www.grapecity.com.cn/),葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 # 前言 为了更加深入地介绍Go语言以及与C\#语言的比较,本文将会从多个维度出发进行详细的

4.9 C++ Boost 命令行解析库

命令行解析库是一种用于简化处理命令行参数的工具,它可以帮助开发者更方便地解析命令行参数并提供适当的帮助信息。C++语言中,常用的命令行解析库有许多,通过本文的学习,读者可以了解不同的命令行解析库和它们在C++项目中的应用,从而更加灵活和高效地处理命令行参数。

[转帖]lua-book-元表

http://me.52fhy.com/lua-book/chapter9.html 在Lua5.1语言中,元表 (metatable) 的表现行为类似于 C++ 语言中的操作符重载,类似PHP的魔术方法。Python里也有元类(metaclass)一说。 通过元表,Lua有了更多的扩展特性。Lua

算法金 | Python 中有没有所谓的 main 函数?为什么?

​大侠幸会,在下全网同名[算法金] 0 基础转 AI 上岸,多个算法赛 Top [日更万日,让更多人享受智能乐趣] 定义和背景 在讨论Python为何没有像C或Java那样的明确的main函数之前,让我们先理解一下什么是main函数以及它在其他编程语言中的作用。 在C和C++等语言中,main函数是

[转帖]【Linux学习】awk内置函数详细介绍(实例)

https://www.cnblogs.com/gtea/p/12668736.html 这节详细介绍awk内置函数,主要分以下3种类似:算数函数、字符串函数、其它一般函数、时间函数 一、算术函数: 以下算术函数执行与 C 语言中名称相同的子例程相同的操作: 函数名 说明 atan2( y, x )

C语言指针易混淆知识点总结

指针 定义 指针是一个变量,存储另一个变量的内存地址,它允许直接访问和操作内存中的数据,使得程序能够以更灵活和高效的方式处理数据和内存。 获取变量地址:使用取地址符 &。 访问地址上的数据:使用解引用符 *。 例子1 指针是存储另一个变量地址的变量。通过使用取地址符 & 和解引用符 *,我们可以灵活