C++ 快速入門筆記:進階程式設計
C++入門筆記:高階程式設計
檔案和流
-
開啟檔案
void open (const char *filename, ios::openmode mode);
- ios::app 追加模式。所有寫入都追加到檔案末尾
- ios::ate 檔案開啟後定位到檔案末尾
- ios::in 開啟檔案用於讀取
- ios::out 開啟檔案用於寫入
- ios::trunc 如果該檔案已經存在,其內容將在開啟檔案之前被截斷,即把檔案長度設為 0。
-
關閉檔案
void close();
-
寫入檔案
- 使用流插入運算子 ( << ),向 ofstream / fstream 流中寫入資訊
-
讀取檔案
- 使用流提取運算子 ( >> ),向 ifstream / fstream 流中寫入資訊
-
檔案讀寫例項
#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
-
檔案位置指標
-
stream 和 ostream 都提供了用於重新定位檔案位置指標的成員函式。
- istream 的 seekg ("seek & get")
- ostream 的 seekp ("seek & put")
// 定位到 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 );
-
異常處理
-
try / catch / throw
- try: try 塊中的程式碼標識將被啟用的特定異常。它後面通常跟著一個或多個 catch 塊。
- catch: 在您想要處理問題的地方,通過異常處理程式捕獲異常。catch 關鍵字用於捕獲異常。
- throw: 當問題出現時,程式會丟擲一個異常。這是通過使用 throw 關鍵字來完成的。
-
C++ 標準的異常
-
定義新的異常
- 通過繼承或過載 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) { //其他的錯誤 } }
- 通過繼承或過載 exception 類來定義新的異常
動態記憶體
-
C++ 程式中的記憶體分為兩個部分
- 棧:在函式內部宣告的所有變數都將佔用棧記憶體。
- 堆:這是程式中未使用的記憶體,在程式執行時可用於動態分配記憶體。
可以使用 new 運算子為給定型別的變數在執行時分配堆內的記憶體,這會返回所分配的空間地址。
不需要動態分配記憶體時,可以使用 delete 運算子,刪除之前由 new 運算子分配的記憶體。
-
new 和 delete 運算子
-
使用 new 運算子來為任意的資料型別動態分配記憶體
// new data-type; // 如果自由儲存區已被用完,可能無法成功分配記憶體。所以建議檢查 new 運算子是否返回 NULL 指標。 double* pvalue = NULL; if( !(pvalue = new double )) { cout << "Error: out of memory." <<endl; exit(1); }
-
使用 delete 操作符釋放它所佔用的記憶體
#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; }
-
-
陣列的動態記憶體分配
int ROW = 2; int COL = 3; double **pvalue = new double* [ROW]; // 為行分配記憶體 // 為列分配記憶體 for(int i = 0; i < COL; i++) { pvalue[i] = new double[COL]; } for(int i = 0; i < COL; i++) { delete[] pvalue[i]; } delete [] pvalue;
-
物件的動態記憶體分配
#include <iostream> using namespace std; class Box { public: Box() { cout << "呼叫建構函式!" <<endl; } ~Box() { cout << "呼叫解構函式!" <<endl; } }; int main( ) { Box* myBoxArray = new Box[4]; delete [] myBoxArray; // Delete array return 0; }
名稱空間
名稱空間可作為附加資訊來區分不同庫中相同名稱的函式、類、變數等。使用了名稱空間即定義了上下文。本質上,名稱空間就是定義了一個範圍。
-
定義名稱空間
#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 指令
#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; }
#include <iostream> using std::cout; int main () { cout << "std::endl is used with std!" << std::endl; return 0; }
-
不連續的名稱空間
- 名稱空間可以定義在幾個不同的部分中,因此名稱空間是由幾個單獨定義的部分組成的。一個名稱空間的各個組成部分可以分散在多個檔案中。
-
巢狀的名稱空間
#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::second_space; int main () { // 呼叫第二個名稱空間中的函式 func(); return 0; }
模板
模板是泛型程式設計的基礎,泛型程式設計即以一種獨立於任何特定型別的方式編寫程式碼。
模板是建立泛型類或函式的藍圖或公式。庫容器,比如迭代器和演算法,都是泛型程式設計的例子,它們都使用了模板的概念。每個容器都有一個單一的定義,比如 向量,我們可以定義許多不同型別的向量,比如 vector <int> 或 vector <string>。
-
函式模板
template <class 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 是佔位符型別名稱,可以在類被例項化的時候進行指定。您可以使用一個逗號分隔的列表來定義多個泛型資料型別。
#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; } }
前處理器
-
#define 預處理
-
define 預處理指令用於建立符號常量。該符號常量通常稱為巨集。
#define macro-name replacement-text
-
-
函式巨集
-
使用 #define 來定義一個帶有引數的巨集:
#define MIN(a,b) (((a)<(b)) ? a : b)
-
-
條件編譯
有幾個指令可以用來有選擇地對部分程式原始碼進行編譯。這個過程被稱為條件編譯。
-
ifdef & endif
#ifndef NULL #define NULL 0 #endif
-
DEBUG 模式
#ifdef DEBUG cerr <<"Variable x = " << x << endl; #endif
-
可以使用 #if 0 語句註釋掉程式的一部分。
#if 0 不進行編譯的程式碼 #endif
-
# 和 ## 運算子
-
運算子會把 replacement-text 令牌轉換為用引號引起來的字串。
#include <iostream> using namespace std; #define MKSTR( x ) #x int main () { cout << MKSTR(HELLO C++) << endl; return 0; }
-
運算子用於連線兩個令牌。
#include <iostream> using namespace std; #define concat(a, b) a ## b int main() { int xy = 100; cout << concat(x, y); return 0; }
-
-
預定義巨集
巨集 描述 _LINE_ 這會在程式編譯時包含當前行號。 _FILE_ 這會在程式編譯時包含當前檔名。 _DATE_ 這會包含一個形式為 month/day/year 的字串,它表示把原始檔轉換為目的碼的日期。 _TIME_ 這會包含一個形式為 hour:minute:second 的字串,它表示程式被編譯的時間。
訊號處理
訊號是由作業系統傳給程序的中斷,會提早終止一個程式。在 UNIX、LINUX、Mac OS X 或 Windows 系統上,可以通過按 Ctrl+C 產生中斷。
-
可捕獲的訊號表(定義在 <csignal> 中)
訊號 描述 SIGABRT 程式的異常終止,如呼叫 abort。 SIGFPE 錯誤的算術運算,比如除以零或導致溢位的操作。 SIGILL 檢測非法指令。 SIGINT 接收到互動注意訊號。 SIGSEGV 非法訪問記憶體。 SIGTERM 傳送到程式的終止請求。 -
signal() 函式
-
用來捕獲突發事件
void (*signal (int sig, void (*func)(int))(int);
接收兩個引數:第一個引數是一個整數,代表訊號編號;第二個引數是一個指向訊號處理函式的指標。
-
無論要在程式中捕獲什麼訊號,都必須使用 singal 函式來註冊訊號,並將其與訊號處理程式相關聯。
#include <iostream> #include <csignal> using namespace std; void signalHandler( int signum ) { cout << "Interrupt signal (" << signum << ") received.\n"; // 清理並關閉 // 終止程式 exit(signum); } int main () { // 註冊訊號 SIGINT 和訊號處理程式 signal(SIGINT, signalHandler); while(1){ cout << "Going to sleep...." << endl; sleep(1); } return 0; }
-
-
raise() 函式
-
使用函式 raise() 生成訊號。
int raise (signal sig);
-
sig 是要傳送的訊號編號,這些訊號包括:SIGINT SIGABRT SIGFPE SIGILL SIGSEGV SIGTERM SIGHUP
#include <iostream> #include <csignal> using namespace std; void signalHandler( int signum ) { cout << "Interrupt signal (" << signum << ") received.\n"; // 清理並關閉 // 終止程式 exit(signum); } int main () { int i = 0; // 註冊訊號 SIGINT 和訊號處理程式 signal(SIGINT, signalHandler); while(++i){ cout << "Going to sleep...." << endl; if( i == 3 ){ raise( SIGINT); } sleep(1); } return 0; }
-