C++snprintf和stringstream

前言

最近写了一个Github的C++Json解析器,这是其中遇到的一个问题,在查询大量资料之后编写了这篇文章。

snprintf()

这个函数位于头文件cstdio中,我们先来看看它的作用是什么:将格式化输入写入指定的缓冲区

int snprintf(char *str, size_t size, const char *format, ...)
  • str是指向要写入的缓冲区的指针
  • size是要写入的字符的最大数量
  • format是一个格式化字符串,用于指定输出的格式
  • …是可变参数列表,用于传递变量值

在我的Json项目中,有这么一段代码:

static void dump(double value, string& out){
    // std::isfinite在头文件cmath中,用于判断浮点数是否是有限的
    // 即是否既不是无穷大(inf),也不是非数(NaN)
    //如果参数是有限的浮点数,则返回 true;否则,返回 false。
    if(std::isfinite(value)){
        char buf[32];
        // 在头文件cstdio中
        // 用于:将格式化的输入写入指定的缓冲区
        snprintf(buf, sizeof buf, "%.17g", value);
    }
    else{
        out += "null";
    }
}

在这里,我的使用是:

snprintf(buf, sizeof buf, "%.17g", value);

其中,我只需要再解释一下value就行了:snprintf将变量 value 格式化为一个字符串,也就是将value中所存储的数据更改为字符串类型。
但是我C语言学的不是很深,我对C不是很了解,我只想到了C++的stream头文件好像也是类似的作用,因此可以对它进行一个改写,使其更合乎C++代码的规范:

#include <sstream>
// 这个头文件用于规定输入输出的小数点位数
// 全称是“input/output manipulation”
#include <iomanip>

std::ostringstream oss;
oss << std::setprecision(17) << std::fixed << value;
std::string str = oss.str();

接下来我们就说说sstream这个头文件的使用

sstream

sstream中有个十分关键的类,叫做stringstream,首先需要说下stringstream它究竟是什么:

stringstream 实际上是使用一个字符串作为内部的缓冲区。在内存中,这个缓冲区是一个字符数组,可以在其中存储任意长度的字符序列。

也就是说,我们能够将stringstream当作一个包装器,用于将字符串类型转换为其他类型。它能够存储任意的字符类型,并且能够很方便地将其转换为不同类型的数据,很安全也很方便。

方法作用
<<用于向流中插入数据
>>用于向流中提取数据
str()1.获取流中的内容 2.更改流中的内容
setstate()设置流的状态(状态有很多,使用的时候再去找资料吧)
clear()清除流的状态标识

本文只给出了stringstream的部分常用函数,它的函数很多,因为它的基类是std::basic_stringstream,再往底层走那些文档我就不是很能看得懂了。
这里就用一个例子来说明stringstream的使用:

#include <sstream>
#include <iostream>
#include <string>

int main(){
    std::stringstream ss;
    // 向流中写入数据
    ss << "Hello World";
    // 获取流中的数据
    std::string out = ss.str();
    std::cout << out << std::endl;

    // 使用带参数的str对流中的内容进行替换
    ss.str("None");
    out = ss.str();
    std::cout << out << std::endl;

    // 更改流状态
    ss.setstate(std::ios_base::iostate::_S_badbit);

    // 重置流数据
    // 但是由于流状态已经出错了
    // 此时重置流数据无效
    ss << "Hello";
    out = ss.str();
    std::cout << out << std::endl;

    // 清除状态标识
    ss.clear();
    // 可以重置流数据了
    ss << "Hello";
    out = ss.str();
    std::cout << out << std::endl;
}

str()的使用

str()有两个版本:

  • 无参版本:str()函数返回stringstream对象当前的字符序列作为一个std::string对象
  • 有参版本:str(const std::string& s)函数将std::string类型的参数s作为stringstream对象的新内容,并清除原有内容

好像这个函数没有什么需要注意的地方:

#include <iostream>
#include <sstream>
#include <string>

int main(){
	std::stringstream ss;
	ss.str("hello world");
	std::string out = ss.str();
	std::cout << out;
}

除了使用str()进行数据写入,我们还能够使用它重载的"<<"运算符进行数据写入
虽然str()和"<<”都有更改stringstream中数据的作用,但是它们的作用还是有所不同:str()是直接替换其中的,而"<<"是将输入的元素添加在原本的数据后面,于是我写了如下代码:

#include <iostream>
#include <sstream>
#include <string>

int main(){
	std::stringstream ss;
	ss.str("hello world");
	std::string out = ss.str();
	std::cout << out << std::endl;

    ss << "??";
    out = ss.str();
	std::cout << out << std::endl;
}

运行结果取跟我想的不一样,我希望它是:

hello world
hello world??

实际上它是:

hello world
??llo world

stringstream中维护了一个流指针,str()会将流指针的位置重置至stringstream的首部,而<<是根据流指针的位置进行数据写入,因此就产生了数据覆盖
因此我们想要完成字符串的拼接可以这么写:

#include <sstream>
#include <iostream>

int main(){
	std::stringstream ss;
	ss.str("hello ");

	// 拼接
	ss << ss.str() << "world";
	std::cout << ss.str();
}

还有很多种写法,反正宗旨都是注意流指针的位置

clear()的使用

这个函数其实用的也比较少,只有当stringstream出现异常状态的时候才需要使用,这里我们使用一段代码来说明:

#include <sstream>
#include <iostream>

int main(){
    std::stringstream ss;
    // 先将ss置空
    ss.str("");

    // 手动增加流状态标识
    ss.setstate(std::ios_base::badbit);

    // 再次更改ss
    ss << "Hello world";
    // 没输出,说明更改失败
    std::cout << ss.str() << std::endl;

    // 清除流的状态标识
    ss.clear();
    // 再次更改ss
    ss << "Hello world";
    // 更改成功
    std::cout << ss.str() << std::endl;
}

这个情况只有在使用“<<”运算符才会出现,如果我们使用的str()进行数据的修改,不管它是什么状态都能够正常运行,代码如下

#include <sstream>
#include <iostream>

int main(){
    std::stringstream ss;
    // 先将ss置空
    ss.str("");

    // 手动增加流状态标识
    ss.setstate(std::ios_base::badbit);

    // 再次更改ss
    ss.str("Hello world");
    // 更改成功
    std::cout << ss.str() << std::endl;

    // 清除流的状态标识
    ss.clear();
    // 再次更改ss
    ss.str("Hello world!!!");
    // 还是更改成功
    std::cout << ss.str() << std::endl;
}

这是因为:由于std::stringstream继承自std::basic_istream和std::basic_ostream,它继承了在流类中常见的成员函数,其中包括 str() 方法。str()方法用于获取或设置流中的字符串内容,而不涉及流的状态标识。因此,不管我们怎么设置流的状态标识,str()都能够正常获取和更改数据,这涉及了C++继承机制的知识点。

对于stringstream的疑惑

因为之前说,stringstream的输入输出操作是基于流指针的,于是我又编写了以下代码:

#include <sstream>
#include <iostream>

int main(){
    std::stringstream ss;
    ss.str("Hello World");

    std::cout << ss.tellg() << '\n';

    // 清空
    ss.str("");
    ss << "Hello World";
    std::cout << ss.tellg();
}

发现:不管我怎么对ss进行操作,tellg()所输出的结果始终是0,那么,流指针操控stringstream的说法就不攻自破了,那么,是为什么呢?
所以我猜测,stringstream的底层是维护了一个计数器,但是我却没有办法去验证,所以只能记住这个特性了。

2024.2.15更新内容

上面说到的疑惑已经解决了,在新文章对stringstream行为的补充

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默示MoS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值