第8章 C++IO流类库
练习8.1 编写函数,接受一个istream&参数,返回值类型也是istream&。此函数须从给定流中读取数据,直至遇到文件结束标识时停止。它将读取的数据打印在标准输出上。完成这些操作后,在返回流之前,对流进行复位,使其处于有效状态。
练习8.2 测试函数,调用参数为cin.
答案:
1 #include <iostream> 2 using namespace std; 3 istream& func(istream& os) 4 { 5 int v; 6 while (os >> v, os.eof() != 1)//只有在碰到结束符时才停止输入,windows下为ctrl+z 7 { 8 if (os.bad()) 9 throw runtime_error("IO流错误"); 10 if (os.fail())//遇到数据型错误时,必须清空缓冲区 11 { 12 cerr << "数据型错误,重新输入" << endl; 13 os.clear(); 14 //清理方式1 ,直接清空缓冲区中的当前新行的剩余数据 15 os.ignore(100,'\n');//必须清理缓冲区,清理错误数据,否则过不去 16 continue; 17 } 18 cout << v << endl; 19 } 20 os.clear();//必须复位恢复流正常状态,后续才可继续使用输入 21 return os; 22 } 23 24 istream& func2(istream& os) 25 { 26 int v; 27 while (os >> v, os.eof() != 1)//只有在碰到结束符时才停止输入 28 { 29 if (os.bad()) 30 throw runtime_error("IO流错误"); 31 if (os.fail())//遇到数据型错误时,必须清空缓冲区 32 { 33 cerr << "数据型错误,重新输入" << endl; 34 os.clear(); 35 //清理方式2,只清空遇到的错误类型数据,后面正确类型的数据保留。 36 while (!isspace(cin.get()))//只把空白符之前(包括空白符)的错误型数据读取出来扔掉 37 continue; 38 continue; 39 } 40 cout << v << endl; 41 } 42 os.clear();//必须复位恢复流正常状态,后续才可继续使用输入 43 return os; 44 } 45 int main() 46 { 47 //istream mycin; //错误,不能自定义标准IO流对象,其构造函数都是私有的。 48 func(cin); 49 //用于测试如果不复位,能否继续使用cin, 并不能。 50 //cin.clear(); 51 //int v;//继续输入 52 //cin >> v; 53 //cout << v << endl; 54 }
练习8.3 什么情况下,下面的while
循环会终止?
1 while (cin >> i) /* ... */ 1 while (cin >> i) /*...*/
1 while (cin >> i) /*...*/1 while (cin >> i) /*...*/
答案:
当遇到了IO流错误,或者遇到文件结束符,或者读入了无效数据时,会终止循环。
即当badbit,failbit,eofbit任何一个流状态位被置位时将会终止循环。
练习8.4 编写函数,以读模式打开一个文件,将其内容读入到一个string
的vector
中,将每一行作为一个独立的元素存于vector
中。
答案:
1 #include <iostream> 2 #include <fstream> 3 #include <vector> 4 #include <string> 5 using namespace std; 6 int main() 7 { 8 //方式1 9 ifstream fcin("temp.txt"); 10 if (!fcin)//如果打开文件失败 11 { 12 cout << "文件打开失败" << endl; 13 return -1; 14 } 15 16 string strbuf(""); 17 vector<string> vi; 18 while (std::getline(fcin, strbuf)) 19 { 20 vi.push_back(strbuf); 21 } 22 23 //读取完毕后打印vector进行测试 24 for (auto iter = vi.begin(); iter != vi.end(); iter++)//传统for 25 { 26 cout << *iter << endl; 27 } 28 29 fcin.close(); 30 }
1 #include <iostream> 2 #include <fstream> 3 #include <vector> 4 #include <string> 5 using namespace std; 6 int main() 7 { 8 //方式2 9 ifstream fcin; 10 fcin.open("temp.txt"); 11 if (!fcin)//如果打开文件失败 12 { 13 cout << "文件打开失败" << endl; 14 return -1; 15 } 16 string strbuf(""); 17 vector<string> vi; 18 while (std::getline(fcin, strbuf)) 19 { 20 vi.push_back(strbuf); 21 } 22 23 //读取完毕后打印vector进行测试 24 for (auto line : vi)//范围for 25 { 26 cout << line << endl; 27 } 28 29 fcin.close(); 30 }
练习8.5 重写上面的程序,将每个单词作为一个独立的元素进行存储。
答案:
1 #include <iostream> 2 #include <fstream> 3 #include <vector> 4 #include <string> 5 using namespace std; 6 int main() 7 { 8 9 ifstream fcin("temp.txt"); 10 if (!fcin)//如果打开文件失败 11 { 12 cout << "文件打开失败" << endl; 13 return -1; 14 } 15 16 string strbuf(""); 17 vector<string> vi; 18 while (fcin >> strbuf)//从第一个非空白字符开始按单词读取,到空白符结束 19 { 20 vi.push_back(strbuf); 21 } 22 23 //读取完毕后打印vector进行测试 24 for (auto iter = vi.begin(); iter != vi.end(); iter++)//传统for 25 { 26 cout << *iter << endl; 27 } 28 29 fcin.close(); 30 }
练习8.6 修改书店程序
重写7.1.1节的书店程序,从一个文件中读取交易记录。将文件名作为一个参数传递给main
。
答案:
1 //Sales.cpp 2 3 #include <iostream> 4 using std::istream; 5 using std::ostream; 6 7 #include "Sales_data.h" 8 Sales_data::Sales_data(std::istream& is) 9 { 10 // read will read a transaction from is into this object 11 read(is, *this); 12 } 13 14 double Sales_data::avg_price() const { 15 if (units_sold) 16 return revenue / units_sold; 17 else 18 return 0; 19 } 20 21 // add the value of the given Sales_data into this object 22 Sales_data& Sales_data::combine(const Sales_data& rhs) 23 { 24 units_sold += rhs.units_sold; // add the members of rhs into 25 revenue += rhs.revenue; // the members of ``this'' object 26 return *this; // return the object on which the function was called 27 } 28 29 Sales_data add(const Sales_data& lhs, const Sales_data& rhs) 30 { 31 Sales_data sum = lhs; // copy data members from lhs into sum 32 sum.combine(rhs); // add data members from rhs into sum 33 return sum; 34 } 35 36 // transactions contain ISBN, number of copies sold, and sales price 37 istream& read(istream& is, Sales_data& item) 38 { 39 double price = 0; 40 is >> item.bookNo >> item.units_sold >> price; 41 item.revenue = price * item.units_sold; 42 return is; 43 } 44 45 ostream& print(ostream& os, const Sales_data& item) 46 { 47 os << item.isbn() << " " << item.units_sold << " " 48 << item.revenue << " " << item.avg_price(); 49 return os; 50 }
1 //Sales_data.h 2 3 #ifndef SALES_DATA_H 4 #define SALES_DATA_H 5 6 #include <string> 7 #include <iostream> 8 9 class Sales_data { 10 friend Sales_data add(const Sales_data&, const Sales_data&); 11 friend std::ostream& print(std::ostream&, const Sales_data&); 12 friend std::istream& read(std::istream&, Sales_data&);// 13 public: 14 // constructors 15 Sales_data() 16 : units_sold(0), revenue(0.0) 17 { 18 } 19 Sales_data(const std::string& s) 20 :bookNo(s), units_sold(0), revenue(0.0) 21 { 22 } 23 Sales_data(const std::string& s, unsigned n, double p)//书的isbn字符串,销售数量,单价 24 :bookNo(s), units_sold(n), revenue(p * n) 25 { 26 } 27 Sales_data(std::istream&); 28 29 // operations on Sales_data objects 30 std::string isbn() const //返回isbn 31 { return bookNo; } 32 Sales_data& combine(const Sales_data&);//+= 33 double avg_price() const; //计算本书的单价 34 private: 35 std::string bookNo;//ISBN 36 unsigned units_sold;//本书已销售数量 37 double revenue;//销售总金额 38 }; 39 40 41 // nonmember Sales_data interface functions 42 Sales_data add(const Sales_data&, const Sales_data&); 43 std::ostream& print(std::ostream&, const Sales_data&); 44 std::istream& read(std::istream&, Sales_data&); 45 46 // used in future chapters 47 inline 48 bool compareIsbn(const Sales_data& lhs, const Sales_data& rhs) 49 { 50 return lhs.isbn() < rhs.isbn(); 51 } 52 #endif
1 #include "Sales_data.h" 2 #include <iostream> 3 #include <fstream> 4 using namespace std; 5 //已假设文件内同一类书的所有交易项(每单交易项)均是紧挨连续在一起的,且每行都一条订单交易项 6 7 int main(int args,char* argv[]) 8 { 9 fstream fcin(argv[1]);//关联并打开参数指定的文件 10 if (!fcin) 11 { 12 cout << "文件打开失败" << endl; 13 return -1; 14 } 15 16 Sales_data total;//用于保存同类书的数据总和 17 18 if (read(fcin, total)) 19 { 20 Sales_data next; 21 while (read(fcin, next)) 22 { 23 if (total.isbn() == next.isbn())//如果是相同书,则累加交易量 24 { 25 total.combine(next);//累加 26 } 27 else//碰到下一种书,则打印上一种书的交易总量 28 { 29 print(cout, total) << endl;// 打印上一种书的交易总量 30 total = next;//为统计下一种书做准备 31 } 32 } 33 print(cout, total) << endl;// 补刀,打印最后一种书的交易总量 34 } 35 else 36 { 37 cout << "一条交易数据也没读取到" << endl; 38 } 39 41 std::cout << "Hello World!\n"; 42 }
练习8.7
//修改上一节的书店程序,将结果保存到一个文件中。将输出文件名作为第二个参数传递给main函数。 #include "Sales_data.h" #include <iostream> #include <fstream> using namespace std; //已假设文件内同一类书的所有交易项(每单交易项)均是紧挨连续在一起的,且每行都一条订单交易项 int main(int args,char* argv[]) { fstream fcin(argv[1]);//关联并打开参数指定的文件 if (!fcin) { cout << "文件1打开失败" << endl; return -1; } fstream fcout(argv[2]); //关联并打开参数指定的文件 默认打开in|out if (!fcout) { cout << "文件2打开失败" << endl; fcin.close(); return -1; } Sales_data total;//用于保存同类书的数据总和 if (read(fcin, total)) { Sales_data next; while (read(fcin, next)) { if (total.isbn() == next.isbn())//如果是相同书,则累加交易量 { total.combine(next);//累加 } else//碰到下一种书,则打印上一种书的交易总量 { // 把上一种书的交易总量写入文件 print(fcout, total) << endl; total = next;//为统计下一种书做准备 } } print(fcout, total) << endl;// 补刀,写入最后一种书的交易总量 } else { cout << "一条交易数据也没读取到" << endl; } fcin.close(); fcout.close(); }
练习8.8
//修改上一题的程序,将结果追加到给定的文件末尾。对同一个输出文件,运行程序至少两次,检验数据是否得以保留。 #include <iostream> #include <fstream> using namespace std; //已假设文件内同一类书的所有交易项(每单交易项)均是紧挨连续在一起的,且每行都一条订单交易项 int main(int args,char* argv[]) { fstream fcin(argv[1]);//关联并打开参数指定的文件 if (!fcin) { cout << "文件1打开失败" << endl; return -1; } fstream fcout(argv[2],fstream::app); //关联并打开参数指定的文件 默认打开out|app if (!fcout) { cout << "文件2打开失败" << endl; fcin.close(); return -1; } Sales_data total;//用于保存同类书的数据总和 if (read(fcin, total)) { Sales_data next; while (read(fcin, next)) { if (total.isbn() == next.isbn())//如果是相同书,则累加交易量 { total.combine(next);//累加 } else//碰到下一种书,则打印上一种书的交易总量 { // 把上一种书的交易总量写入文件 print(fcout, total) << endl; total = next;//为统计下一种书做准备 } } print(fcout, total) << endl;// 补刀,写入最后一种书的交易总量 } else { cout << "一条交易数据也没读取到" << endl; } fcin.close(); fcout.close(); }
练习8.9
使用你为8.1.2节第一个练习所编写的函数打印一个istringstream
对象的内容。
那个题的循环读取的判断方式并不适合用读取字符串流对象上,因为用在字符串输入流对象中的数据结尾可能不存在空白符,导致在最后一次读取数据后无法再进入循环,造成最后一次的数据没有打印。. 或者说之前那个题的写法不好。不通用。 下面是其他人的代码:虽然考虑的简单,但胜在没BUG,通用。 //之前那个题的: std::istream& func(std::istream &is) { std::string buf; while (is >> buf) std::cout << buf << std::endl; is.clear(); return is; } //现在这个题的: #include <iostream> #include <sstream> using std::istream; istream& func(istream &is) { std::string buf; while (is >> buf) std::cout << buf << std::endl; is.clear(); return is; } int main() { std::istringstream iss("hello"); func(iss); return 0; }
练习8.10
//编写程序,将来自一个文件中的行保存在一个vector中。然后使用一个istringstream从vector读取数据元素,每次读取一个单词。 /*temp.txt文本内容 aaa aaa aaa//判断四次的情况 bbb bbb bbb//判断四次的情况 ccc ccc ccc//判断四次的情况 ddd ddd ddd//这是最后一行,后面无换行 ,属于判断三次的情况 */ #include <iostream> #include <string> #include <vector> #include <fstream> #include <sstream> using namespace std; int main() { fstream fcin("temp.txt"); string line; vector<string> vi; while (getline(fcin, line))//只依据failbit,屏蔽eofbit被置位的不同时机 { vi.push_back(line); } fcin.close(); for (auto iter = vi.begin(); iter != vi.end(); ++iter) { istringstream scin(*iter); string word; cout << "关联" << endl; //primer习题集答案错误,如果判断条件用eofbit判断,存在bug,在判断三次的情况(即行末没有空白分隔符)时,只会进入循环两次。 while (scin >> word)//只依据failbit,判断了四次或三次,进入循环三次,屏蔽eofbit被置位的不同时机 { cout << word << " "; } cout << endl; } return 0; } /*temp.txt文本内容 aaa aaa aaa//判断四次的情况 bbb bbb bbb//判断四次的情况 ccc ccc ccc//判断四次的情况 ddd ddd ddd//这是最后一行,后面无换行 ,属于判断三次的情况 */ #include <iostream> #include <string> #include <vector> #include <fstream> #include <sstream> using namespace std; int main() { fstream fcin("temp.txt"); string line; vector<string> vi; while (getline(fcin, line))//只依据failbit,屏蔽eofbit被置位的不同时机 { vi.push_back(line); } fcin.close(); istringstream scin;//只定义 for (auto iter = vi.begin(); iter != vi.end(); ++iter) { scin.str(*iter);//关联 string word; cout << "关联" << endl; //primer习题集答案错误,如果判断条件用eofbit判断,存在bug,在判断三次的情况(即行末没有空白分隔符)时,只会进入循环两次。 while (scin >> word)//只依据failbit,判断了四次或三次,哪种情况都会进入循环三次,屏蔽eofbit被置位的不同时机 { cout <<word << " "; } cout << endl; scin.clear();//重用同一个字符串输入流对象时需要重置它的状态,为读取下一行的单词做准备 } return 0; }
8.11练习
#if 0 //正文案例 //电话本文件,记录了每个人的一个或多个电话号码 /*temp.txt wangwu 88888888 77777777 2222222 lisi 182000000 zhangsan 19800000 12345678 */ #include <iostream> #include <string> #include <vector> #include <fstream> #include <sstream> using namespace std; struct PersonInfo { string name; vector<string> phones; }; int main() { fstream fcin("temp.txt"); vector<PersonInfo> people;//记录每个人电话信息 string line; string onephone; while (getline(fcin, line)) { istringstream record(line); // PersonInfo info; record >> info.name;//把当前人的名字记录到个人信息中 while (record >> onephone)//把当前人的所有电话号码记录到个人信息中 { info.phones.push_back(onephone); } people.push_back(info);//把当前人的个人信息存入vector中 } fcin.close(); //测试 for (auto v : people) { cout << v.name << " "; for (auto n : v.phones) { cout << n << " "; } cout << endl; } return 0; } #endif //本题要求, //本节的程序在外层while循环中定义了istringstream对象。如果record对象定义在循环之外,你需要对程序进行怎样的修改?重写程序,将record的定义移到while循环之外,验证你设想的修改方法是否正确。 //电话本文件,记录了每个人的一个或多个电话号码 /*temp.txt wangwu 88888888 77777777 2222222 lisi 182000000 zhangsan 19800000 12345678 */ #include <iostream> #include <string> #include <vector> #include <fstream> #include <sstream> using namespace std; struct PersonInfo { string name; vector<string> phones; }; int main() { fstream fcin("temp.txt"); vector<PersonInfo> people;//记录每个人电话信息 string line; string onephone; istringstream record;//修改为这个 while (getline(fcin, line)) { //istringstream scin(line); //如果在循环外定义此字符串输入流对象进行重用的话,则需要每次重置 record.str(line);//修改为这个 PersonInfo info; record >> info.name;//把当前人的名字记录到个人信息中 while (record >> onephone)//把当前人的所有电话号码记录到个人信息中 { info.phones.push_back(onephone); } people.push_back(info);//把当前人的个人信息存入vector中 record.clear();//必须重置 } fcin.close(); //测试 for (auto v : people) { cout << v.name << " "; for (auto n : v.phones) { cout << n << " "; } cout << endl; } return 0; }
8.12 练习
//我们为什么没有在PersonInfo中使用类内初始化? //因为直接类内初始化给默认值没啥意义,每个人名又不同电话号码又不同数量也不同。
8.13练习
略,不做了,只是在上一题基础上去挨个验证vector中所保存的电话号码是否失效或错误,加以提示,因为有俩函数未实现,无法测试,不做了。
8.14练习
用引用是为了提高效率,用const引用表示没有修改的意图的约定