C++ Primer 筆記+習題解答(八)

今天是第八篇筆記,主要總結的內容是C++標準庫中的第一部分之IO庫。最近幾天因爲一些環境干擾,更新的速度着實變慢。其實對小白來說,學習過程中比較可怕的一件事就是信息負載,故今日貼在博文首頁,警醒自己。大致估計下日程安排。從2.4號起到除夕2.18號,共計14天。C++ Primer一共十八章,算上附錄是19個章節。預計大年初二左右結束這本書的學習。節奏應該是一天看書,一天寫博文總結。進入正題。

若存在錯誤 請指正 謝謝

0.引言:

  • 1.C++ 不存在直接處理輸入輸出的機制,而是轉交給標準庫中的類來處理IO。C++的IO機制支持從各種設備讀寫數據。這個地方的設備要好好理解下。設備可以指的是控制檯窗口,命名文件,內存IO。

  • 2.IO類型定義了內置數據類型的讀寫操作,不然形如這樣的語句:

<span style="font-size:18px;">int var=0;
cin>>var;
cout<<var;</span>
我剛學C++時候就特別好奇爲什麼可以這個樣子搞,當時眼界閱歷有限,雖然翻了不少書,但是大多數教材都是閉口不提標準庫,對我而言着實坑爹。
我們已經接觸到IO設施:
<span style="font-size:18px;">cout / cin /cerr />> /<< /getline /iostream </span>
  • 3.下面會介紹一些新知識。

目前我們使用的IO類型都是關聯到控制檯窗口的,所以你會在屏幕上看見輸入輸出。但是程序的需求絕不僅限於此。

相關頭文件:

<span style="font-size:18px;">iostream/ ostream /istream 
fstream / ofstream /ifstream
sstream / istringstream /ostringstream</span>
其中C++爲了支持寬字符類型,又定義了對應的寬字符版本類型,比如你可能看見如下的:
<span style="font-size:18px;">wostream
wcin
wcout
...</span>
無需奇怪,兩個版本之前用法無差異。
由於IO類型之間存在繼承關係,所以把繼承對象當作基類對象使用。

1.IO對象無拷貝或賦值:

  • 1.由於無法拷貝和賦值,因此不能將形參和返回類型設爲流類型,而是要設置成引用類型。因爲讀寫流會改變流的狀態,所以IO對象絕不會是const的。

  • 2.相關狀態量和函數:

<span style="font-size:18px;color:#330099;">iostate/goodbit/failbit/eofbit/badbit
上面的量是一些狀態量,其iostate總合。
相關函數:
eof() /fail()/good()/bad()/clear()/clear(flags)/setstate(flags)/rdstate()</span>
具體的函數意思翻書或者谷歌就知曉了。

在使用對象的時候,最好檢測下流的狀態,比如我們常見的:

<span style="font-size:18px;color:#330099;">while(cin>>word)</span>
我們已經學過運算符了,>>運算符返回的cin對象,所以實際檢測的是對象的狀態。

我們講流(對象)作爲條件使用,只能知曉其是否出錯,但是一旦出錯我們不知道具體錯誤的,這個時候就可以通過函數檢測。

<span style="font-size:18px;color:#330099;">不可恢復的錯誤:badbit 被置位。
可修復的錯誤:failbit  被置位。
</span>
當遇到文件結束符的時候,failbit和eofbit會被置位。一旦eofbit/failbit/badbit 任何一個被置位了,檢測流狀態得到布爾值是false。
  • 3.緩衝相關:

每個流都管理一個緩衝區,用了保存數據。如下的行爲會導致緩衝刷新:

1.程序正常結束

2.緩衝區滿。

3.輸出控制符,如endl/ends/flush等。

4.在每個輸出操作之後用unitbuf設置流內部狀態。

5.流之間的相互關聯。

相關解釋:

1.刷新輸出緩衝區:

<span style="font-size:18px;color:#330099;">cout<<endl; 刷新緩衝並且換行。
cout<flush; 刷新緩衝。
cout<<ends; 附加一個空字符然後刷新緩衝區。</span>

2.unitbuf操縱符:

每次輸出操作之後都會刷新緩衝區,行爲是類似flush,不會有附加內容。一般來說unitbuf同cerr關係比較近,所以cerr是立即輸出不緩衝的。5e

<span style="font-size:18px;color:#330099;">cout<<unitbuf;
//每次輸出後都會刷新緩衝。
恢復正常緩衝刷新機制:
cout<<nonunitbuf;</span>

3.緩衝去的一些補充知識,我就做個搬運工了哈。

緩衝去相關知識鏈接

其中注意到一點,緩衝區的幾個分類:

全緩衝/行緩衝/無緩衝。具體內容可以翻看我搬運的博文,而且他們總結的比我好多了。

  • 4.關聯輸入流和輸出流:

執行讀取操作會導致輸入緩衝區被刷新,也就是cin會刷新cout.

相關函數:

1.無形參tie函數:返回指向調用函數對象所關聯對象的指針,如調用函數的對象不不存在關聯,那麼返回的是一個空指針。

