第20章 iostream库
C++中的IO流是通过多继承和虚拟继承实现的,下面是它的关系.
我们要学习的就是三个库,这里我会把重点的拿出来
- iostream: 终端操作
- fstream:文件操作
- sstream:格式化操作
20.1 输出操作符<<
输出操作符可以接受任何内置数据类型的实参,包含我们的const char*,string,甚至是自定义类型.先说为何我们可以连续的经行打印,看下面的现象.
这是因为计算的结果是左边的ostream 操作数也就是说表达式的结果是cout 对象自己于是通过这个序列它又被应用到下一个输出操作符上等等我们说操作符<<从左向右结合.那么这里我们就可以输入自定义类型了,不过我们需要重载一下这个运算符.
#include <iostream>
class Node
{
public:
friend std::ostream& operator<<(std::ostream& out, const Node& n);
private:
int _x = 0;
int _y = 1;
};
std::ostream& operator<<(std::ostream& out, const Node& n){
out << n._x << " " << n._y;
return out;
}
int main()
{
Node n1;
std::cout << n1 << std::endl;
return 0;
}
注意,由于我们把const char*解释成C语言的字符串,同时有支持地址的打印,这里就会出现一些不太舒服的现象.
int main()
{
int a = 10;
const char* str = "hello";
std::cout << "&a: " << &a << std::endl;
std::cout << str << std::endl;
return 0;
}
z注意,书上给了解决方法,这里我不推荐,我也没有看.我们知道const char*可以打印字符串就可以了,至于指针,我们不是有C语言的printf函数吗?那个不是更爽.
下面有一道练习题,先把部分答案写出来,不过我们这里看到一个非常有趣的类,这个类叫做complex,那么它究竟是什么呢?他是一个复数类,大家搜一下就可以了.
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void print_addr(const char* p)
{
printf("%p\n", p);
}
void max_num(int x, double d)
{
cout << (x < d ? x : d) << endl;
}
int main()
{
// 我把第一问给省了,注意是太麻烦,但是不难
string robin( "christopher robin" );
const char *pc = robin.c_str();
int ival = 1024;
double dval = 3.14159;
print_addr(pc);
max_num(ival, dval);
return 0;
}
20.2 输入操作符>>
输入主要由右移操作符>> 来支持的,我们已经用过了.不过这里提到了一个非常有趣的概念,我们知道cin是支持循环读取的,那么下面的代码编译器是如何知道我们读取应该结束了呢?
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
int n = 0;
while(cin >> n)
{
}
return 0;
}
他给了两个解释,先看结论,后面解释.
- 读到文件结束
- 读到无效值,这个有趣的很
缺省情况下输入操作符丢弃任何中间空白空格制表符换行符走纸以及回车符,意思就是我们可以这样输入.不过大家用的时候还是规范一点吧,看个人喜好.
#include <iostream>
#include <string>
using namespace std;
int main()
{
int a = 0;
int b = 0;
int c = 0;
cin >> a >> b >> c;
cout << "a: " << a << endl;
cout << "b: " << b << endl;
cout << "c: " << c << endl;
return 0;
}
20.3 其他输入输出操作符
这里我们只谈两个成员函数,都是非常简单的,这本书谈的实在是太全了,一般我们都不会用到这么多的功能,当然也可能是见识有点少.我们上面说了,输入和输出的时候遇到某一些字符会出现问题,这里继续看例子.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str;
cin >> str;
cout << str << endl;
return 0;
}
那么我们是不是可以找一个方法来解决这个问题呢?我们要求想空格也可以输入.这里看里面提供的函数.
- get 从流中得到一个字符
- put 向流中输送一个字符
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str;
char ch = 0;
while (cin.get(ch))
{
cout.put(ch);
}
return 0;
}
那么我们该如何退出来呢?太简单了,看下面的
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str;
char ch = 0;
while (cin.get(ch))
{
cout.put(ch);
if (ch == '\n')
break;
}
return 0;
}
我看书上说了我们在输入多行的时候可以使用ignore(),来去掉一个字符,就像我们的换行符,说实话我没有把它的原理看太懂,不过我们可以使用上面的方法来好好设计一下也是可以完成这样的功能而且成本也不高.
20.4 重载输出操作符<<
这里直接上代码,我们之前学过了.
#include <iostream>
#include <string>
using namespace std;
class Myclass
{
friend ostream &operator<<(ostream &os, const Myclass &node);
private:
int x = 0;
int y = 1;
};
ostream &operator<<(ostream &os, const Myclass &node)
{
os << node.x << " " << node.y;
return os;
}
int main()
{
Myclass node;
cout << node << endl;
return 0;
}
20.5 重载输入操作符>>
这个更加简单.
#include <iostream>
#include <string>
using namespace std;
class Myclass
{
friend ostream &operator<<(ostream &os, const Myclass &node);
friend istream &operator>>(istream &os, Myclass &node);
private:
int x = 0;
int y = 1;
};
ostream &operator<<(ostream &os, const Myclass &node)
{
os << node.x << " " << node.y;
return os;
}
istream &operator>>(istream &in, Myclass &node)
{
in >> node.x >> node.y;
return in;
}
int main()
{
Myclass node;
cin >> node;
cout << node << endl;
return 0;
}
20.6 文件输入和输出
这个已经来到一个比较常用的功能了,C++的文件IO非常好用.他的思想是把文件作为一个参数构造出一个对象,我们把这些对象作为流就可以进行操作了,多个不说,上代码.
打开一个我们读操作的流
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
ifstream in("file.txt");
if (!in.is_open())
return -1;
char ch = 0;
while (in.get(ch))
{
cout << ch;
}
return 0;
}
下面我们打开一个写的文件,我们这里直接追加.
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
// std::ios::out 是 把原本内容清空,默认就是它
ofstream out("file.txt", std::ios::app);
if (!out.is_open())
return -1;
string str = "这是追加";
out << str;
return 0;
}
有人感觉太麻烦,我们还要定义两个对象,这里我们直接用一个,然后传入我们是读还是写.来看代码.
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
fstream in_out;
in_out.open("file.txt", std::ios::in);
string str;
while (std::getline(in_out, str))
{
cout << str;
cout << endl;
}
in_out.close();
in_out.open("file.txt", std::ios::out);
str = "这是截断";
in_out << str;
in_out.close();
return 0;
}
20.7 条件状态
前面所谓的调节状态就是我们什么时候不能正确使用我们的流,我们这里直接看例子吧,为何我们把字符串给int类型会报错,我们知道他应该报错,可是编译器是如何检测出来的,我们有理由怀疑这里存在检测机制,实际上确实有,不过我这里不和大家分享了,我们规范使用就可以了.
int main()
{
int val = 0;
cin >> val;
return 0;
}
那么我们这里我们谈什么呢?老生重谈,我们这里你觉得如何?
int main()
{
int val = 0;
while (cin >> val)
{
}
return 0;
}
请问cin>>val为何可以作为判断条件,我们知道cin返回的对象就是流,本质就类似我们自定义类型,那么如果我们自己写一下自定类型可以这样吗,它可以自己东的转换为bool吗?试一下.
struct Node
{
int x;
};
int main()
{
int val = 0;
Node n;
while (n)
{
}
return 0;
}
我们发现不行,不过我们要是这样做就可以了,看好了
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
struct Node
{
int x;
operator bool()
{
return true;
}
};
int main()
{
int val = 0;
Node n;
while (n)
{
cout << "hello" << endl;
break;
}
return 0;
}
实际上cin对象所在的类型里面就重载了operator bool,这就是我们为何可以转换的原因,这里有两个问题要谈
- 可以转换成其他累心吗 -- 可以
- cin 还是没有谈如何判断我们不能用
来我们测试其他的类型.
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
struct Node
{
int x;
operator int()
{
return 1;
}
};
int main()
{
int val = 0;
Node n;
int ret = n;
cout << ret << endl;
return 0;
}
我们来解释第二个问题,我们看下面的代码.
int main()
{
int ret = 0;
cin >> ret;
while (!cin)
{
cout << "hello" << endl;
break;
}
return 0;
}
那么 operator bool()函数里面一定存在条件判断,那么就是这个就是我们书上这一小节谈的就是这个,具体的我们直接看看书就可以了.
20.8 string 流
来说我们我们第三个部分,就是我们所谓的格式化,看书上这段话.
ostringstream 类向一个string 插入字符,istringstream 类从一个string 对象读取字符而stringstream 类可以用来支持读和写两种操作.
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
using namespace std;
struct Node
{
int x = 1;
int y = 2;
};
int main()
{
Node n;
ostringstream out;
out << "x: " << n.x << "\n"
<< "y: " << n.y << "\n";
cout << out.str();
return 0;
}
下面我们来说反序列化,也是挺简单的.,注意,这里我们没有实际的场景,写的非常挫.
struct Node
{
int x = 1;
int y = 2;
};
istream &operator>>(istream &os, Node &n)
{
os >> n.x >> n.y;
}
int main()
{
string str = "10 20";
istringstream in(str);
Node n;
in >> n;
cout << n.x << " " << n.y << endl;
return 0;
}
20.9 格式状态
不谈,不如我们回来看printf,那里我们更加熟悉.
20.10 强类型库
iostream库是强类型的例如试图从一个ostream 读数据或者写数据到一个istream都会在编译时刻被捕获到并标记为类型违例,实际上我们不关心,最起码,我现在没有关心.