c++
的 I/O 类型分别定义在头文件
iostream
控制窗口fstream
已命名文件sstream
string 对象
iostream
iostream
继承自 istream
和 ostream
,又有子类 fstream
和 stringstream
可以使用 >>
和 <<
对流输入输出。这两个操作符返回左操作数(也就是流本身),因此可以进行链式操作
string s1, s2;
cin >> s1 >> s2;
cout << s1 << s2 << endl;
/* input:
* hello, world!
* output:
* hello,world!
*/
注意到析取字符或字符串时,以空白字符或换行符为分隔。为了控制输入输出的一系列特性,c++
提供了一系列操纵符。这些操纵符都是以流对象的引用为参数的函数,在使用时会返回操纵后的流引用,因此可以用于链式输入输出当中
#include <iostream>
#include <iomanip>
#include <sstream>
using namespace std;
int main() {
cout << boolalpha << true << endl // true
<< noboolalpha << true << endl; // 1
cout << showbase // 显示数字前缀
<< hex << 100 << endl // 0x64
<< oct << 100 << endl // 0144
<< dec << 100 << endl; // 100
cout << showpos // 显示符号
<< 100 << endl // +100
<< noshowpos
<< 100 << endl; // 100
cout << fixed
<< 0.01 << endl // 0.010000
<< scientific
<< 0.01 << endl // 1.000000e-2
<< defaultfloat
<< 0.01 << endl; // 0.01
stringstream ss(" a bc");
char c;
ss >> noskipws >> c; // 析取时不跳过前导空白符
cout << c << endl; // ' '
ss >> skipws >> c;
cout << c << endl; // 'a'
cout << setw(10) // 设置下一个域的宽度
<< 10 << endl // " 10"
<< setfill('*') // 不足宽度的部分以某字符填充
<< setw(10)
<< 10 << endl // "********10"
<< setw(10) << left // 输出内容在域中靠左对齐
<< 10 << endl // "10********"
<< right;
return 0;
}
Condition State
在某些情况下,流可能处于不正常状态。例如
int main() {
int x; cin >> x;
cout << x << endl;
cout << cin.good() << endl;
return 0;
}
/* input: abc
* output:
* 0
* 0
*/
可以看出,good
函数用于判断流是否处于正常状态。与之等价的判断方法为
if (cin) cout << "cin is valid" << endl;
else cout << "cin is not valid" << endl;
更为具体的,c++
定义了三个 strm::iostate
类型的标志位,其中 strm
表示流类型
位名 | 位置 | 查询函数 | 描述 |
---|---|---|---|
badbit | 1 | bad() | 系统级故障 |
eofbit | 2 | eof() | 读入文件结束符 |
failbit | 4 | fail() | 可恢复错误 |
前面提到的 good
函数相当于 !(bad() | eof() | fail())
。一般来说,如果 badbit
被置 1 ,流就不能再继续使用了。而 failbit
则可能代表着用户输入了与析取类型不符的字符串,例如
int main() {
int x; cin >> x;
cout << x << endl;
cout << "failbit: " << cin.fail() << endl;
return 0;
}
/* input: abc
* output:
* 0
* failbit: 1
*/
eofbit
可以看作 failbit
的一个子集,即遇到文件结束符时同时设置 eofbit
和 failbit
。例如
int main() {
int x; cin >> x;
cout << x << endl;
cout << "failbit: " << cin.fail() << endl;
cout << "eofbit: " << cin.fail() << endl;
return 0;
}
/* input: ctrl-z
* output:
* 358350902
* failbit: 1
* eofbit: 1
*/
可以使用 rdstate
同时查看三个位的叠加
int main() {
int x; cin >> x;
cout << "status: " << cin.rdstate() << endl;
return 0;
}
/* input: ctrl-z
* output:
* status: 6
*/
使用函数 clear(strm::iostate)
和 setstate(strm::iostate)
可以针对性的设置某个标志位。其中,clear(strm::iostate)
函数会清除参数以外的条件状态位。不提供 clear
函数的参数相当于将流设置为有效(good)
int main() {
cin.setstate(iostream::badbit); // 001
cin.setstate(iostream::failbit); // 101
cin.clear(iostream::badbit); // 001
cin.setstate(iostream::eofbit); // 011
cin.clear(); // 000
return 0;
}
值得注意的是,尽管上述函数只接受一个 strm::iostate
类型参数,但该类型位运算后的结果仍是 strm::iostate
类型。也就是说,可以使用这种方法同时设置多个条件状态位
cin.setstate(iostream::badbit | iostream::failbit); // 101
getline 函数
getline
函数用于从输入流读入一行文本。全局的 std::getline
函数声明在 string
头文件中
istream& getline(istream& is, string& str, char delim = '\n');
这个函数的功能是从 is
中一个个读取字符,直到遇到 delim
。读取到的字符串会被保存到 str
中,而 str
中原本的内容则会被替换。最后,函数返回 is
以方便判断读取是否成功
while (getline(cin, s)) // ...
istream::getline
成员函数具有类似的功能
istream& istream::getline (char* s, streamsize n, char delim = '\n');
不同之处在于
- 存储对象是
C
风格字符串 - 成员函数中增加了一个参数
n
,用于限制读取字符串的最大长度
int main() {
char s[10];
cin.getline(s, 10);
cout << s << endl;
return 0;
}
正常情况下,如果一行输入的字符数不超过
10
10
10 个(包含 \n
),这段程序会将其原样打印。反之,程序只截取输入的前
9
9
9 个字符。也可以这样理解,该函数不断从流中读取字符并存储到字符数组中,直到取到 delim
或第 n
个字符。最后取得的这个字符不会被放入字符数组,因为函数需要放入 \0
以标志字符串的结束。同时如果一行输入的字符数超过
10
10
10 个,也就是说字符串因过长而被截断,那么 cin
的条件状态将被置为 fail
。
另外,由于这一函数使用字符数组作为容器,因此可能导致溢出
int main() {
char s[10];
cin.getline(s, 13);
cout << s << endl;
cout << strlen(s) << endl;
cout << cin.rdstate() << endl;
return 0;
}
/* input:
* 12345678901
* output:
* 12345678901
* 11
* 0
*/
可见,数组溢出并没有触发异常或其他错误,尽管这是十分危险的。
fstream
文件也可以以流的方式处理,最简单的方法是
fstream ifile("in_file_name");
// ...
ifile.close()
构造函数接受文件名作为参数,用于绑定文件和流对象。这一绑定过程也可以通过函数实现
fstream ifile;
ifile.open("in_file_name");
也就是说,一个流对象可以多次绑定不同的文件,只要保证 open
操作总在流对象无绑定文件时即可(否则流会 fail
)
int main() {
fstream ifile("in");
cout << ifile.rdstate() << endl; // 0
ifile.open("main.cpp");
cout << ifile.rdstate() << endl; // 4
return 0;
}
打开文件后,通常需要判断打开是否成功
if (!ifile) {
// open failed
}
File Mode
文件模式和条件状态类似,以位的形式标志了文件的属性。下表中对象类型均忽略了 fstream
模式名 | 位置 | 说明 | 对象类型 |
---|---|---|---|
app | 1 | 写入前定位到文件尾 | ofstream |
ate | 2 | 打开文件后定位到文件尾 | ifstream, ofstream |
binary | 4 | 二进制模式 | ifstream, ofstream |
in | 8 | 可进行读操作 | ifstream |
out | 16 | 可进行写操作 | ofstream |
trunc | 32 | 打开文件是清空已存在文件流 | ofstream |
默认情况下,文件模式为
ifstream ifile("in_file_name", fstream::in);
ofstream ofile("out_file_name", fstream::out);
fstream file("file_name", fstream::in | fstream::out);
需要注意的是,作为组合出现的文件模式和单独出现时可能具有不同的含义。除 ate
模式和 binary
模式外,剩余模式的有效组合为(这两个模式可以附在任何有效组合之后)
组合 | 说明 |
---|---|
out | 做写操作,同时删除文件中已有的数据 |
out | app | 做写操作,在文件尾写入 |
out | trunc | 与表中 out 组合模式相同 |
in | 做读操作 |
in | out | 做读写操作,定位于文件头 |
in | out | trunc | 做读写操作,同时删除文件中已有的数据 |
stringstream
stringstream
可用于字符串的格式化输出与解析。可以将其构造函数等效看做
stringstream::stringstream(string str = "");
也就是说,stringstream
可以用一个字符串来初始化。要查看 stringstream
对象所包含的字符串,可以使用
stringstream ss("abc");
cout << ss.str() << endl;
与之类似的另一个函数是
stringstream::str(const string&)
这个函数用于更改 stringstream
对象的值,相当于对其进行重新初始化。
string
string
类型支持长度可变的字符串。除了使用字符串字面量对其进行初始化,还可以构造仅包含某个字符的字符串
string s(3, 'c'); // "ccc"
这一方法可以用于将字符转化为 string
类型,而整型变量转字符串则复杂一些。一种方法是使用字符串流,但是由于需要引入流变量,这样做的效率并不高。c++11
开始支持 string
头文件中的 std::to_string
函数,可以将数值转化为字符串。
使用头文件 cctype
可以对字符串中的字符进行判断。常用判断函数如下
函数 | 释义 | 函数 | 释义 |
---|---|---|---|
isalnum(c) | alpha or digit | ||
isalpha(c) | isdigit(c) | ||
islower(c) | isupper(c) | ||
ispunct(c) | punctuation | isspace(c) | |
tolower(c) | returns isupper(c) ? lower case of c : c | toupper(c) | returns islower(c) ? upper case of c : c |
为了理解其中某些函数的功能,定义
- 可打印字符 可以表示的字符
- 空白字符 空格、制表符、垂直制表符、回车符、换行符或进纸符
- 标点符号 除了数字、字母、空白字符以外的可打印字符