<span style="font-size:18px;color:#330099;">cin.tie();</span>
<span style="font-size:18px;color:#330099;">正常情況下,cin和cout關聯,所以會返回一個指向cout的指針。</span>

2.有參數tie函數:將調用的對象同參數的對象關聯起來。

<span style="font-size:18px;color:#330099;">cin.tie(&cerr);</span>
<span style="font-size:18px;color:#330099;">將cin關聯到cerr.不被推薦的做法,因爲cin應該同cout關聯。</span>

tips:關聯之間存在一種類似函數的關係,可以多對一,不可以一對多。意思就是多個對象可以關聯到同一個ostream對象,但是一個對象不能被關聯到多個ostream對象。

2.文件輸入輸出:

由於繼承關係的存在,前面介紹的一些狀態量和函數都是可以繼續使用的,補充一下新增的函數:

<span style="font-size:18px;">open/close/is_open</span>
<span style="font-size:18px;color:#330099;">三個函數是新增的,具體含義就不寫了。</span>
  • 1.ifstream in(file) file是文件名,可以是string 字符串或者c風格字符串。file是c風格字符串時記得一定要以空字符結尾。

  • 2.可以先定義一個對象,然後調用open函數同具體文件綁定起來。如果open失敗,failbit會被置位,儘量設置檢測措施。

  • 3.當一個對象被銷燬時,會自動調用其close函數,斬斷之間的關聯。

  • 4.文件模式:

  • in/out/app/ate/trunc/binary

  • 5.以寫模式打開的文件會丟失已有的數據:

  • 解決措施是以app或着in模式打開文件。

  • ofstream out(file)ofstream out(file,ofstream::out)//丟失數據ofstream out(file,ofstream::out|ofstream::app)

3.string 流:

  • 三個頭文件:

<span style="font-size:18px;">istringstream/ostingstream/stringstream</span>
  • 新增操作:

<span style="font-size:18px;">strm.str()//返回strm綁定的字符串拷貝。
strm.str(s)//把s拷貝到stream中。</span>

4.總結:

  • iostream 處理控制檯IO;

  • fstream 處理命名文件IO;

  • sstream 處理string IO .

5.習題解答:

8.1

<span style="font-size:18px;">#include <iostream>
using namespace std;
istream& read(istream& cin){
	int var = 0;
	while (cin>>var){
		cout << var << " ";
	}
	cout << "Test the stream before clearing " << endl;
	if (cin)
		cout << "The stream is valid " << endl;
	else
		cout << "The stream is not valid " << endl;
	cout << "Test the stream after clearing " << endl;
	cin.clear();
	if (cin)
		cout << "The stream is valid " << endl;
	else
		cout << "The stream is not valid " << endl;
	return cin;
}
int main(){
	read(cin);
	system("pause");
	return 0;
}</span>
8.2

參見8.1兩次測試。

8.3

<span style="font-size:18px;">遇到錯誤的輸入,比如i是int型的,你輸入一個字符,那麼流的狀態就失效。
或者當你鍵入文件結束標識的時候,流的狀態也會失效。
流的狀態爲假後,循環就會終止。一般來說eofbit /badbit/failbit 三者任何一個被置位都會導致循環結束。
</span>
8.4
<span style="font-size:18px;">#include <iostream>
#include <fstream>
#include <vector>
#include <string>
using namespace std;
int main(){
	ofstream out;
	out.open("test.txt");
	out << "hello world !!!"<<endl;
	out << "You are so beautiful " << endl;
	out.close();
	ifstream in("test.txt");
	string word;
	vector<string> svec;
	while (!in.eof()){
		getline(in, word);
		svec.push_back(word);
	}
	for (auto x : svec)
		cout << x << " ";
	cout << endl;
	system("pause");
	return 0;
}</span>
8.5
<span style="font-size:18px;">#include <iostream>
#include <fstream>
#include <vector>
#include <string>
using namespace std;
int main(){
	ofstream out;
	out.open("test.txt");
	out << "hello world !!!"<<endl;
	out << "You are so beautiful " << endl;
	out.close();
	ifstream in("test.txt");
	string word;
	vector<string> svec;
	while (!in.eof()){
		//getline(in, word);
		in >> word;//可以上下對比下。
		svec.push_back(word);
	}
	for (auto x : svec)
		cout << x;
	cout << endl;
	system("pause");
	return 0;
}</span>

8.6 8.7 8.8

具體的答案就不寫了,我寫一下如何從命令行編譯運行程序以及如何向命令行傳入參數。我看的書都直接一筆帶過,導致我根本不會從命令行編譯程序。這個東西就是很基礎的知識,但是寫書的人都默認我們會了,所以正計算機三觀太有必要了。

