boost 文件系統

第 9 章 文件系統


 該書採用 Creative Commons License 受權php


9.1. 概述

庫 Boost.Filesystem 簡化了處理文件和目錄的工做。 它提供了一個名爲 boost::filesystem::path 的類,能夠對路徑進行處理。 另外,還有多個函數用於建立目錄或驗證某個給定文件的有效性。html


9.2. 路徑

boost::filesystem::path 是 Boost.Filesystem 中的核心類,它表示路徑的信息,並提供了處理路徑的方法。ios

實際上,boost::filesystem::path 是 boost::filesystem::basic_path<std::string> 的一個 typedef。 此外還有一個 boost::filesystem::wpath 是boost::filesystem::basic_path<std::wstring> 的 typedefwindows

全部定義均位於 boost::filesystem 名字空間,定義於 boost/filesystem.hpp 中。框架

能夠經過傳入一個字符串至 boost::filesystem::path 類來構建一個路徑。函數

#include <boost/filesystem.hpp> 

int main() 
{ 
  boost::filesystem::path p1("C:\\"); 
  boost::filesystem::path p2("C:\\Windows"); 
  boost::filesystem::path p3("C:\\Program Files"); 
} 

沒有一個 boost::filesystem::path 的構造函數會實際驗證所提供路徑的有效性,或檢查給定的文件或目錄是否存在。 所以,boost::filesystem::path 甚至能夠用無心義的路徑來初始化。ui

#include <boost/filesystem.hpp> 

int main() 
{ 
  boost::filesystem::path p1("..."); 
  boost::filesystem::path p2("\\"); 
  boost::filesystem::path p3("@:"); 
} 

以上程序能夠執行的緣由是,路徑其實只是字符串而已。 boost::filesystem::path 只是處理字符串罷了;文件系統沒有被訪問到。this

boost::filesystem::path 特別提供了一些方法來以字符串方式獲取一個路徑。 有趣的是,有三種不一樣的方法。編碼

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("C:\\Windows\\System"); 
  std::cout << p.string() << std::endl; 
  std::cout << p.file_string() << std::endl; 
  std::cout << p.directory_string() << std::endl; 
} 

string() 方法返回一個所謂的可移植路徑。 換句話說,就是 Boost.Filesystem 用它本身預約義的規則來正規化給定的字符串。 在以上例子中,string() 返回 C:/Windows/System。 如你所見,Boost.Filesystem 內部使用斜槓符 / 做爲文件名與目錄名的分隔符。spa

可移植路徑的目的是在不一樣的平臺,如 Windows 或 Linux 之間,惟一地標識文件和目錄。 所以就再也不須要使用預處理器宏來根據底層的操做系統進行路徑的編碼。 構建可移植路徑的規則大多符合POSIX標準,在 Boost.Filesystem 參考手冊 給出。

請注意,boost::filesystem::path 的構造函數同時支持可移植路徑和平臺相關路徑。 在上面例子中所使用的路徑 "C:\\Windows\\System" 就不是可移植路徑,而是 Windows 專用的。 它能夠被 Boost.Filesystem 正確識別,但僅當該程序是在 Windows 操做系統下運行的時候! 當程序運行於一個 POSIX 兼容的操做系統,如 Linux 時,string() 將返回 C:\Windows\System。 由於在 Linux 中,反斜槓符 \ 並不被用做分隔符,不管是可移植格式或原生格式,Boost.Filesystem 都不會認爲它是文件和目錄的分隔符。

不少時候,都不能避免使用平臺相關路徑做爲字符串。 一個例子就是,使用操做系統函數時必需要用平臺相關的編碼。 方法 file_string() 和 directory_string() 正是爲此目的而提供的。

在上例中,這兩個方法都會返回 C:\Windows\System - 與底層操做系統無關。 在 Windows 上這個字符串是有效路徑,而在一個 Linux 系統上則既不是可移植路徑也不是平臺相關路徑,會象前面所說那樣被解析。

如下例子使用一個可移植路徑來初始化 boost::filesystem::path

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("/"); 
  std::cout << p.string() << std::endl; 
  std::cout << p.file_string() << std::endl; 
  std::cout << p.directory_string() << std::endl; 
} 

