--- GA: G-RZYLL0RZGV --- ###### tags: `大一程設-下` `東華大學` `東華大學資管系` `基本程式概念` `資管經驗分享` C++ 檔案讀寫函式庫 fstream - 下 === [TOC] ## 路徑講解 > 我知道講路徑一定一堆人不爽,覺得這很簡單。 > 但你們不知道的是有更多人不知道,這讓我更不爽XD。 > [name=Orange] 大家在上一份筆記,使用 open 函式的時候應該都會在字串裡面填入自己檔案的名字,但其實這份檔案是被存在你的電腦中的,他其實會有一個自己專屬的路徑。 比方說今天我有一隻程式,在我電腦的 D 槽內,那他可能會有像這樣的路徑。 `D:\ndhu-programming\intern\code\20211231\data.txt` `D槽底下的 ndhu-programming 資料夾底下的 intern 資料夾底下的 code 資料夾底下的 20211231 資料夾底下的 data 文字檔` 斜線你可以用 **`的`** 來翻譯,每個斜線後代表一個資料夾,而最後一個斜線後面的是你的檔案名稱,txt 是你檔案的附檔名。word 可能會是 docx,ppt 可能是 pptx,諸如此類。 而最前面的 `D:\` 代表說在 D 槽底下的甚麼東西,而斜線後是什麼就根據你的電腦摟。 ### 絕對路徑 絕對路徑就是由你親自完整地告訴電腦路徑從頭到尾由哪裡到哪裡,像這個。 `D:\ndhu-programming\intern\code\20211231\data.txt` 你可以實際去你的檔案總管,你就會相信我了。 ![](https://i.imgur.com/nNsTuvN.png) 所以你在開檔案的時候可以這樣寫。 `in.open("D:\\ndhu-programming\\intern\\code\\20211231\\data.txt");` 電腦會根據你的路徑去抓檔案。 #### 補充 - 反斜線重複 你會發現這邊的<span style="color:red">**反斜線都打了兩次**</span>,原因是因為在 C++ 裡面 `\` 符號被視為特殊字符,像 `\n` 是換行,`\t` 是 tab,所以你只打一個 `\` 的話程式會認為你 `\` 的下一個字是要處理的事情,所以你要多加一個反斜線,他就會認定你的第二個反斜線是真的反斜線。 這沒有為什麼! ### 相對路徑 既然說<span style="color:red">**相對**</span>,代表一定有一個<span style="color:red">**基準**</span>,這邊的基準是什麼呢? 就是你 main.cpp 在的資料夾 **(也就是你專案建立的資料夾)**,舉例來說我們建立了一個專案,main.cpp 在這個路徑底下。 `D:\code\test1230\main.cpp` 那你開檔案的時候可能會這樣寫。 `in.open("data.txt")`,在前一份筆記也有很多這種例子,只有寫檔名,如果你已經了解絕對路徑,那你一定會好奇這邊真正的路徑是什麼,其實就是 `D:\code\test1230\data.txt`。 而我們在 open 不用寫上全部路徑的原因是因為電腦認定這隻程式就是由 `D:\code\test1230\` 這個資料夾(專案建立的資料夾)開始。 那如果今天有一個檔案叫做 answer.txt,在當前資料夾的**上一層**,我們想存取他怎麼辦呢? 當然你可以絕對路徑。 `in.open("D:\\code\\answer.txt");` 但其實沒那麼麻煩,這就可以依靠相對路徑來完成。 #### 相對路徑的 `.` 相對路徑的點 `.` 代表的是當前路徑的資料夾,所以可寫可不寫。 `in.open(".\\data.txt");` 跟 `in.open("data.txt");`,是相同的意思。 #### 相對路徑的 `..` 兩個點代表將資料夾跳到當前專案的上一層,以下面為例。 我們知道當前的資料夾是在 `D:\code\test1230` 這個資料夾。 下面這個寫法就會從當前的資料夾跳到上一層的資料夾,我們就能夠存取 answer.txt 這個檔案。 `in.open("..\\answer.txt"); 這邊的絕對路徑其實是 D:\code\answer.txt` 那如果在上兩層有一個 info.txt 這個檔案,要存取怎麼辦呢? `in.open("..\\..\\info.txt"); 這邊的絕對路徑其實是 D:\info.txt` 這樣寫就可以摟! 為了說明更清楚,畫成樹會長這樣。 ``` D:(資料夾) |___ info.txt(檔案) |___ code(資料夾) |___ answer.txt(檔案) |___ test1230(當前專案的資料夾) |___main.cpp(在 test1230 資料夾內) ``` #### 相對路徑的好處 今天檔案可能會一起做搬移,絕對路徑很容易會改變,如果針對函式都使用絕對路徑,那當今天你的檔案搬去別的地方,全部的函式都要改路徑,那會非常麻煩。 但今天若使用相對路徑,這個問題可以被好好的解決,今天整份資料夾做搬移,檔案間的相對路徑都沒有改變,以 main.cpp 跟 answer.txt 為例,他們就是介於上層跟下層間,這用 `..` 就可以有效的處理。 ## 絕對路徑範例 ```cpp= #include <iostream> #include <fstream> using namespace std; int main(){ ifstream in; in.open("D:\\NDHU\\Programming\\test0117\\test.txt"); in.close(); return 0; } ``` ## 相對路徑範例 ```cpp= #include <iostream> #include <fstream> using namespace std; int main(){ ifstream in, in1; in.open(".\\test.txt"); in1.open("..\\answer.txt"); string a, b; in >> a; in1 >> b; in.close(); in1.close(); return 0; } ``` ## ifstream 與 ofstream 作為參數 講到作為參數,相信現在大家都能馬上想到 function 了,至於要使用 function 代表我們一定想拆分什麼功能出來,不要讓程式碼都寫在 main 裡面,提高程式利用率。 我們先看下面的範例程式。 ```cpp= void readwrite_INT_fromFile(ifstream &fin, ofstream &fout){ int value = 0; while(!fin.eof()){ fin >> value; if(!fin.fail()){ cout << value << endl; fout << value << endl; } } } int main(){ ifstream in; ofstream out; in.open("data.txt"); out.open("input.txt"); readwrite_INT_fromFile(in, out); in.close(); out.close(); return 0; } ``` 還記得在參數內我有說過,你是明確定義一個 function 的藍圖(規格),告訴電腦我們需要傳入什麼,常見的有 int、string、char、double、float。 但今天我們想要把讀資料這件事情拆分給函式來完成,第 12 行先建立資料讀入物件,然後呼叫我們設計的讀資料函式並把這個讀入串流當作參數傳進函式內。 到第一行開始執行我們的函式內容。 這邊你會問,為什麼要 call-by reference? > 幫大家快速複習,傳參考是為 actual parameter 建立一個參考變數,所以會有下面這樣的操作發生。 > `ifstream &fin = in;` 還記得 call-by reference 當初說的時候能夠讓我們操控同樣一塊記憶體,減少記憶體的使用,避免不必要空間的浪費。 代表說 in、out 這兩個參數也會需要記憶體哦,然而讀入與寫出串流其實在整份程式裡面可以只<span style="color:red">**各依靠一個物件就能夠全部完成**</span>,也就是第 12 行的 in 跟 第 13 行的 out,我們可以利用 in 開啟很多檔案,讀入很多資料,反之。 既然整個程式內只要一份,那為什麼還要多開記憶體呢? 所以這是為什麼使用 call-by reference 的原因。 <span style="color:red">**而 ifstream 或 ofstream 當參數時,一定要是 call-by reference**</span> > 小總結 > 在 C++ 內,<span style="color:red">**串流物件當作參數時務必要為參考變數**</span>,也就是一定要使用 call-by reference 來做參數傳遞。<span style="color:red">**目的是為了不浪費記憶體空間。**</span> > 沒有為什麼,底層就已經這樣寫好了。 > 避免你覺得我騙你,我貼上一張圖,**但如果看不懂的話可以跳過沒關係**。 > ![](https://i.imgur.com/X74t4S9.png) > 這部分比較複雜,後面談論運算子多載的時候,你會更有感覺為什麼要使用 call-by reference。 > [name=Orange] ### 那什麼是只要一份?? > 甚麼叫做只要一份? 我們看一下下面的範例。 ```cpp= int main(){ ifstream in; in.open("test.txt"); int a, b , c; in >> a >> b >> c; //其實下面的註解也是一樣的意思 /* in >> a; in >> b; in >> c; */ } ``` 在上面的這個小範例,註解內的內容我們可以寫成第五行一次完成,然而在註解內的方式出現三次 in,他們其實都是相同的 in 物件哦,因為我們只有宣告一個 ifstream 物件叫做 in,他<span style="color:red">**一個人**</span>負責檔案讀入的全部事務。 希望這樣你有感覺「只要一份」的意思是什麼。 > 那如果有兩個檔案,都各有五個數字,我想把第一個檔案的第一個數字跟第二個檔案的第一個數字相加,依此類推的話,那怎麼辦? > > 只有一個讀入物件的話你可以利用陣列。 ```cpp= int main(){ ifstream in; in.open("test1.txt"); int arr1[5], a; for(int i = 0; i < 5; i++){ in >> a; //從檔案讀入一個數字 arr1[i] = a;// assign 給陣列 } in.close(); int arr2[5]; in.open("test2.txt"); for(int i = 0; i < 5; i++){ in >> a; arr2[i] = a; } for(int i = 0; i < 5; i++){ cout << arr1[i] + arr2[i] << endl; } return 0; } ``` 那你可能會覺得這樣有點費功夫,能不能再多一個讀入物件,來負責讀入 test2.txt 呢? 這是絕對沒問題的。 ```cpp= int main(){ ifstream in1, in2; in1.open("test1.txt"); in2.open("test2.txt"); int a, b; for(int i = 0; i < 5; i++){ in1 >> a; in2 >> b; cout << a + b << endl; } in1.close(); in2.close(); return 0; } ``` 這樣寫相對就簡單很多,所以你說到底甚麼時候要多宣告讀入(或寫出)物件呢? <span style="color:red">**完全端看你的需求而定!!**</span> ## 補充 - 輸出入模式詳談 這一塊就是補充,大家自己可以看看下面的參考資料哦。如果你想使用補充內的內容,麻煩在自己的電腦多多測試,跑看看是不是對的,不要考試的時候亂寫一通哦。 > [參考來源請點我](http://www2.lssh.tp.edu.tw/~hlf/class-1/lang-c/c++file.htm)