如何從命令行編譯運行程序以及向main函數傳遞參數。
這個方法適用於Visual Studio 2013,其餘平臺未測試過。
首先在vs 2013 中找到工具一欄,點開可以發現倒數幾行有一個是 Visual Studion 命令提示。
點擊它我們會發現彈出一個CMD命令行窗口。
接下來是先創建一個文件或者打開一個已經存在的文件。注意一定要加上notepad 關鍵字。格式是: notepad test.cpp
其中test可以換成自己想要建立的程序名。接着會彈出一個窗口,如果test未創建過,那麼會提示你建立一個記事本文件。
你可以在記事本文件裏面輸入自己的源代碼,然後點擊文件菜單,保存,退出。
接下來就是編譯階段:
首先輸入:
cl /EHsc test.cpp
然後你會在命令行窗口看見生成的可執行文件。
這個時候如果你想要向main函數傳遞參數,那麼輸入:
test 1 2 3 //後面的1 2 3 是我們傳遞給main的參數。你可以輸出argc測試下。應該輸出數字4.argv[0]指的是程序的名字。
如果你只是想單詞的執行程序,那麼直接輸入:
test.exe 即可。
ps:像main函數傳入參數的前提是你的main函數要有形參列表。
暫時介紹到這個地方,我也是初次接觸。感興趣的話可以直接搜索關鍵字,已經有前輩們替我們總結過了。

8.9

#include <iostream>
#include <string>
#include<sstream>
using namespace std;
istream& read(istream& cin){
	string var;
	while (cin>>var){
		cout << var << " ";
	}
	cout << endl;
	cout << "Test the stream before clearing " << endl;
	if (cin)
		cout << "The stream is valid " << endl;
	else
		cout << "The stream is not valid " << endl;
	cout << "Test the stream after clearing " << endl;
	cin.clear();
	if (cin)
		cout << "The stream is valid " << endl;
	else
		cout << "The stream is not valid " << endl;
	return cin;
}
int main(){
	string s = "Hello World ";
	istringstream is(s);
	read(is);
	system("pause");
	return 0;
}
//這個地方就體現了繼承關係。我的形參是istream&類型,但是我傳遞的是一個isstringstream類型。
正好符號上面我們提到的子類當作父類用。
8.10

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
using namespace std;
int main(){
	ofstream fout("temp.txt");  //打開一個文件,寫點東西進去。
	fout << "Hello Word " << endl;
	fout << "You are so beautiful " << endl;
	fout.close();//關閉這個文件。並且用讀的方式再次打開,寫的方式再次打開會丟失數據。
	ifstream fin("temp.txt");
	string line;
	vector<string> svec;
	while (!fin.eof()){
		getline(fin, line);
		svec.push_back(line); //按行存進vector 中。
	}
	string word;
	for (auto x : svec){
		istringstream istirngin(x);
		while (istirngin >> word)
			cout << word<<" ";
		cout << endl;
	}
	cout << endl;
	system("pause");
	return 0;
}
8.11

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
using namespace std;
int main(){
	ofstream fout("temp.txt");  //打開一個文件,寫點東西進去。
	fout << "You are so beautiful " << endl;
	fout << "Hello Word " << endl;
	fout.close();//關閉這個文件。並且用讀的方式再次打開,寫的方式再次打開會丟失數據。
	ifstream fin("temp.txt");
	string line;
	vector<string> svec;
	while (!fin.eof()){
		getline(fin, line);
		svec.push_back(line); //按行存進vector 中。
	}
	string word;
	istringstream istirngin;  //在體外定義對象。
	for (auto x:svec){
		istirngin.str(x);  //體內採用自己函數進行綁定。
		while (istirngin >> word)
			cout << word;
		cout << endl;
		istirngin.clear(); //因爲用while循環讀取,所以最後eofbit被置位。故要清空流,我一開始沒注意到這個問題。
	}
	cout << endl;
	system("pause");
	return 0;
}
體外的寫法可以參考上面的。記得復位流的狀態。

8.12

因爲string同vector<stirng>可以順利地被合成的默認構造函數初始化,無須多此一舉。
8.13

#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <fstream>
using namespace std;
struct PersonInfo{
	string name;
	vector<string> phone;
};
//定義的結構體用了保存員工信息。
int main(){
	string line, word;
	vector<PersonInfo> People;
	ofstream fout("information");//因爲並沒有寫好的信息文件,所以只能在程序當成寫。
	fout << "John 1234567 2345678" << endl;
	fout << "Max 6789090 12343890" << endl;
	fout.close();
	ifstream fin("information");
	while (!fin.eof()){
		getline(fin, line);
		PersonInfo temp;
		istringstream istringin(line);//同line 綁定到一起。
		istringin >>temp.name;//保存姓名。
		while (istringin>>word){
			temp.phone.push_back(word);
		}
		People.push_back(temp);
	}
	system("pause");
	return 0;
}
8.14
避免了拷貝同時也防止了修改。


後記:

    昨天晚上本打算一口氣寫玩的,但是等到寫的時候突然泄氣了,一股自卑感油然而生,心裏總是想着你寫不出來的,放棄吧。所以當時我選擇了休息,第二天繼續寫,果然今天的狀態好了很多。

End



發佈了53 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章