因爲 string() 返回的是一個可移植路徑,因此它與用於初始化 boost::filesystem::path 的字符串相同:/。 可是 file_string() 和 directory_string() 方法則會因底層平臺而返回不一樣的結果。 在 Windows 中,它們都返回 \,而在 Linux 中則都返回 /

你可能會奇怪爲何會有兩個不一樣的方法用來返回平臺相關路徑。 到目前爲止,在所看到的例子中,file_string() 和 directory_string() 都是返回相同的值。 可是,有些操做系統可能會返回不一樣的結果。 由於 Boost.Filesystem 的目標是支持儘量多的操做系統,因此它提供了兩個方法來適應這種狀況。 即便你可能更爲熟悉 Windows 或 POSIX 系統如 Linux,但仍是建議使用file_string() 來取出文件的路徑信息,且使用 directory_string() 取出目錄的路徑信息。 這無疑會增長代碼的可移植性。

boost::filesystem::path 提供了幾個方法來訪問一個路徑中的特定組件。

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("C:\\Windows\\System"); 
  std::cout << p.root_name() << std::endl; 
  std::cout << p.root_directory() << std::endl; 
  std::cout << p.root_path() << std::endl; 
  std::cout << p.relative_path() << std::endl; 
  std::cout << p.parent_path() << std::endl; 
  std::cout << p.filename() << std::endl; 
} 

若是在是一個 Windows 操做系統上執行,則字符串 "C:\\Windows\\System" 被解釋爲一個平臺相關的路徑信息。 所以,root_name() 返回 C:root_directory() 返回 /root_path() 返回C:/relative_path() 返回 Windows/Systemparent_path() 返回 C:/Windows, 而 filename() 返回 System

如你所見,沒有平臺相關的路徑信息被返回。 沒有一個返回值包含反斜槓 \,只有斜槓 /。 若是須要平臺相關信息,則要使用 file_string() 或 directory_string()。 爲了使用這些路徑中的單獨組件,必須建立一個類型爲 boost::filesystem::path 的新對象並相應的進行初始化。

若是以上程序在 Linux 操做系統中執行,則返回值有所不一樣。 多數方法會返回一個空字符串,除了 relative_path() 和 filename() 會返回 C:\Windows\System。 字符串 "C:\\Windows\\System" 在 Linux 中被解釋爲一個文件名,這個字符串既不是某個路徑的可移植編碼,也不是一個被 Linux 支持的平臺相關編碼。 所以,Boost.Filesystem 沒有其它選擇,只能將整個字符串解釋爲一個文件名。

Boost.Filesystem 還提供了其它方法來檢查一個路徑中是否包含某個特定子串。 這些方法是:has_root_name()has_root_directory()has_root_path()has_relative_path(),has_parent_path() 和 has_filename()。 各個方法都是返回一個 bool 類型的值。

還有兩個方法用於將一個文件名拆分爲各個組件。 它們應當僅在 has_filename() 返回 true 時使用。 不然只會返回一個空字符串,由於若是沒有文件名就沒什麼可拆分了。

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("photo.jpg"); 
  std::cout << p.stem() << std::endl; 
  std::cout << p.extension() << std::endl; 
} 

這個程序分別返回 photo 給 stem(),以及 .jpg 給 extension()

除了使用各個方法調用來訪問路徑的各個組件之外,你還能夠對組件自己進行迭代。

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("C:\\Windows\\System"); 
  for (boost::filesystem::path::iterator it = p.begin(); it != p.end(); ++it) 
    std::cout << *it << std::endl; 
} 

若是是在 Windows 上執行,則該程序將相繼輸出 C:/Windows 和 System。 在其它的操做系統如 Linux 上,輸出結果則是 C:\Windows\System

前面的例子示範了不一樣的方法來訪問路徑中的各個組件,如下例子則示範了修改路徑信息的方法。

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("C:\\"); 
  p /= "Windows\\System"; 
  std::cout << p.string() << std::endl; 
} 

經過使用重載的 operator/=() 操做符,這個例子將一個路徑添加到另外一個之上。 在 Windows 中,該程序將輸出 C:\Windows\System。 在 Linux 中,輸出將會是 C:\/Windows\System,由於斜槓符 / 是文件與目錄的分隔符。 這也是重載 operator/=() 操做符的緣由:畢竟,斜槓是這個方法名的一個部分。

