文章目录
前言
- 为了避免大量内容挤在一篇文章里,这里将关于泛型算法和I/O的讨论放在这一章
- 本章I/O处理部分包括了在刷算法题、leetcode、uva中常遇到包括但不限于下列问题:
- 输入处理、输出处理
- 字符、数字转换
- 字符串、数组转换
- 字符串分割
- 做一些对输入处理要求较高的算法题时,常常会发现这类题的解决方案很简单,算法也很容易,但是唯独在处理输入上花费大量时间。本文I/O部分除了介绍基本的c++标准库中的I/O外,还附加介绍如何在这类题目中利用I/O处理这类问题。
函数对象
- 可以像调用函数一样调用的对象。
- 函数对象常用于泛型算法的实参。
- 函数对象的本质就是重载函数调用运算符,即括号
class Test
{
public:
void operator() (int a)//重载函数调用运算符
{
cout << a << endl;
}
};
int main(int argc, char const *argv[])
{
Test t;
t(3);
return 0;
}
I/O处理
- I/O处理的内容非常广泛,除了和标准输入输出相关的I/O,文件I/O和字符串I/O都很常用,具有大量实用功能。本文尚未介绍文件I/O,将在后续进行补充
- I/O library中有三组常用的I/O classes,分别是
- 继承关系满足
derived class -> base class
条件状态位
- 这些状态位对所有I/O library的I/O classes都支持
- failbit:例如,当往一个
int
类型变量输入一个char
时,failbit会置1。一般failbit是指示发生了可恢复的错误 - badbit:发生了系统级的读写错误时置位,一般不可恢复
while(cin >> a)
==while(cin.good())
- 置位:
cin.setstate(s.badbit & ~s.good() & s.failbit & ~s.endbit)
- 获取当前状态位:
auto state = cin.rdstate()
sstring
字符串输入:getline()
input
:输入流str
:用于存储从输入流中获取的字符串delim
:分割符号。当str在输入流input中遇到该符号时停止读入,并舍弃该符号- 更详细的函数原型相关请自行查阅官方文档
- I/O处理上,继承机制中很可能采用了虚函数+base class的引用或指针+动态绑定从而实现多态的方式。这就意味着在函数原型中的
std::basic_istream<..>& input
(即istream)引用类型参数可以是ifstream
或istringstream
。
string s;
getline(cin, s);
cout << s;
- 尝试使用
ifstream
和istringstream
作为参数:
//istringstream
string s("1234");
istringstream is(s);
string t;
getline(is, t);
cout << t;
//输出:1234
//ifstream
ifstream f("Output.c");
string t;
getline(f, t);
cout << t;
//输出:文件中的第一行,除了换行符以外其它都包括
- 很好!我们发现getline可以拓展到字符串流和文件流中操作,这样为我们读取一行提供给了极大的方便。
- 由于getline在istringstream上的强大操作,它还可以帮助我们实现许多功能。
字符串分割
- 假设现在从stdin或者文件中读入一行单词,每个单词之间如果是用空格分割的,那么提取单词的方式其实很简单:
ifstream f("Output.c");
string s;
while(f >> s)
cout << s << endl;
- 但如果单词之间的分割是某个符号呢?比方说逗号或者分号,上面的办法就无效了,因为逗号和分号也会被当作char类型。我们可以利用getline:
ifstream f("Output.c");
string s;
while(getline(f, s, ','))
cout << s << endl;
获取字符串和流的内容
string s("1234");
istringstream is(s)
cout << s.c_str() << endl;
//returns a non-modifiable standard C character array
//version of the string
cout << is.str() << endl;
字符转数字
char a = '3';
cout << (int)a-48 << endl;
cout << static_cast<int>(a)-48 << endl;
//48这个常数不变
数字转字符
int a = 9;
cout << (char)(a+48) << endl;
//注意括号
字符串转数组
string s("1234");
istringstream is(s);
int a[4];
char c;
for(int i = 0; i < 4; ++i)
{
is >> c;
a[i] = (int)c-48;
}
字符串转数字
string s("1234");
istringstream is(s);
int a;
is >> a;
//注意!
//istringstream会把流内的内容“给”外部变量且不自我保留
//故在下一次使用istringstream向外部输出时,会发现stream内已空
//但用于给stream初始化的string不会丢失,因为流内的内容是从
//string 复制进去的
数组转字符串
int a[4] = {1, 2, 3, 4};
string s;
for(int i = 0; i < 4; ++i)
s.push_back((char)(a[i]+48));
数字转字符串
int a = 1234;
string s;
ostringstream os;
os << a;
s.assign(os.str());
//注意!
//用ostringstream os(s)的方式将os和s绑定
//使用 os << a的方式
//不会给s带来任何改变
//os(s)这种构造方式只会给os的 underlying string device object 一个初始值
//事实上大多时候我们也不需要os(s)这样的操作
Tips
从上面的实验可以发现一些结论
string用string s
的方式来定义,没有给初始值,这么做string底层存储字符串的空间是否为空?直接给这样的s赋值是否会导致内存溢出?
- Ans:这么做是没有任何问题的。通过调用
s.capacity()
可以发现,s在刚开始定义时就已经分配了空间。通过s.reserve(n)
,其中n为期望s分配的新的空间大小。 - 当n <
s.capacity()
时该调整无效;当n >s.capacity()
时调整有效
如何清空stringstream
?
- 注意:每次使用同一个stringstream对象前都要进行标志位和内存的清空,否则下一次往stringstream对象内输入字符串不会改变其内容,甚至有可能会报错
- 清空操作:
stringstream ss;
ss.clear();//清空状态标志位
ss.str("");//清空内容,双引号内没有字符
- 两种清空缺一不可!