C++ 学习笔记2—— I/O 操作

c++ 的 I/O 类型分别定义在头文件

  • iostream 控制窗口
  • fstream 已命名文件
  • sstream string 对象

iostream

iostream 继承自 istreamostream,又有子类 fstreamstringstream
ios 继承层次

可以使用 >><< 对流输入输出。这两个操作符返回左操作数(也就是流本身),因此可以进行链式操作

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 表示流类型

位名位置查询函数描述
badbit1bad()系统级故障
eofbit2eof()读入文件结束符
failbit4fail()可恢复错误

前面提到的 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 的一个子集,即遇到文件结束符时同时设置 eofbitfailbit 。例如

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');

不同之处在于

  1. 存储对象是 C 风格字符串
  2. 成员函数中增加了一个参数 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

模式名位置说明对象类型
app1写入前定位到文件尾ofstream
ate2打开文件后定位到文件尾ifstream, ofstream
binary4二进制模式ifstream, ofstream
in8可进行读操作ifstream
out16可进行写操作ofstream
trunc32打开文件是清空已存在文件流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)punctuationisspace(c)
tolower(c)returns isupper(c) ? lower case of c : ctoupper(c)returns islower(c) ? upper case of c : c

为了理解其中某些函数的功能,定义

  • 可打印字符 可以表示的字符
  • 空白字符 空格、制表符、垂直制表符、回车符、换行符或进纸符
  • 标点符号 除了数字、字母、空白字符以外的可打印字符

参考

getline()函数详解

C/C++中的 getline()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LutingWang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值