除了 operator/=(),Boost.Filesystem 只提供了 remove_filename() 和 replace_extension() 方法來修改路徑信息。


9.3. 文件與目錄

boost::filesystem::path 的各個方法內部其實只是對字符串進行處理。 它們能夠用來訪問一個路徑的各個組件、相互添加路徑等等。

爲了處理硬盤上的物理文件和目錄,提供了幾個獨立的函數。 這些函數須要一個或多個 boost::filesystem::path 類型的參數,而且在其內部會調用操做系統功能來處理這些文件或目錄。

在介紹各個函數以前,很重要的一點是要弄明白出現錯誤時會發生什麼。 全部要在內部訪問操做系統功能的函數都有可能失敗。 在失敗的狀況下,將拋出一個類型爲boost::filesystem::filesystem_error 的異常。 這個類是派生自 boost::system::system_error 的,所以適用於 Boost.System 框架。

除了繼承自父類 boost::system::system_error 的 what() 和 code() 方法之外,還有另外兩個方法:path1() 和 path2()。 它們均返回一個類型爲 boost::filesystem::path 的對象,所以在發生錯誤時能夠很容易地肯定路徑信息 - 即便是對那些須要兩個 boost::filesystem::path 參數的函數。

多數函數存在兩個變體:在失敗時,一個會拋出類型爲 boost::filesystem::filesystem_error 的異常,而另外一個則返回類型爲 boost::system::error_code 的對象。 對於後者,須要對返回值進行明確的檢查以肯定是否出錯。

