流的来源和目的
- 在C++中,流可使用三个公共的来源与目的:控制台、文件和字符串。
控制台流
流式输出
std::endl
会刷新目标缓冲区,在循环中应该谨慎使用它。
put()
接受单个字符,write()
接受一个字符数组。传给这两个方法的数据将按照原本的形式输出。const char* str{"hello world!\n"}; cout.write(str,strlen(str)); cout.put('c');
在以下任意一种条件下,流将刷新积累的数据:
- 遇到endl操作算子;
- 流离开作用域被析构;
- 流缓冲区满;
- 显式地刷新缓冲区,
cout.flush()
;- 从对应的输入流(cin)输入数据时。
good()
方法获得流的基本验证信息,bad()
方法返回true时说明发生了致命错误,fail()
方法在最近一次操作失败时返回true,eof()方法在流到达文件尾部时返回true。流具有可转换为bool类型的转换运算符,转换运算符与
!fail()
返回的结果相同。即!cout == cout.fail()
。遇到文件结束标记时,
good()
和fail()
都会返回false。关系如下:good() == (!fail()) && !eof()
。
clear()
方法可以重置流的错误状态。可以要求流在发生故障时抛出异常(ios_base::failure)。
cout.exceptions(ios::failbit | ios::badbit | ios::eofbit); try{ cout<<"hello world!"<<endl; }catch(const ios_base::failure& ex){ cerr<<"Caught exception: "<<ex.what()<<",error code: "<<ex.code()<<endl; }
输出操作算子
boolalpha
和noboolalpha
,将布尔值输出为true和false(boolalpha)或者1和0(noboolalpha),默认是noboolalpha;hex
、oct
和dec
,分别以十六进制、八进制和十进制输出数字;setprecision
,接收一个参数,设置输出小数时的小数位数;setw
,接收一个参数,设置输出的数值数据的字段宽度;setfill
,接收一个字符,设置为流的填充字符;showpoint
和noshowpoint
,对于没有小数部分的浮点数,设置显示或者不显示小数点;put_money
,参数化的操作算子,向流写入一个格式化的货币值;put_time
,参数化的操作算子,向流写入一个格式化的时间值;quoted
,参数化的操作算子,把给定的字符串放到引号中,并转义嵌入的引号。
流式输入
>>运算符会根据空白字符进行标志化,包括
' '
、'\f'
、'\n'
、'\r'
、'\t'
、'\v'
。读取数据后应该检查流状态,这样可以从异常输入中恢复。
int sum{0}; if(!cin.good()){ cerr<<"Standard input is in a bad state!"<<endl; return 1; } while(!cin.bad()){//ctrl+Z退出循环(windows)或者ctrl+D退出循环(linux) int num; cin>>num; if(cin.good())sum+=num; else if(cin.eof())break; else if(cin.fail()){//如果输入的不是int而是字符或字符串 cin.clear();//重置错误状态 string badToken; cin>>badToken;//将字符或字符串写入badToken cerr<<"WARNING:Bad input encountered: "<<badToken<<endl; } } cout<<"sum = "<<sum<<endl;
get()
方法从流中读入原始输入数据,返回流中的下一个字符(最简单版本)。可用于避免>>运算符的自动标志化。string readName(istream& stream){//参数不能是const string name; while(stream){//或者:while(!stream.fail()){ int next{stream.get()};//由于get()会返回一些特殊的非字符值,例如文件结束std::char_traits<char>::eof(),因此用int if(!stream||next==std::char_traits<char>::eof())break; name+=static_cast<char>(next); } return name; } //可以使用另一个版本的get()使代码更简洁,这个版本的get()接收一个字符的引用,返回一个流的引用。 string readName(istream& stream){ string name; char next; while(stream.get(next)){ name += next; } return name; }
unget()
可以将读入的前一个字符放回流中。如果当前位置就是流的起始位置,那么调用unget()就会失败,可通过fail()方法查看。int main() { string name; string left; char ch; cin >> noskipws;//不跳过空白字符 while (cin >> ch) { if (isdigit(ch)) {//如果是数字则放回流中并退出循环 cin.unget(); if (cin.fail()) cout << "unget() failed" << endl; break; } name += ch; } if (cin) cin >> left;//将剩余部分写入left if (!cin) cerr << "error getting size" << endl; cout << "name: " << name << "\nleft: " << left << endl; }
putback()
方法使用一个字符作为参数,将其放入cin中。
peek()
方法可以预览流中的下一个字符。
getline()
方法用一行数据填充字符缓冲区,数据量最多至指定大小,指定大小包括\0
字符。const int buffersize{10}; char buffer[buffersize] {0}; cin.getline(buffer, buffersize, '@');//读取9个字符,或者读到行尾,或者遇到@符号,第三个参数是可选的分割符,默认是\n string str; std::getline(cin,str,'#');//读到行尾或遇到#符号,这个函数不需要指定缓冲区大小
输入操作算子
boolalpha
和noboolalpha
:如果设置了boolalpha,字符串false会被解析为布尔值false,其他任何字符串都会被解析为布尔值true;如果设置了noboolalpha,0会被解析为false,其他任何值都被解析为true。默认为noboolalpha。dec
、hex
和oct
:分别读取十进制数、十六进制数和八进制数。skipws
和noskipws
:读入空白字符作为标记,或者在标记化时跳过空白字符。- ws:跳过流中当前位置的一串空白字符。
- get_money():从流中读入一个格式化的货币值。
- get_time():从流中读入一个格式化的时间值。
- quote():读取引号中的字符串,并转义嵌入的引号。
字符串流
std::ostringstream
- 通过str()方法将数据转化为string对象。
std::istringstream
文件流
文本模式与二进制模式
- 默认情况下,文件流在文本模式中打开。在文本模式中会执行一些隐式转换,写入文件或从文件中读取的每一行都以
\n
结束(windows下是\r\n
)。- 二进制模式中,要求把流处理的字节写入文件。读取时,完全按照文件中的形式返回字节。
ofstream
ofstream
的构造函数第一个参数为要打开的文件名,第二个参数为打开文件的模式。默认包含ios_base::out
模式。
常量 说明 ios_base::app 打开文件,在每一次写操作之前,移到文件末尾 ios_base::ate 打开文件,打开之后立即移到文件末尾 ios_base::binary 以二进制模式执行输入输出操作 ios_base::in 打开文件,从开头开始读取 ios_base::out 打开文件,从开头开始写入,覆盖已有的数据 ios_base::trunc 打开文件,并删除任何已有的数据 可使用
seekp()
方法在输出流中移动到任何位置。seekp()有两个重载版本,一个版本接收一个绝对位置作为参数,调用之后定位到这个位置。另一个版本接收一个偏移量和一个绝对位置作为参数,调用之后定位到绝对位置加偏移量的位置。位置的类型为std::streampos
,偏移量的类型为std::streamoff
,整数可被隐式转换为streampos类型和streamoff类型。可使用
tellp()
方法查询流的当前位置。这个方法返回一个表示当前位置的streampos值。
位置 说明 ios_base::beg 表示流的开头 ios_base::end 表示流的结尾 ios_base::cur 表示流的当前位置 析构函数会自动关闭底层文件,因此不需要显式调用
close()
。
ifstream
- 可使用
seekg()
方法在输出流中移动到任何位置。- 可使用
tellg()
方法查询流的当前位置
将流链接在一起
- 任何输入流和输出流之间都可以建立链接,从而实现“访问时刷新”,即当输入流请求数据时,链接的输出流会自动刷新。
- 对输入流调用
tie()
方法,并传入输出流的地址即可建立链接。传入nullptr即可解除链接。- 输出流可以链接至另一个输出流。cerr和cout之间存在链接,因此cerr的任何输出都会导致刷新cout。
双向I/O
- fstream类提供双向文件流。
- 可以通过stringstream类双向访问字符串流。
- 双向流用不同的指针保存读位置和写位置,在读取和写入之间切换时需要定位到正确的位置。
文件系统支持库
路径:
path
接口,append()方法可以添加组件到路径中,会自动插入平台相关的路径分割符。目录:如果要查询文件系统上的实际目录或文件,需要通过
path
构造一个directory_entry
。copy()函数可以复制文件或目录。
create_directory()函数可以在文件系统上创建一个新目录。
file_size()函数可以获取文件大小。
last_write_time()函数可以获取文件最后的修改时间。
remove()函数可以删除文件。
temp_directory_path()函数可以获取适合存储临时文件的目录。
space()函数可用于查询文件系统上的可用空间。
遍历目录
#include <filesystem> void printDirectoryStructure(const std::filesystem::path& p) { if (!std::filesystem::exists(p))return; std::filesystem::recursive_directory_iterator begin{ p };//起始迭代器 std::filesystem::recursive_directory_iterator end{};//结束迭代器,默认构造 for (auto iter{ begin }; iter != end; ++iter) { const string spacer(iter.depth() * 2, ' '); auto& entry{ *iter };//访问迭代器所引用的directory_entry if (is_regular_file(entry)) { cout << std::format("{}File:{}({}bytes)", spacer, entry.path().string(), file_size(entry)) << endl; } else if (is_directory(entry)) { cout << std::format("{}Dir:{}", spacer, entry.path().string()) << endl; } } } void printDirectoryStructure(const std::filesystem::path& p,size_t level) { if (!std::filesystem::exists(p))return; const string spacer(level * 2, ' '); if (is_regular_file(p)) { cout << std::format("{}File:{}({}bytes)", spacer, p.string(), file_size(p)) << endl; } else if (is_directory(p)) { cout << std::format("{}Dir:{}", spacer, p.string()) << endl; for (auto& entry : std::filesystem::directory_iterator{ p }) { printDirectoryStructure(entry, level + 1); } } } int main() { std::filesystem::path p{ R"(C:\mingw64)"}; printDirectoryStructure(p, 0); }