简单总结梳理了C++中有关文件输入输出的知识,并添加程序实例(实例见最后)
例程2:2016年华为软件编程大赛文件(topo.csv、demand.csv、result.csv)的输入输出(删减)
涉及知识:类、函数重载、模板、多重继承、流操作、缓冲区、控制符和格式化常量、类型转换
学习目的:文本文件输入输出、控制输出格式
使用的类:iostream(标准输入输出类)、fstream(文件输入输出类)、sstream(string字符串输入输出类)
流操作:(C++把输入输出看作字节流,通过对字节流对象进行操作来对数据做相应的输入输出处理)
- 流是由字符字节(char)组成的数据,流操作(方法)的本质是类型转换操作
- 将流与程序关联起来(声明输入流/输出流对象)
- 将流与文件关联起来(将输入流/输出流对象与文件关联起来,调用成员函数open(char*)方法)
- 以上两个关联可以通过声明初始化一步完成,流的使用方法同标准的流cin/cout
缓冲区:(内存块,设备与程序之间的临时存储工具,帮助匹配两者不同的传输速率)
- 键盘输入时,通常是按下回车键时,刷新输入缓冲区
- 屏幕输出时,通常是发送换行符时,刷新输出缓冲区;有输入到来时,也可能会刷新输出缓冲区
- 使用控制符endl和flush
继承关系:作图
- ios_base类(流的一般特征:格式化常量等) ---> ios类(包括一个指向streambuf对象的指针) ---> istream类/osteam类(方法) ---> iostream类 ---> fstream类 ---> ifstream类/ofstream类
- sstream类的继承关系流程同fstream类 ---> istringstream类/ostringstream类
- streambuf类(管理输入/输出缓冲区的内存的类)
- 创建类对象,会打开一个流,并自动创建一个属于各自对象的缓冲区;包含iostream头文件会自动创建8个流对象(cin/cout/cerr/clog)(4个用于窄字符流,4个用于宽字符流)和一些对象代表流(它是与流特性相关的数据成员)
- 其它,将iostream类在std名称空间中,istream类和ostream类都是模板char具体化的typedef
输出格式化:
- cout<<输出、ostream & put(char)、ostream & write(char*,int),类型转换为char以文本字符格式输出(参数为普通基本类型或const的char指针或void*),另外write()不会遇到空字符停止打印字符
- ios_base类中的成员函数setf()方法(可调整单个格式标记信息)两个原型:fmtflags setf(fmtflags); fmtflags setf(fmtflags,fmtflags); (fmtflags是bitmask类型),该方法需结合使用格式化常量;fmtflags类型也是在ios_base类中定义的;另外,unsetf()方法原型:void unsetf(fmtflags);
- 标准控制符:计数系统控制(dec/oct/hex:basefield)数字显示控制(fixed/scientific:floatfield)对齐控制(left/right/internal:adjustfield)等等
- 3个最常用的控制符(头文件iomanip):setprecision(int)、setfill(char)、setw(int)分别设置精度、填充字符、字段宽度,相对于precision(int)、fill(char)、int width(int)使用更方便
- 控制符(运算符函数重载)可用cout语句进行连接,控制符是函数,但不是成员函数,不能通过对象调用使用;控制符位于名称空间std中
- 默认显示模式的精度是总位数,fixed/scientific显示模式的精度是小数位数
- cout.setf(ios_base::showpoint);可显示末尾的0,显示的多少取决于显示模式和精度,显示的数字位数与数字被存储时的精度没有任何关系
输入:(给程序提供数据)
- cin>>输入,格式化输入函数(参数为基本类型引用或char指针),跳过空白(空格、换行符、制表符)
- ios_base类中数据成员流状态(eofbit/badbit/failbit/goodbit)iostate类型(也是bitmask类型)是在ios_base类中定义的,这3个状态位都为0时,说明一切正常
- 常用的设置流状态方法:good()、clear(iostate s=0)(无参可以用于重新打开输入)、setstate(iostate s);设置流状态位有一个非常重要的后果,流将对后面的输入或输出关闭,直到位被清除
- 非格式化输入成员函数istream & get(char &)、int get(void)单字符输入,不跳过空白
- 非格式化输入成员函数get(char*,int+1,char)、getline(char*,int+1,char)读取字符串进行输入,不跳过空白;第三个参数省略的话,默认将换行符用作分界字符;读取最大数目的字符或分界字符后为止;两者的主要区别是是否保留分界符,getline(char*,int+1,char)不保留分界符
- 非预期设置failbit,流被破坏设置badbit,文件尾设置eofbit;文件尾符号常量EOF(头文件iostream中定义),cin.get(ch)遇文件尾转换为false 并设置failbit,ch=cin.get()遇到文件尾输出EOF的值;文件尾或空行(即没有读取任何字符),get(char*,int+1,char)都会设置failbit,文件尾(或特殊空行)或读取最大数目的字符(且行中还有字符)时,会让getline(char*,int+1,char)设置failbit;while(getline(cin,str)&&str.size()>0){}空行处理方法
- cin.ignore(int,'\n');可用于删除行中剩余的字符;char cin.peek();(==get(char)+putback(char))可用于查看下一个字符,但不抽取,可以用于多种条件(多个分界符)同时分割字符串;read()不会添加空字符,对应于write()
- 其它,getline(cin/fin/instr,string str,char ch)很方便
文件关联:
- 包含头文件fstream就可以,不必包含iostream,因为继承关系,但cin/cout/cerr对象的使用需要iostream头文件
- 以默认模式打开文件进行输出将自动把文件的长度截短为零,即删除已有内容
- 输入输出流对象过期时(即程序终止时),文件关联自动关闭,或用close()方法类似显式关闭;换其它文件进行关联需要显式使用close()方法;没有打开文件的话,不需要进行关闭关联,因为没关联上
- 关闭关联并不会删除流对象,则可以将流重新关联同一文件或另一文件,可能会需要cin.clear()方法重新打开输入,这取决于将文件与ifstream对象关联起来时,是否自动重置流状态,使用cin.clear()方法是无害的;关闭文件关联会刷新缓冲区
- 成员函数open(char*)方法以及ofstream的构造函数和ifstream的构造函数,参数指向char的指针(地址)是C-风格字符串,有必要将以string对象为文件名的字符串使用c_str()方法来转换为C-风格的字符串
- 打开一个不存在的文件进行输入时,将设置failbit,一种更好的检查文件是否被打开的方法is_open()
- 多文件处理策略取决于是同时处理使用还是依次处理使用,少开一些流会节省计算机资源
- 命令行参数(指定文件)int main(int argc,char* argv[])注意[]在argv后面,即第二个参数为char(* argv)[],argc是命令行参数个数,char(* argv)[]是一个指向char的指针数组(地址数组)
文件模式:(描述文件如何被使用:读、写、追加)
- 文件流的构造函数和i/ofstream open(char*,openmode mode=ios_base::in/out|trunc)方法,第二参数指定文件模式;注意,fstream类不提供默认的文件模式值,创建该对象必须显式提供第二参数
- openmode类型与fmtflags类型和iostate类型一样,都是一种bitmask类型,在ios_base中定义;文件模式常量(in/out/ate/app/trunc/binary);文件模式错误能够被is_open()方法检测出来
内核格式化:(负责程序和string对象的I/O)
- iostream族负责程序与终端之间的I/O;fstream族负责程序与文件之间的I/O;sstream族负责程序与string对象之间的I/O;接口使用方法相同
- string对象本质是文本文件,所以所对应的流操作依然是类型转换,也叫格式化信息操作(内核格式化)
- ostringstream中成员函数string str();返回一个被初始化为缓冲区内容的字符串对象,该方法可以“冻结”该对象
- getline(cin/fin/instr,string,char)很方便,因为string类很好用
- string字符串关联,istringstream流对象可以使用string对象进行初始化,如istringstream instr(str);
例程1:实现命令行参数读取文件,并对文件中的字符数进行统计求和
#include<fstream>
#include<iostream> // for cerr/cout
//#include<cstdlib> // for exit()
int main(int argc,char* argv[]) // 命令行参数,argv[i]是第 i 个指向 char 的指针(地址)
{
using namespace std;
if (argc == 1) // quit if no arguments
{
cerr << "Usage: " << argv[0] << " filename[s]\n";
exit(EXIT_FAILURE);
}
ifstream fin; // 开一个文件输入流
long count;
long total = 0;
char ch;
for (int i = 1;i < argc;i++)
{
fin.open(argv[i]); // 关联第 i 文件
cout << i << " | " << argc << " ";
if (!fin.is_open()) // 没有打开就不用使用close()
{
cerr << "Can't open " << argv[i] << endl;
fin.clear(); // 重新打开输入
continue;
}
count = 0;
while (fin.get(ch))count++; // 统计文件中的字符数
cout << count << " character in " << argv[i] << endl;
total += count;
fin.clear(); // 重新打开输入
fin.close(); // 换文件需要先断开之前的文件 disconect file
}
cout << total << " charaters in all files.";
return 0;
}
例程2:2016年华为软件编程大赛文件(topo.csv、demand.csv、result.csv)的输入输出(删减)
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>
#include<iomanip> // for setw() setfill()
int main()//int argc,char* argv[]) // 命令行参数
{
using namespace std;
ifstream file_topo("F:\\topo.csv");//argv[1]); // 关联 topo.csv 文件
string topo_line;
string topo_num[4];
int top_num[4];
if (file_topo.good()) // check
{
while (getline(file_topo,topo_line)) // read one line
{
istringstream strl_topo(topo_line); // 将每一行初始化一个字符串输入流对象
for (int i = 0;i < 4;i++)
{
getline(strl_topo,topo_num[i],','); // 按“,”进行分割
top_num[i] = atoi(topo_num[i].c_str()); // 转换为 int 类型,为之后比较大小
cout << setw(2) << setfill('0') << top_num[i] << " "; // 格式化输出
}
cout << endl;
}
}
cout << "*********************************" << endl;
file_topo.close();
//file_topo.clear();
ifstream file_demand("F:\\demand.csv");//argv[2]); // 关联 demand.csv 文件
string demand_line;
string demand_sec[3];
string demand_num[52];
int num_len = 0;
if (file_demand.good())
{
if (getline(file_demand,demand_line))
{
istringstream strl_demand(demand_line);
for (int i = 0;i < 3;i++)
{
getline(strl_demand,demand_sec[i],',');
}
istringstream strsec_demand(demand_sec[2]);
int i = 2;
while (getline(strsec_demand,demand_num[i],'|'))
{
i++;
}
num_len = i;
}
}
int* dem_num = new int[num_len]; // 动态数组
dem_num[0] = atoi(demand_sec[0].c_str());cout << dem_num[0] << " ";
dem_num[1] = atoi(demand_sec[1].c_str());cout << dem_num[1] << " ";
for(int i = 2;i < num_len;i++)
{
dem_num[i] = atoi(demand_num[i].c_str());cout << dem_num[i] << " ";
}
cout << endl << "*********************************" << endl;
file_demand.close();
//file_demand.clear();
ofstream file_result; // 关联 result.csv 文件
file_result.open("F:\\result.csv",ios::out|ios::trunc); //argv[3],ios::out|ios::trunc);
file_result << setw(2) << setfill('0') << dem_num[0];
for(int i = 1;i < num_len;i++)
{
file_result << "|" << setw(2) << setfill('0') << dem_num[i];
}
file_result.close();
//file_result.clear();
return 0;
}
本文总结自《C++ primer plus》(第六版中文版)第十七章:输入、输出和文件