如下例子介紹了一個函數,它能夠查詢一個文件或目錄的狀態。

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("C:\\"); 
  try 
  { 
    boost::filesystem::file_status s = boost::filesystem::status(p); 
    std::cout << boost::filesystem::is_directory(s) << std::endl; 
  } 
  catch (boost::filesystem::filesystem_error &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

boost::filesystem::status() 返回一個 boost::filesystem::file_status 類型的對象,該對象能夠被傳遞給其它輔助函數來評估。 例如,若是查詢的是一個目錄的狀態,則boost::filesystem::is_directory() 將返回 true。 除了 boost::filesystem::is_directory(),還有其它函數,如 boost::filesystem::is_regular_file()boost::filesystem::is_symlink()和 boost::filesystem::exists(),它們都會返回一個 bool 類型的值。

除了 boost::filesystem::status(),另外一個名爲 boost::filesystem::symlink_status() 的函數可用於查詢一個符號連接的狀態。 在此狀況下,實際上查詢的是符號連接所指向的文件的狀態。在 Windows 中,符號連接以 lnk 文件擴展名識別。

另有一組函數可用於查詢文件和目錄的屬性。

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("C:\\Windows\\win.ini"); 
  try 
  { 
    std::cout << boost::filesystem::file_size(p) << std::endl; 
  } 
  catch (boost::filesystem::filesystem_error &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

函數 boost::filesystem::file_size() 以字節數返回一個文件的大小。

#include <boost/filesystem.hpp> 
#include <iostream> 
#include <ctime> 

int main() 
{ 
  boost::filesystem::path p("C:\\Windows\\win.ini"); 
  try 
  { 
    std::time_t t = boost::filesystem::last_write_time(p); 
    std::cout << std::ctime(&t) << std::endl; 
  } 
  catch (boost::filesystem::filesystem_error &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

要得到一個文件最後被修改的時間,可以使用 boost::filesystem::last_write_time()

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("C:\\"); 
  try 
  { 
    boost::filesystem::space_info s = boost::filesystem::space(p); 
    std::cout << s.capacity << std::endl; 
    std::cout << s.free << std::endl; 
    std::cout << s.available << std::endl; 
  } 
  catch (boost::filesystem::filesystem_error &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

boost::filesystem::space() 用於取回磁盤的總空間和剩餘空間。 它返回一個 boost::filesystem::space_info 類型的對象,其中定義了三個公有屬性:capacityfree 和 available。 這三個屬性的類型均爲 boost::uintmax_t,該類型定義於 Boost.Integer 庫,一般是 unsigned long long 的 typedef。 磁盤空間是以字節數來計算的。

目前所看到的函數都不會觸及文件和目錄自己,不過有另外幾個函數能夠用於建立、更名或刪除文件和目錄。

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("C:\\Test"); 
  try 
  { 
    if (boost::filesystem::create_directory(p)) 
    { 
      boost::filesystem::rename(p, "C:\\Test2"); 
      boost::filesystem::remove("C:\\Test2"); 
    } 
  } 
  catch (boost::filesystem::filesystem_error &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

以上例子應該是自解釋的。 仔細察看,能夠看到傳遞給各個函數的不必定是 boost::filesystem::path 類型的對象,也能夠是一個簡單的字符串。 這是能夠的,由於 boost::filesystem::path提供了一個非顯式的構造函數,能夠從簡單的字符串轉換爲 boost::filesystem::path 類型的對象。 這實際上簡化了 Boost.Filesystem 的使用,由於能夠無須顯式建立一個對象。

還有其它的函數,如 create_symlink() 用於建立符號連接,以及 copy_file() 用於複製文件或目錄。

如下例子中介紹了一個函數,基於一個文件名或一小節路徑來建立一個絕對路徑。

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  try 
  { 
    std::cout << boost::filesystem::complete("photo.jpg") << std::endl; 
  } 
  catch (boost::filesystem::filesystem_error &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

輸出哪一個路徑是由該程序運行時所處的路徑決定的。 例如,若是該例子從 C:\ 運行,輸出將是 C:/photo.jpg

請再次留意斜槓符 /! 若是想獲得一個平臺相關的路徑,則須要初始化一個 boost::filesystem::path 類型的對象,且必須調用 file_string()

要取出一個相對於其它目錄的絕對路徑,可將第二個參數傳遞給 boost::filesystem::complete()

#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  try 
  { 
    std::cout << boost::filesystem::complete("photo.jpg", "D:\\") << std::endl; 
  } 
  catch (boost::filesystem::filesystem_error &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

如今,該程序顯示的是 D:/photo.jpg

最後,還有一個輔助函數用於取出當前工做目錄,以下例所示。

#include <windows.h> 
#include <boost/filesystem.hpp> 
#include <iostream> 

int main() 
{ 
  try 
  { 
    std::cout << boost::filesystem::current_path() << std::endl; 
    SetCurrentDirectory("C:\\"); 
    std::cout << boost::filesystem::current_path() << std::endl; 
  } 
  catch (boost::filesystem::filesystem_error &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

以上程序只能在 Windows 中執行,這是 SetCurrentDirectory() 函數的緣由。 這個函數更換了當前工做目錄,所以對 boost::filesystem::current_path() 的兩次調用將返回不一樣的結果。

函數 boost::filesystem::initial_path() 用於返回應用程序開始執行時所處的目錄。 可是,這個函數取決於操做系統的支持,所以若是須要可移植性,建議不要使用。 在這種狀況下,Boost.Filesystem 文檔中建議的方法是,能夠在程序開始時保存 boost::filesystem::current_path() 的返回值,以備後用。


9.4. 文件流

C++ 標準在 fstream 頭文件中定義了幾個文件流。 這些流不能接受 boost::filesystem::path 類型的參數。 因爲 Boost.Filesystem 庫頗有可能被包含在 C++ 標準的 Technical Report 2 中,因此這些文件流將經過相應的構造函數來進行擴展。 爲了當前可讓文件流與類型爲 boost::filesystem::path 的路徑信息一塊兒工做,可使用頭文件 boost/filesystem/fstream.hpp。 它提供了對文件流所需的擴展,這些都是基於 Technical Report 2 即將加入 C++ 標準中的。

#include <boost/filesystem/fstream.hpp> 
#include <iostream> 

int main() 
{ 
  boost::filesystem::path p("test.txt"); 
  boost::filesystem::ofstream ofs(p); 
  ofs << "Hello, world!" << std::endl; 
} 

不只是構造函數,還有 open() 方法也須要重載,以接受類型爲 boost::filesystem::path 的參數。


9.5. 練習

You can buy solutions to all exercises in this book as a ZIP file.

  1. 建立一個程序,該程序爲位於應用程序當前工做目錄的上一層目錄中的一個名爲 data.txt 的文件建立一個絕對路徑。 例如,若是該程序從 C:\Program Files\Test 執行,則應顯示C:\Program Files\data.txt

相關文章
相關標籤/搜索