最近写了一个Github的C++Json解析器,这是其中遇到的一个问题,在查询大量资料之后编写了这篇文章。
这个函数位于头文件cstdio中,我们先来看看它的作用是什么:将格式化输入写入指定的缓冲区。
int snprintf(char *str, size_t size, const char *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
// 这个头文件用于规定输入输出的小数点位数
// 全称是“input/output manipulation”
#include
std::ostringstream oss;
oss << std::setprecision(17) << std::fixed << value;
std::string str = oss.str();
接下来我们就说说sstream这个头文件的使用。
sstream中有个十分关键的类,叫做stringstream,首先需要说下stringstream它究竟是什么:
stringstream 实际上是使用一个字符串作为内部的缓冲区。在内存中,这个缓冲区是一个字符数组,可以在其中存储任意长度的字符序列。
也就是说,我们能够将stringstream当作一个包装器,用于将字符串类型转换为其他类型。它能够存储任意的字符类型,并且能够很方便地将其转换为不同类型的数据,很安全也很方便。
方法 | 作用 |
---|---|
<< | 用于向流中插入数据 |
>> | 用于向流中提取数据 |
str() | 1.获取流中的内容 2.更改流中的内容 |
setstate() | 设置流的状态(状态有很多,使用的时候再去找资料吧) |
clear() | 清除流的状态标识 |
本文只给出了stringstream的部分常用函数,它的函数很多,因为它的基类是std::basic_stringstream,再往底层走那些文档我就不是很能看得懂了。
这里就用一个例子来说明stringstream的使用:
#include
#include
#include
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()有两个版本:
好像这个函数没有什么需要注意的地方:
#include
#include
#include
int main(){
std::stringstream ss;
ss.str("hello world");
std::string out = ss.str();
std::cout << out;
}
除了使用str()进行数据写入,我们还能够使用它重载的"<<"运算符进行数据写入。
虽然str()和"<<”都有更改stringstream中数据的作用,但是它们的作用还是有所不同:str()是直接替换其中的,而"<<"是将输入的元素添加在原本的数据后面,于是我写了如下代码:
#include
#include
#include
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
#include
int main(){
std::stringstream ss;
ss.str("hello ");
// 拼接
ss << ss.str() << "world";
std::cout << ss.str();
}
还有很多种写法,反正宗旨都是注意流指针的位置。
这个函数其实用的也比较少,只有当stringstream出现异常状态的时候才需要使用,这里我们使用一段代码来说明:
#include
#include
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
#include
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的输入输出操作是基于流指针的,于是我又编写了以下代码:
#include
#include
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的底层是维护了一个计数器,因为查找gcc源码,在str()函数调用一直向下查,能找到这么一行:
![[289163feae27ce6d15d54b4130649d4a.png]]
因此我猜测是这个,但是我却没有办法去验证,所以只能记住这个特性了。