C++?文件和流
- 到目前為止,目前使用最為廣泛的是?iostream?標準庫,它提供了?cin?和?cout?方法分別用于從標準輸入讀取流和向標準輸出寫入流。
- 以下將介紹從文件讀取流和向文件寫入流。這就需要用到 C++ 中另一個標準庫?fstream,它定義了三個新的數據類型:
數據類型 | 描述 |
---|---|
ofstream | 該數據類型表示輸出文件流,用于創建文件并向文件寫入信息。 |
ifstream | 該數據類型表示輸入文件流,用于從文件讀取信息。 |
fstream | 該數據類型通常表示文件流,且同時具有 ofstream 和 ifstream 兩種功能,這意味著它可以創建文件,向文件寫入信息,從文件讀取信息。 |
- 源代碼文件中包含頭文件 <iostream> 和 <fstream>
打開文件
- 在從文件讀取信息或者向文件寫入信息之前,必須先打開文件。ofstream?和?fstream?對象都可以用來打開文件進行寫操作,如果只需要打開文件進行讀操作,則使用?ifstream?對象。
- 下面是 open() 函數的標準語法,open() 函數是 fstream、ifstream 和 ofstream 對象的一個成員。
void open(const char *filename, ios::openmode mode);
-
在這里,open()?成員函數的第一參數指定要打開的文件的名稱和位置,第二個參數定義文件被打開的模式。?
模式標志 | 描述 |
---|---|
ios::app | 追加模式。所有寫入都追加到文件末尾。 |
ios::ate | 文件打開后定位到文件末尾。 |
ios::in | 打開文件用于讀取。 |
ios::out | 打開文件用于寫入。 |
ios::trunc | 如果該文件已經存在,其內容將在打開文件之前被截斷,即把文件長度設為 0。 |
- 可以把以上兩種或兩種以上的模式結合使用。例如,如果想要以寫入模式打開文件,并希望截斷文件,以防文件已存在,那么可以使用下面的語法:
ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc );
- 類似地,如果想要打開一個文件用于讀寫,可以使用下面的語法:
ifstream afile;
afile.open("file.dat", ios::out | ios::in );
關閉文件
- 當 C++ 程序終止時,它會自動關閉刷新所有流,釋放所有分配的內存,并關閉所有打開的文件。但程序員應該養成一個好習慣,在程序終止前關閉所有打開的文件。
- 下面是 close() 函數的標準語法,close() 函數是 fstream、ifstream 和 ofstream 對象的一個成員。
void close();
寫入文件
- 在 C++ 編程中,使用流插入運算符( << )向文件寫入信息,就像使用該運算符輸出信息到屏幕上一樣。唯一不同的是,在這里使用的是?ofstream?或?fstream?對象,而不是?cout?對象。
讀取文件
- 在 C++ 編程中,使用流提取運算符( >> )從文件讀取信息,就像使用該運算符從鍵盤輸入信息一樣。唯一不同的是,在這里使用的是?ifstream?或?fstream?對象,而不是?cin?對象。
讀取 & 寫入實例
- 下面的 C++ 程序以讀寫模式打開一個文件。在向文件 afile.dat 寫入用戶輸入的信息之后,程序從文件讀取信息,并將其輸出到屏幕上
#include <fstream>
#include <iostream>
using namespace std;int main ()
{char data[100];// 以寫模式打開文件ofstream outfile;outfile.open("afile.dat");cout << "Writing to the file" << endl;cout << "Enter your name: "; cin.getline(data, 100);// 向文件寫入用戶輸入的數據outfile << data << endl;cout << "Enter your age: "; cin >> data;cin.ignore();// 再次向文件寫入用戶輸入的數據outfile << data << endl;// 關閉打開的文件outfile.close();// 以讀模式打開文件ifstream infile; infile.open("afile.dat"); cout << "Reading from the file" << endl; infile >> data; // 在屏幕上寫入數據cout << data << endl;// 再次從文件讀取數據,并顯示它infile >> data; cout << data << endl; // 關閉打開的文件infile.close();return 0;
}
- ?cin 對象的附加函數,比如 getline()函數從外部讀取一行,ignore() 函數會忽略掉之前讀語句留下的多余字符
文件位置指針
- istream?和?ostream?都提供了用于重新定位文件位置指針的成員函數。這些成員函數包括關于 istream 的?seekg("seek get")和關于 ostream 的?seekp("seek put")。
- seekg 和 seekp 的參數通常是一個長整型。第二個參數可以用于指定查找方向。查找方向可以是?ios::beg(默認的,從流的開頭開始定位),也可以是?ios::cur(從流的當前位置開始定位),也可以是?ios::end(從流的末尾開始定位)。
- 文件位置指針是一個整數值,指定了從文件的起始位置到指針所在位置的字節數。下面是關于定位 "get" 文件位置指針的實例:
// 定位到 fileObject 的第 n 個字節(假設是 ios::beg)
fileObject.seekg( n );// 把文件的讀指針從 fileObject 當前位置向后移 n 個字節
fileObject.seekg( n, ios::cur );// 把文件的讀指針從 fileObject 末尾往回移 n 個字節
fileObject.seekg( n, ios::end );// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
C++?異常處理
異常是程序在執行期間產生的問題。C++ 異常是指在程序運行時發生的特殊情況,比如嘗試除以零的操作。異常提供了一種轉移程序控制權的方式。C++ 異常處理涉及到三個關鍵字:try、catch、throw。
- throw:?當問題出現時,程序會拋出一個異常。這是通過使用?throw?關鍵字來完成的。
- catch:?在您想要處理問題的地方,通過異常處理程序捕獲異常。catch?關鍵字用于捕獲異常。
- try:?try?塊中的代碼標識將被激活的特定異常。它后面通常跟著一個或多個 catch 塊。
如果有一個塊拋出一個異常,捕獲異常的方法會使用?try?和?catch?關鍵字。try 塊中放置可能拋出異常的代碼,try 塊中的代碼被稱為保護代碼。使用 try/catch 語句的語法如下所示:
try
{// 保護代碼
}catch( ExceptionName e1 )
{// catch 塊
}catch( ExceptionName e2 )
{// catch 塊
}catch( ExceptionName eN )
{// catch 塊
}
- 如果?try?塊在不同的情境下會拋出不同的異常,這個時候可以嘗試羅列多個?catch?語句,用于捕獲不同類型的異常。
拋出異常
- 可以使用?throw?語句在代碼塊中的任何地方拋出異常。throw 語句的操作數可以是任意的表達式,表達式的結果的類型決定了拋出的異常的類型。
double division(int a, int b)
{if( b == 0 ){throw "Division by zero condition!";}return (a/b);
}
捕獲異常
catch?塊跟在?try?塊后面,用于捕獲異常。您可以指定想要捕捉的異常類型,這是由 catch 關鍵字后的括號內的異常聲明決定的。
try
{// 保護代碼
}catch( ExceptionName e )
{// 處理 ExceptionName 異常的代碼
}
- 上面的代碼會捕獲一個類型為?ExceptionName?的異常。如果您想讓 catch 塊能夠處理 try 塊拋出的任何類型的異常,則必須在異常聲明的括號內使用省略號 ...,如下所示:
try
{// 保護代碼
}catch(...)
{// 能處理任何異常的代碼
}
- 由于拋出了一個類型為?const char*?的異常,因此,當捕獲該異常時,必須在 catch 塊中使用 const char*。當上面的代碼被編譯和執行時,它會產生下列結果:
Division by zero condition!
C++ 標準的異常
- C++ 提供了一系列標準的異常,定義在?<exception>?中,可以在程序中使用這些標準的異常。它們是以父子類層次結構組織起來的,如下所示:
異常 | 描述 |
---|---|
std::exception | 該異常是所有標準 C++ 異常的父類。 |
std::bad_alloc | 該異常可以通過?new?拋出。 |
std::bad_cast | 該異常可以通過?dynamic_cast?拋出。 |
std::bad_exception | 這在處理 C++ 程序中無法預期的異常時非常有用。 |
std::bad_typeid | 該異常可以通過?typeid?拋出。 |
std::logic_error | 理論上可以通過讀取代碼來檢測到的異常。 |
std::domain_error | 當使用了一個無效的數學域時,會拋出該異常。 |
std::invalid_argument | 當使用了無效的參數時,會拋出該異常。 |
std::length_error | 當創建了太長的 std::string 時,會拋出該異常。 |
std::out_of_range | 該異常可以通過方法拋出,例如 std::vector 和 std::bitset<>::operator[]()。 |
std::runtime_error | 理論上不可以通過讀取代碼來檢測到的異常。 |
std::overflow_error | 當發生數學上溢時,會拋出該異常。 |
std::range_error | 當嘗試存儲超出范圍的值時,會拋出該異常。 |
std::underflow_error | 當發生數學下溢時,會拋出該異常。 |
定義新的異常
- 可以通過繼承和重載?exception?類來定義新的異常。下面的實例演示了如何使用 std::exception 類來實現自己的異常:
#include <iostream>
#include <exception>
using namespace std;struct MyException : public exception
{const char * what () const throw (){return "C++ Exception";}
};int main()
{try{throw MyException();}catch(MyException& e){std::cout << "MyException caught" << std::endl;std::cout << e.what() << std::endl;}catch(std::exception& e){//其他的錯誤}
}
- what()?是異常類提供的一個公共方法,它已被所有子異常類重載。這將返回異常產生的原因。
C++?動態內存
了解動態內存在 C++ 中是如何工作的是成為一名合格的 C++ 程序員必不可少的。C++ 程序中的內存分為兩個部分:
- 棧:在函數內部聲明的所有變量都將占用棧內存。
- 堆:這是程序中未使用的內存,在程序運行時可用于動態分配內存。
很多時候,無法提前預知需要多少內存來存儲某個定義變量中的特定信息,所需內存的大小需要在運行時才能確定。在 C++ 中,可以使用特殊的運算符為給定類型的變量在運行時分配堆內的內存,這會返回所分配的空間地址。這種運算符即?new?運算符。如果不再需要動態分配內存空間,可以使用?delete?運算符,刪除之前由 new 運算符分配的內存。
new 和 delete 運算符
下面是使用 new 運算符來為任意的數據類型動態分配內存的通用語法:
new data-type;
- 在這里,data-type?可以是包括數組在內的任意內置的數據類型,也可以是包括類或結構在內的用戶自定義的任何數據類型。如果使用內置的數據類型,例如,可以定義一個指向 double 類型的指針,然后請求內存,該內存在執行時被分配。我們可以按照下面的語句使用?new?運算符來完成這點:
double* pvalue = NULL; // 初始化為 null 的指針
pvalue = new double; // 為變量請求內存
- 如果自由存儲區已被用完,可能無法成功分配內存。所以建議檢查 new 運算符是否返回 NULL 指針,并采取以下適當的操作:
double* pvalue = NULL;
if( !(pvalue = new double ))
{cout << "Error: out of memory." <<endl;exit(1);}
- malloc()?函數在 C 語言中就出現了,在 C++ 中仍然存在,但建議盡量不要使用 malloc() 函數。new 與 malloc() 函數相比,其主要的優點是,new 不只是分配了內存,它還創建了對象。
- 在任何時候,當某個已經動態分配內存的變量不再需要使用時,可以使用 delete 操作符釋放它所占用的內存,如下所示:
delete pvalue; // 釋放 pvalue 所指向的內存
#include <iostream>
using namespace std;int main ()
{double* pvalue = NULL; // 初始化為 null 的指針pvalue = new double; // 為變量請求內存*pvalue = 29494.99; // 在分配的地址存儲值cout << "Value of pvalue : " << *pvalue << endl;delete pvalue; // 釋放內存return 0;
}
數組的動態內存分配
- 假設要為一個字符數組(一個有 20 個字符的字符串)分配內存,可以使用上面實例中的語法來為數組動態地分配內存,如下所示:
char* pvalue = NULL; // 初始化為 null 的指針
pvalue = new char[20]; // 為變量請求內存
- 要刪除剛才創建的數組,語句如下:
delete [] pvalue; // 刪除 pvalue 所指向的數組
下面是 new 操作符的通用語法,可以為多維數組分配內存
一維數組
// 動態分配,數組長度為 m
int *array=new int [m];//釋放內存
delete [] array;
二維數組
int **array
// 假定數組第一維長度為 m, 第二維長度為 n
// 動態分配空間
array = new int *[m];
for( int i=0; i<m; i++ )
{array[i] = new int [n] ;
}
//釋放
for( int i=0; i<m; i++ )
{delete [] array[i];
}
delete [] array;
二維數組例子
#include <iostream>
using namespace std;int main()
{int **p; int i,j; //p[4][8] //開始分配4行8列的二維數據 p = new int *[4];for(i=0;i<4;i++){p[i]=new int [8];}for(i=0; i<4; i++){for(j=0; j<8; j++){p[i][j] = j*i;}} //打印數據 for(i=0; i<4; i++){for(j=0; j<8; j++) { if(j==0) cout<<endl; cout<<p[i][j]<<"\t"; }} //開始釋放申請的堆 for(i=0; i<4; i++){delete [] p[i]; }delete [] p; return 0;
}
三維數組
int ***array;
// 假定數組第一維為 m, 第二維為 n, 第三維為h
// 動態分配空間
array = new int **[m];
for( int i=0; i<m; i++ )
{array[i] = new int *[n];for( int j=0; j<n; j++ ){array[i][j] = new int [h];}
}
//釋放
for( int i=0; i<m; i++ )
{for( int j=0; j<n; j++ ){delete[] array[i][j];}delete[] array[i];
}
delete[] array;
三維數組例子
?
#include <iostream>
using namespace std;int main()
{ int i,j,k; // p[2][3][4]int ***p;p = new int **[2]; for(i=0; i<2; i++) { p[i]=new int *[3]; for(j=0; j<3; j++) p[i][j]=new int[4]; }//輸出 p[i][j][k] 三維數據for(i=0; i<2; i++) {for(j=0; j<3; j++) { for(k=0;k<4;k++){ p[i][j][k]=i+j+k;cout<<p[i][j][k]<<" ";}cout<<endl;}cout<<endl;}// 釋放內存for(i=0; i<2; i++) {for(j=0; j<3; j++) { delete [] p[i][j]; } } for(i=0; i<2; i++) { delete [] p[i]; } delete [] p; return 0;
}
對象的動態內存分配
- 對象與簡單的數據類型沒有什么不同
#include <iostream>
using namespace std;class Box
{public:Box() { cout << "調用構造函數!" <<endl; }~Box() { cout << "調用析構函數!" <<endl; }
};int main( )
{Box* myBoxArray = new Box[4];delete [] myBoxArray; // 刪除數組return 0;
}
- 如果要為一個包含四個 Box 對象的數組分配內存,構造函數將被調用 4 次,同樣地,當刪除這些對象時,析構函數也將被調用相同的次數(4次)。
- 當上面的代碼被編譯和執行時,它會產生下列結果:
調用構造函數!
調用構造函數!
調用構造函數!
調用構造函數!
調用析構函數!
調用析構函數!
調用析構函數!
調用析構函數!
C++?命名空間
- 假設這樣一種情況,當一個班上有兩個名叫 Zara 的學生時,為了明確區分它們,我們在使用名字之外,不得不使用一些額外的信息,比如他們的家庭住址,或者他們父母的名字等等。
- 同樣的情況也出現在 C++ 應用程序中。例如,您可能會寫一個名為 xyz() 的函數,在另一個可用的庫中也存在一個相同的函數 xyz()。這樣,編譯器就無法判斷您所使用的是哪一個 xyz() 函數。
- 因此,引入了命名空間這個概念,專門用于解決上面的問題,它可作為附加信息來區分不同庫中相同名稱的函數、類、變量等。使用了命名空間即定義了上下文。本質上,命名空間就是定義了一個范圍。
- 以一個計算機系統中的例子,一個文件夾(目錄)中可以包含多個文件夾,每個文件夾中不能有相同的文件名,但不同文件夾中的文件可以重名。
定義命名空間
- 命名空間的定義使用關鍵字?namespace,后跟命名空間的名稱,如下所示:
namespace namespace_name {// 代碼聲明
}
- 為了調用帶有命名空間的函數或變量,需要在前面加上命名空間的名稱,如下所示:
name::code; // code 可以是變量或函數
例子
#include <iostream>
using namespace std;// 第一個命名空間
namespace first_space{void func(){cout << "Inside first_space" << endl;}
}
// 第二個命名空間
namespace second_space{void func(){cout << "Inside second_space" << endl;}
}
int main ()
{// 調用第一個命名空間中的函數first_space::func();// 調用第二個命名空間中的函數second_space::func(); return 0;
}
using指令
- 可以使用?using namespace?指令,這樣在使用命名空間時就可以不用在前面加上命名空間的名稱。這個指令會告訴編譯器,后續的代碼將使用指定的命名空間中的名稱。
?
#include <iostream>
using namespace std;// 第一個命名空間
namespace first_space{void func(){cout << "Inside first_space" << endl;}
}
// 第二個命名空間
namespace second_space{void func(){cout << "Inside second_space" << endl;}
}
using namespace first_space;
int main ()
{// 調用第一個命名空間中的函數func();return 0;
}
- using 指令也可以用來指定命名空間中的特定項目。例如,如果您只打算使用 std 命名空間中的 cout 部分,您可以使用如下的語句:
using std::cout;
- using?指令引入的名稱遵循正常的范圍規則。名稱從使用?using?指令開始是可見的,直到該范圍結束。此時,在范圍以外定義的同名實體是隱藏的。
不連續的命名空間
- 命名空間可以定義在幾個不同的部分中,因此命名空間是由幾個單獨定義的部分組成的。一個命名空間的各個組成部分可以分散在多個文件中。
- 所以,如果命名空間中的某個組成部分需要請求定義在另一個文件中的名稱,則仍然需要聲明該名稱。下面的命名空間定義可以是定義一個新的命名空間,也可以是為已有的命名空間增加新的元素:
namespace namespace_name {// 代碼聲明
}
嵌套的命名空間
- 命名空間可以嵌套,您可以在一個命名空間中定義另一個命名空間,如下所示:
namespace namespace_name1 {// 代碼聲明namespace namespace_name2 {// 代碼聲明}
}
-
可以通過使用 :: 運算符來訪問嵌套的命名空間中的成員:
// 訪問 namespace_name2 中的成員
using namespace namespace_name1::namespace_name2;// 訪問 namespace:name1 中的成員
using namespace namespace_name1;
- 在上面的語句中,如果使用的是 namespace_name1,那么在該范圍內 namespace_name2 中的元素也是可用的,如下所示:
C++?模板
- 模板是泛型編程的基礎,泛型編程即以一種獨立于任何特定類型的方式編寫代碼。
- 模板是創建泛型類或函數的藍圖或公式。庫容器,比如迭代器和算法,都是泛型編程的例子,它們都使用了模板的概念。每個容器都有一個單一的定義,比如?向量,可以定義許多不同類型的向量,比如?vector <int>?或?vector <string>。
函數模板
template <typename type> ret-type func-name(parameter list)
{// 函數的主體
}
-
在這里,type 是函數所使用的數據類型的占位符名稱。這個名稱可以在函數定義中使用。下面是函數模板的實例,返回兩個數中的最大值:
#include <iostream>
#include <string>using namespace std;template <typename T>
inline T const& Max (T const& a, T const& b)
{ return a < b ? b:a;
}
int main ()
{int i = 39;int j = 20;cout << "Max(i, j): " << Max(i, j) << endl; double f1 = 13.5; double f2 = 20.7; cout << "Max(f1, f2): " << Max(f1, f2) << endl; string s1 = "Hello"; string s2 = "World"; cout << "Max(s1, s2): " << Max(s1, s2) << endl; return 0;
}
類模板
- 正如定義函數模板一樣,也可以定義類模板。泛型類聲明的一般形式如下所示:
template <class type> class class-name {
.
.
.
}
- 在這里,type?是占位符類型名稱,可以在類被實例化的時候進行指定。可以使用一個逗號分隔的列表來定義多個泛型數據類型。下面的實例定義了類 Stack<>,并實現了泛型方法來對元素進行入棧出棧操作:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>using namespace std;template <class T>
class Stack { private: vector<T> elems; // 元素 public: void push(T const&); // 入棧void pop(); // 出棧T top() const; // 返回棧頂元素bool empty() const{ // 如果為空則返回真。return elems.empty(); }
}; template <class T>
void Stack<T>::push (T const& elem)
{ // 追加傳入元素的副本elems.push_back(elem);
} template <class T>
void Stack<T>::pop ()
{ if (elems.empty()) { throw out_of_range("Stack<>::pop(): empty stack"); }// 刪除最后一個元素elems.pop_back();
} template <class T>
T Stack<T>::top () const
{ if (elems.empty()) { throw out_of_range("Stack<>::top(): empty stack"); }// 返回最后一個元素的副本 return elems.back();
} int main()
{ try { Stack<int> intStack; // int 類型的棧 Stack<string> stringStack; // string 類型的棧 // 操作 int 類型的棧 intStack.push(7); cout << intStack.top() <<endl; // 操作 string 類型的棧 stringStack.push("hello"); cout << stringStack.top() << std::endl; stringStack.pop(); stringStack.pop(); } catch (exception const& ex) { cerr << "Exception: " << ex.what() <<endl; return -1;}
}
?
?
?
?
?
?
?
?