C++学习(二):学会使用stringstream
1.前言
今天在CppTemplateTutorial群里,有人问了一个问题:这一堆add怎么简化掉 https://wandbox.org/permlink/vDPDwMFbBIQSSymS。代码如下:
1 #include <variant> 2 #include <map> 3 #include <string> 4 #include <cassert> 5 #include <iostream> 6 struct myval : public std::variant<int, double, std::string> 7 { 8 using base = std::variant<int, double, std::string>; 9 myval() : base() {} 10 myval(int v) : base(v) {} 11 myval(double v) : base(v) {} 12 myval(std::string v) : base(v) {} 13 14 myval(myval const& v) : base(v) {} 15 myval(myval&& v) : base(std::move(v)) {} 16 }; 17 18 template<class T, class U> 19 myval add(T const& a, U const& b) { 20 return a + b; 21 } 22 23 myval add(std::string const& a, int b) { 24 return a + std::to_string(b); 25 } 26 myval add(std::string const& a, double b) { 27 return a + std::to_string(b); 28 } 29 myval add(std::string const& a, std::string const& b) { 30 return a + b; 31 } 32 33 myval add(int a, std::string const& b) { 34 return std::to_string(a) + b; 35 } 36 myval add(double a, std::string const& b) { 37 return std::to_string(a) + b; 38 } 39 40 myval operator+(myval const& a, myval const& b) 41 { 42 return std::visit([](auto& a, auto& b) { return add(a, b); }, a, b); 43 } 44 45 std::ostream& operator<<(std::ostream& os, myval const& b) 46 { 47 return std::visit([&](auto b)->std::ostream& { os << b; return os; }, b); 48 } 49 50 int main() { 51 myval a = 1; 52 myval b = 10.0; 53 myval c = a + b; 54 std::map<std::string, myval> m{{"1", a}, {"2", b}, {"3", c}}; 55 if (m.find("3") == m.end()) 56 { 57 std::cout << m["3"]; 58 } 59 }
一种解决方法是:给to_string加个string重载,不过这并不是最好的办法。另一种方法是使用stringstream。不过,想到前一段时间,我还经常用C语言的sprintf,我突然觉得有点好笑。
处理问题的优先方式应该是充分考虑语言的特性,使得问题处理起来更加简洁高效一点。学会使用stringstream,可以避免一些由C语言中类似atoi,itoa ,strtol以及sprintf带来的问题。
那么,这个问题如果使用stringstream可以这样写:
2.使用stringstream对象简化类型转换
C++标准库中的<sstream>提供了比ANSI C的<stdio.h>更高级的一些功能,即单纯性、类型安全和可扩展性。
为什么要学习stringstream。如果你已习惯了<stdio.h>风格的转换,也许你首先会问:为什么要花额外的精力来学习基于<sstream>的类型转换呢?也许对下面一个简单的例子的回顾能够说服你。假设你想用sprintf()函数将一个变量从int类型转换到字符串类型。为了正确地完成这个任务,你必须确保证目标缓冲区有足够大空间以容纳转换完的字符串。此外,还必须使用正确的格式化符。如果使用了不正确的格式化符,会导致非预知的后果。下面是一个例子:
int n=10000; chars[10]; sprintf(s,"%d",n);// s中的内容为"10000"
到目前为止看起来还不错。但是,对上面代码的一个微小的改变就会使程序崩溃:
int n=10000;char s[10];sprintf(s,"%f",n);// 看!错误的格式化符
在这种情况下,程序员错误地使用了%f格式化符来替代了%d。因此,s在调用完sprintf()后包含了一个不确定的字符串。要是能自动推导出正确的类型,那不是更好吗?
<sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本。简单起见,我主要以stringstream为中心,因为每个转换都要涉及到输入和输出操作。
注意,<sstream>使用string对象来代替字符数组。这样可以避免缓冲区溢出的危险。而且,传入参数和目标对象的类型被自动推导出来,即使使用了不正确的格式化符也没有危险。
在过去留下来的程序代码和纯粹的C程序中,传统的<stdio.h>形式的转换伴随了我们很长的一段时间。但是,如文中所述,基于stringstream的转换拥有类型安全和不会溢出这样抢眼的特性,使我们有充足得理由抛弃<stdio.h>而使用<sstream>。<sstream>库还提供了另外一个特性—可扩展性。你可以通过重载来支持自定义类型间的转换。
3.参考来源:
http://www.cppblog.com/Sandywin/archive/2007/07/13/27984.html
http://developer.zhiding.cn/2003/0304/83252.shtml
http://www.cnblogs.com/gamesky/archive/2013/01/09/2852356.html
http://www.cnblogs.com/james6176/p/3222671.html
http://www.cnblogs.com/lzjsky/archive/2011/01/04/1925538.html