模板類string的設計屬於底層,其中運用到了很多C++的程式設計技巧,比如模板、迭代器、友元、函數和運運算元過載、內聯等等,為了便於後續理解string類,這裡先對涉及到的概念做個簡單的介紹。C++基礎比較紮實的童鞋可以直接跳到第三節。
typedef char* PCHAR; PCHAR pa, pb; // 同時宣告兩個char型別的指標pa和pb char* pa, pb; // 宣告一個指標(pa)和一個char變數(pb) // 下邊的宣告也是建立兩個char型別的指標。但相對沒有typedef的形式直觀,尤其在需要大量指標的地方 char *pa, *pb;
順便說下,*運運算元兩邊的空格是可選的,在哪裡新增空格,對於編譯器來說沒有任何區別。
char *pa; // 強調*pa是一個char型別的值,C中多用這種格式。 char* pa; // 強調char*是一種型別——指向char的指標。C++中多用此種格式。另外在C++中char*是一種複合型別。
在舊的C程式碼中,宣告struct新物件時,必須要帶上struct,形式為:struct 結構名 物件名。
1 // 定義 2 struct StudentStruct 3 { 4 int ID; 5 string name; 6 }; 7 // C宣告StudentStruct型別物件 8 struct StudentStruct s1;
使用typedef來定義結構體StrudentStruct的別名為Student,宣告的時候就可以少寫一個struct,尤其在宣告多個struct物件時更加簡潔直觀。如下:
1 // 定義 2 typedef struct StudentStruct 3 { 4 int ID; 5 string name; 6 }Student; 7 // C宣告StudentStruct型別物件s1和s2 8 Student s1, s2;
而在C++中,宣告struct物件時本來就不需要寫struct,其形式為:結構名 物件名。
// C++宣告StudentStruct型別物件s1和s2 StudentStruct s1,s2;
所以,在C++中,typedef的作用並不大。瞭解他便於我們閱讀舊程式碼。
比如定義一個REAL的浮點型別,在目標平臺一上,讓它表示最高精度的型別為:
typedef long double REAL;
在不支援long double的平臺二上,改為:
typedef double REAL;
在連double都不支援的平臺三上,改為:
typedef float REAL;
也就是說,在跨平臺時,只要改下typedef本身就行,不要對其他原始碼做任何修改。
標準庫中廣泛使用了這個技巧,比如size_t、intptr_t等
1 // Definitions of common types 2 #ifdef _WIN64 3 typedef unsigned __int64 size_t; 4 typedef __int64 ptrdiff_t; 5 typedef __int64 intptr_t; 6 #else 7 typedef unsigned int size_t; 8 typedef int ptrdiff_t; 9 typedef int intptr_t; 10 #endif
為複雜的宣告定義一個新的簡單的別名
在閱讀程式碼的過程中,我們經常會遇到一些複雜的宣告和定義,例如:
1 // 理解下邊這種複雜宣告可用「右左法則」: 2 // 從變數名看起,先往右,再往左,碰到一個圓括號就調轉閱讀的方向;括號內分析完就跳出括號,還是按先右後左的順序,如此迴圈,直到整個宣告分析完。 3 4 // 例1 5 void* (*(*a)(int))[10]; 6 // 1、找到變數名a,往右看是圓括號,調轉方向往左看到*號,說明a是一個指標; 7 // 2、跳出內層圓括號,往右看是參數列,說明a是一個函數指標,接著往左看是*號,說明指向的函數返回值是指標; 8 // 3、再跳出外層圓括號,往右看是[]運運算元,說明函數返回的是一個陣列指標,往左看是void*,說明陣列包含的型別是void*。 9 // 簡言之,a是一個指向函數的指標,該函數接受一個整型引數並返回一個指向含有10個void指標陣列的指標。 10 11 // 例2 12 float(*(*b)(int, int, float))(int);// 1、找到變數名b,往右看是圓括號,調轉方向往左看到*號,說明b是一個指標; 13 // 2、跳出內層圓括號,往右看是參數列,說明b是一個函數指標,接著往左看是*號,說明指向的函數返回值是指標; 14 // 3、再跳出外層圓括號,往右看還是參數列,說明返回的指標是一個函數指標,該函數有一個int型別的引數,返回值型別是float。 15 // 簡言之,b是一個指向函數的指標,該函數接受三個引數(int, int和float),且返回一個指向函數的指標,該函數接受一個整型引數並返回一個float。 16 17 // 例3 18 double(*(*(*c)())[10])(); 19 // 1、先找到變數名c(這裡c其實是新型別名),往右看是圓括號,調轉方向往左是*,說明c是一個指標; 20 // 2、跳出圓括號,往右看是空參數列,說明c是一個函數指標,接著往左是*號,說明該函數的返回值是一個指標; 21 // 3、跳出第二層圓括號,往右是[]運運算元,說明函數的返回值是一個陣列指標,接著往左是*號,說明陣列中包含的是指標; 22 // 4、跳出第三層圓括號,往右是參數列,說明陣列中包含的是函數指標,這些函數沒有引數,返回值型別是double。 23 // 簡言之,c是一個指向函數的指標,該函數無引數,且返回一個含有10個指向函數指標的陣列的指標,這些函數不接受引數且返回double值。 24 25 // 例4 26 int(*(*d())[10])(); // 這是一個函數宣告,不是變數定義 27 // 1、找到變數名d,往右是一個無參參數列,說明d是一個函數,接著往左是*號,說明函數返回值是一個指標; 28 // 2、跳出裡層圓括號,往右是[]運運算元,說明d的函數返回值是一個指向陣列的指標,往左是*號,說明陣列中包含的元素是指標; 29 // 3、跳出外層圓括號,往右是一個無參參數列,說明陣列中包含的元素是函數指標,這些函數沒有引數,返回值的型別是int。 30 // 簡言之,d是一個返回指標的函數,該指標指向含有10個函數指標的陣列,這些函數不接受引數且返回整型值。
如果想要定義和a同型別的變數a2,那麼得重複書寫:
void* (*(*a)(int))[10]; void* (*(*a2)(int))[10];
那怎麼避免這種沒有價值的重複呢?答案就是用typedef來簡化複雜的宣告和定義。
// 在之前的定義前邊加typedef,然後將變數名a替換為型別名A typedef void* (*(*A)(int))[10]; // 定義相同型別的變數a和a2 A a, a2;
typedef在這裡的用法,總結一下就是:任何宣告變數的語句前面加上typedef之後,原來是變數的都變成一種型別。不管這個宣告中的識別符號號出現在中間還是最後。
typedef定義了一種型別的新別名,不同於宏,它不是簡單的字串替換。比如:
typedef char* PSTR; int mustrcmp(const PSTR, const PSTR);
上邊的const PSTR並不是const char*,而是相當於.char* const。原因在於const給予了整個指標本身以常數性,也就是形成常數指標char* const。
typedef在語法上是一個儲存類的關鍵字(和auto、extern、mutable、static、register等一樣),雖然它並不真正影響物件的儲存特性。如:
typedef static int INT2;
編譯會報錯:「error C2159:指定了一個以上的儲存類」。
#define是宏定義指令,宏定義就是將一個識別符號定義為一個字串,在預編譯階段執行,將源程式中的標誌符全部替換為指定的字串。#define有以下幾種常見用法:
格式:#define 識別符號 字串
其中的「#」表示這是一條預處理命令。凡是以「#」開頭的均為預處理命令。「define」為宏定義命令。「識別符號」為所定義的宏名。「字串」可以是常數、表示式、格式串等。
格式:#define 宏名(形參表) 字串
1 #define add(x, y) (x + y) //此處要打括號,不然執行2*add(x,y)會變成 2*x + y 2 int main() 3 { 4 std::cout << add(9, 12) << std::endl; // 輸出21 5 return 0; 6 }
在大規模開發過程中,標頭檔案很容易發生巢狀包含,而#ifdef配合#define,#endif可以避免這個問題。作用類似於#pragma once。
#ifndef DATATYPE_H #define DATATYPE_H ... #endif
在跨平臺開發中,也常用到#define,可以在編譯的時候通過#define來設定編譯環境。
1 #ifdef WINDOWS 2 ... 3 (#else) 4 ... 5 #endif 6 #ifdef LINUX 7 ... 8 (#else) 9 ... 10 #endif
#:對應變數字串化
##:把宏定義名與宏定義程式碼序列中的識別符號連線在一起,形成一個新的識別符號
1 #include <stdio.h> 2 #define trace(x, format) printf(#x " = %" #format "\n", x) 3 #define trace2(i) trace(x##i, d) 4 5 int main(int argc, _TCHAR* argv[]) 6 { 7 int i = 1; 8 char *s = "three"; 9 float x = 2.0; 10 11 trace(i, d); // 相當於 printf("x = %d\n", x) 12 trace(x, f); // 相當於 printf("x = %f\n", x) 13 trace(s, s); // 相當於 printf("x = %s\n", x) 14 15 int x1 = 1, x2 = 2, x3 = 3; 16 trace2(1); // 相當於 trace(x1, d) 17 trace2(2); // 相當於 trace(x2, d) 18 trace2(3); // 相當於 trace(x3, d) 19 20 return 0; 21 } 22 23 // 輸出: 24 // i = 1 25 // x = 2.000000 26 // s = three 27 // x1 = 1 28 // x2 = 2 29 // x3 =3
__VA_ARGS__:是一個可變引數的宏,這個可變引數的宏是新的C99規範中新增的,目前似乎只有gcc支援。實現思想就是宏定義中參數列的最後一個引數為省略號(也就是三個點)。這樣預定義宏__VA_ARGS__就可以被用在替換部分中,替換省略號所代表的字串,如:
1 #define PR(...) printf(__VA_ARGS__) 2 int main() 3 { 4 int wt=1,sp=2; 5 PR("hello\n"); // 輸出:hello 6 PR("weight = %d, shipping = %d",wt,sp); // 輸出:weight = 1, shipping = 2 7 return 0; 8 }
附:C++中其他常用預處理指令:
#include // 包含一個原始碼檔案 #define // 定義宏 #undef // 取消已定義的宏 #if // 如果給定條件為真,則編譯下面程式碼 #ifdef // 如果宏已定義,則編譯下面程式碼 #ifndef // 如果宏沒有定義,則編譯下面程式碼 #elif // 如果前面#if給定條件不為真,當前條件為真,則編譯下面程式碼 #endif // 結束一個#if...#else條件編譯塊 #error // 停止編譯並顯示錯誤資訊 __FILE__ // 在預編譯時會替換成當前的原始檔cpp名 __LINE__ // 在預編譯時會替換成當前的行號 __FUNCTION__ // 在預編譯時會替換成當前的函數名稱 __DATE__ // 進行預處理的日期(「Mmm dd yyyy」形式的字串文字) __TIME__ // 原始檔編譯時間,格式為「hh:mm:ss」
C++為型別建立別名的方式有兩種,一種是使用前處理器#define,一種是使用關鍵字typedef,格式如下:
#define BYTE char // 將Byte作為char的別名 typedef char byte;
但是在宣告一系列變數是,請使用typedef而不是#define。比如要讓byte_pointer作為char指標的別名,可將byte_pointer宣告為char指標,然後再前面加上typedef:
typedef float* float_pointer;
也可以使用#define,但是在宣告多個變數時,前處理器會將下邊宣告「FLOAT_POINTER pa, pb;」置換為:「float * pa, pb;」,這顯然不是我們想要的結果。但是用typedef就不會有這樣的問題。
#define FLOAT_POINTER float* FLOAT_POINTER pa, pb;
using關鍵字常見用法有三:
using namespace std; // 也可在程式碼中直接使用std::
子類繼承父類別之後,在public、protected、private下使用「using 可存取的父類別成員」,相當於子類在該修飾符下宣告了該成員。
一般情況下,using與typedef作用等同:
// 使用using(C++11) using counter = long; // 使用typedef(C++03) // typedef long counter;
別名也適用於函數指標,但比等效的typedef更具可讀性:
1 // 使用using(C++11) 2 using func = void(*)(int); 3 4 // 使用typedef(C++03) 5 // typedef void (*func)(int); 6 7 // func can be assigned to a function pointer value 8 void actual_function(int arg) { /* ... */ } 9 func fptr = &actual_function;
typedef的侷限是它不適用於模板,但是using支援建立型別別名,例如:
template<typename T> using ptr = T*; // the name 'ptr<T>' is now an alias for pointer to T ptr<int> ptr_int;
template <class T1, class T2>... template <typename T1, typename T2>...
這之前先解釋幾個概念:
> 依賴名稱(dependent name):模板中依賴於模板引數的名稱。
> 巢狀依賴名稱(nested dependent name):從屬名稱巢狀在一個類裡邊。巢狀從屬名稱是需要用typename宣告的。
template<class T> class X { typename T::Y m_y; // m_y依賴於模板引數T,所以m_y是依賴名稱;m_y同時又巢狀在X類中,所以m_y又是巢狀依賴名稱 };
上例中,m_y是巢狀依賴名稱,需要typename來告訴編譯器Y是一個型別名,而非變數或其他。否則在T成為已知之前,是沒有辦法知道T::Y到底是不是一個型別。
1 template <class T> 2 class C1 : typename T::InnerType // Error - typename not allowed. 3 {}; 4 template <class T> 5 class C2 : A<typename T::InnerType> // typename OK. 6 {};
C++提供了模板(template)程式設計的概念。所謂模板,實際上是建立一個通用函數或類,其類內部的型別和函數的形參型別不具體指定,用一個虛擬的型別來代表。這種通用的方式稱為模板。模板是泛型程式設計的基礎,泛型程式設計即以一種獨立於任何特定型別的方式編寫程式碼。
函數模板是通用的函數描述,也就是說,它們使用泛型來定義函數,其中的泛型可用具體的型別(如int或double)替換。通過將型別作為引數傳遞給模板,可使編譯器生成該型別的函數。由於模板允許以泛型方式程式設計,因此又被稱為通用程式設計。由於型別用參數列示,因此模板特性也被稱為引數化型別(parameterized types)。
請注意,模板並不建立任何函數,而只是告訴編譯器如何定義函數。一般如果需要多個將同一種演演算法用於不同型別的函數,可使用模板。
(1)模板定義
template <typename T> // or template <class T> void f(T a, T b) {...}
在C++98新增關鍵字typename之前,用class來建立模板,二者在此作用相同。注意這裡class只是表明T是一個通用的型別說明符,在使用模板時,將使用實際的型別替換它。
(2)顯式具體化(explicit specialization)
舉例如下:
1 #include <iostream> 2 3 // 常規模板 4 template <typename T> 5 void Swap(T &a, T &b); 6 7 struct job 8 { 9 char name[40]; 10 double salary; 11 int floor; 12 }; 13 14 // 顯示具體化 15 template <> void Swap<job>(job &j1, job &j2); 16 17 int main() 18 { 19 using namespace std; 20 cout.precision(2); // 保留兩位小數精度 21 cout.setf(ios::fixed, ios::floatfield); // fixed設定cout為定點輸出格式;floatfield設定輸出時按浮點格式,小數點後有6為數位 22 23 int i = 10, j = 20; 24 Swap(i, j); // 生成Swap的一個範例:void Swap(int &, int&) 25 cout << "i, j = " << i << ", " << j << ".\n"; 26 27 job sxx = { "sxx", 200, 4 }; 28 job xt = { "xt", 100, 3 }; 29 Swap(sxx, xt); // void Swap(job &, job &) 30 cout << sxx.name << ": " << sxx.salary << " on floor " << sxx.floor << endl; 31 cout << xt.name << ": " << xt.salary << " on floor " << xt.floor << endl; 32 33 return 0; 34 } 35 36 // 通用版本,交換兩個型別的內容,該型別可以是結構體 37 template <typename T> 38 void Swap(T &a, T &b) 39 { 40 T temp; 41 temp = a; 42 a = b; 43 b = temp; 44 } 45 46 // 顯示具體化,僅僅交換job結構的salary和floor成員,而不交換name成員 47 template <> void Swap<job>(job &j1, job &j2) 48 { 49 double t1; 50 int t2; 51 t1 = j1.salary; 52 j1.salary = j2.salary; 53 j2.salary = t1; 54 t2 = j1.floor; 55 j1.floor = j2.floor; 56 j2.floor = t2; 57 }
(3)範例化和具體化
template void Swap<int>(int, int); // 該宣告的意思是「使用Swap()模板生成int型別的函數定義」
// 該宣告意思是:「不要使用Swap()模板來生成函數定義,而應使用專門為int型別顯示定義的函數定義」 template <> void Swap<int>(int &, int &); template <> void Swap(int &, int &);
注意:顯示具體化的原型必須有自己的函數定義。
以上三種統稱為具體化(specialization)。下邊的程式碼總結了上邊這些概念:
1 template <class T> 2 void Swap(T &, T &); // 模板原型 3 4 template <> void Swap<job>(job &, job &); // 顯示具體化 5 template void Swap<char>(char &, char &); // 顯式範例化 6 7 int main(void) 8 { 9 short a, b; 10 Swap(a, b); // 隱式範例化 11 12 job n, m; 13 Swap(n, m); // 使用顯示具體化 14 15 char g, h; 16 Swap(g, h); // 使用顯式模板範例化 17 }
編譯器會根據Swap()呼叫中實際使用的E引數,生成相應的版本。
當編譯器看到函數呼叫Swap(a, b)後,將生成Swap()的short版本,因為兩個引數都是short。當編譯器看到Swap(n, m)後,將使用為job型別提供的獨立定義(顯示具體化)。當編譯器看到Swap(g, h)後,將使用處理顯式範例化時生成的模板具體化。
(4)關鍵字decltype(C++11)
template <class T1, Class T2> void ft(T1 x, T2 y) { decltype(x + y) xpy = x + y; // decltype使得xpy和x+y具有相同的型別 }
// 原型 double h(int x, float y); // 新增的語法 auto h(int x, float y) -> double;
該語法將返回引數移到了引數宣告的後面。->double被稱為後置返回型別(trailing return type)。其中auto是一個預留位置,表示後置返回型別提供的型別。
所以在不知道模板函數的返回型別時,可使用這種語法:
template <class T1, Class T2> auto ft(T1 x, T2 y) -> decltype(x + y) { return x + y; }
(1)類別範本定義和使用
1 // 類別範本定義 2 template <typename T> // or template <class T> 3 class A 4 {...} 5 6 // 範例化 7 A<t> st; // 用具體型別t替換泛型識別符號(或者稱為型別引數)T
程式中僅包含模板並不能生成模板類,必須要請求範例化。為此,需要宣告一個型別為模板類物件,方法是使用所需的具體型別替換泛型名。比如用來處理string物件的棧類,就是basic_string類別範本的具體實現。
應注意:類別範本必須顯示地提供所需的型別;而常規函數模板則不需要,因為編譯器可以根據函數的引數型別來確定要生成哪種函數。
(2)模板的具體化
類別範本與函數模板很相似,也有隱式範例化、顯示範例化和顯示具體化,統稱為具體化(specialization)。模板以泛型的方式描述類,而具體化是使用具體的型別生成類宣告。
1 // 類別範本定義 2 template <class T, int n> 3 class ArrayTP 4 {...}; 5 6 // 隱式範例化(生成具體的類定義) 7 ArrayTP<int, 100> stuff 8 9 // 顯示範例化(將ArrayTP<string, 100>宣告為一個類) 10 template class ArrayTP<string, 100>;
第一:部分具體化可以給型別引數之一指定具體的型別:
1 // 通用模板 2 template <typename T1, typename T2> class Pair {...}; 3 // 部分具體化模板(T1不變,T2具體化為int) 4 template <typename T1> class Pair<T1, int> {...}; 5 // 顯示具體化(T1和T2都具體化為int) 6 template <> calss Pair<int, int> {...};
如果有多種模板可供選擇,編譯器會使用具體化程度最高的模板(顯示 > 部分 > 通用),比如對上邊三種模板進行範例化:
Pair<double, double> p1; // 使用通用模板進行範例化 Pair<double, int> p2; // 使用Pair<T1, int>部分具體化模板進行範例化 Pair<int, int> p3; // 使用Pair<T1, T2>顯式具體化模板進行範例化
第二:也可以通過為指標提供特殊版本來部分具體化現有的模板:
// 通用模板 template <typename T> class Feeb {...}; // 指標部分具體化模板 template <typename T*> class Feeb {...};
編譯器會根據提供的型別是不是指標來選擇使用通用模板或者指標具體化模板:
Feeb<char> fb1; // 使用通用模板,T為char型別 Feeb<char *> fb2; // 使用Feeb T*具體化,T為char型別
上述第二個宣告使用具體化模板,將T轉換為char型別;如果使用的是通用模板,則是將T轉換為char*型別。
(3)成員模板
模板可作為結構、類或模板類的成員。下邊範例是將另一個模板類和模板函數作為該模板類的成員:
1 #include <iostream> 2 using std::cout; 3 using std::endl; 4 5 template <typename T> 6 class beta 7 { 8 private: 9 template <typename V> // 巢狀的模板類成員(只能在beta類中存取) 10 class hold 11 { 12 private: 13 V val; 14 public: 15 hold(V v = 0) :val(v) {} 16 void show() const { cout << val << endl; } 17 V value() const { return val; } 18 }; 19 // beta類使用hold模板宣告兩個資料成員 20 hold<T> q; // q是基於T型別(beta模板引數)的hold物件 21 hold<int> n; // n是基於int的hold物件 22 public: 23 beta(T t, int i) :q(t), n(i) {} 24 template <typename U> // 模板方法 25 U blab(U u, T t) { return (n.value() + q.value()) * u / t; } 26 void Show() const { (q.show(); n.show(); } 27 }; 28 29 int main() 30 { 31 beta<double> guy(3.5, 3); 32 cout << "T was set to double\n"; 33 guy.Show(); 34 cout << "V was set to T, which id double, then V was set to int\n"; 35 cout << guy.blab(10, 2.3) << endl; 36 cout << "U was set to int\n"; 37 cout << guy.blab(10.0, 2.3) << endl; 38 cout << "U was set to double\n"; 39 cout << "Done\n"; 40 return 0; 41 }
(4)將模板用作引數
模板可以包含型別引數(如typename T)和非型別引數(如int n),還可以包含本身就是模板的引數。格式如下:
template <template <typename T> class Type> class B
模板引數為template <typename T> classType,其中emplate <typename T> class是型別,Type是引數。
範例如下:
1 #include <iostream> 2 #include "stacktp.h" 3 template <template <typename T> class Thing> 4 class Crab 5 { 6 private: 7 Thing<int> s1; 8 Thing<double> s2; 9 public: 10 Crab() {}; 11 // 假設pop()和push()是Thing類的成員函數 12 bool push(int a, double x) { return s1.push(a) && s2.push(x); } 13 bool pop(int& a, double& x) { return s1.pop(a) && s2.pop(x); } 14 }; 15 16 int main() 17 { 18 using std::cout; 19 using std::cin; 20 using std::endl; 21 22 Crab<Stack> nebula; 23 int ni; 24 double nb; 25 while (cin >> ni >> nb && ni > 0 && nb > 0) 26 { 27 if (!nebula.push(ni, nb)) 28 break; 29 } 30 while (nebula.pop(ni, nb)) 31 cout << ni << ", " << nb << endl; 32 cout << "Done.\n"; 33 return 0; 34 }
模板和迭代器都是STL通用方法的重要組成部分,模板使演演算法獨立於儲存的資料型別,而迭代器使演演算法獨立於使用的容器型別。
迭代器是一個物件,可以迴圈C++標準庫容器中的元素,並提供對單個元素的存取。C++標準庫容器全都提供有迭代器,因此演演算法可以採用標準方式存取元素,而不必考慮用於儲存元素的容器型別。簡言之,迭代器就是為存取容器所提供的STL通用演演算法的統一介面,每個容器類都定義了相應的迭代器型別,通過迭代器就能夠實現對容器中的元素進行存取和操作。
可以使用成員和全域性函數(例如begin()和end())顯式使用迭代器,例如 ++ 和 -- 分別用於向前和向後移動。 還可以將迭代器隱式用於範圍 for 迴圈或 (某些迭代器型別) 下標運運算元 []
。
在 C++ 標準庫中,序列或範圍的開頭是第一個元素。 序列或範圍的末尾始終定義為最後一個元素的下一個位置。 全域性函數begin和end將迭代器返回到指定的容器。 典型的顯式迭代器迴圈存取容器中的所有元素,如下所示:
vector<int> vec{ 0,1,2,3,4 }; for (auto it = begin(vec); it != end(vec); it++) { // 使用解除參照運運算元存取元素 cout << *it << " "; }
也可使用C++11新增的基於範圍的for迴圈完成相同操作:
for (auto num : vec) { // 不使用解除參照運運算元 cout << num << " "; }
STL中定義了5種型別的迭代器:輸入迭代器、輸出迭代器、正向迭代器、雙向迭代器和隨機存取迭代器。
行內函式是C++為提高程式執行速度所做的一項改進。常規函數和行內函式之間的主要區別不在於編寫方式,而在於C++編譯器如何將它們組合到程式中。
我們先來詳細看下常規函數的呼叫過程:
行內函式的編譯程式碼與其他程式程式碼「內聯」起來了,即編譯器將使用相應的函數程式碼替換函數呼叫。對於內聯程式碼,程式無需跳到另一個位置處執行程式碼,再跳回來。因此,行內函式執行速度比常規函數稍快,但代價是需要佔用更多的記憶體。所以要權衡實際情況再選擇是否使用行內函式。
下圖很直觀的給出了行內函式與常規函數的區別:
行內函式使用方式很簡單,只需要在函數宣告和函數定義前加上inline關鍵字即可。使用行內函式需要注意以下幾點:
內聯與宏
行內函式是將語句封裝成函數,而宏定義替換隻是文字替換,沒有經歷系統完整的運算規則的規劃,有一定異變性。比如:
#define SQUARE(X) X*X a = SQUARE(2 + 3) // 相當於 a = 2 + 3 * 2 + 3
運運算元過載是一種形式的C++多型。我們可以定義多個函數名相同但特徵標(參數列)不同的函數,稱之為函數過載(或函數多型)。運運算元過載將過載的概念擴充套件到運運算元上,允許賦予C++運運算元更多的含義。
過載運運算元需要使用運運算元函數,格式如下:
operatorop(argument-list)
其中:op代表運運算元,且必須是有效的C++運運算元。例如,operator+()表示過載+運運算元,operator*()表示過載*運運算元。
那具體應該怎麼在程式中使用呢?我們來結合下邊的例子進行說明:
1 class Time 2 { 3 private: 4 int hours; 5 int minutes; 6 public: 7 Time(); 8 Time(int h, int m = 0); 9 void AddMin(int m); 10 void AddHr(int h); 11 void Reset(int h = 0, int m = 0); 12 Time Sum(const Time& t) const; // Sum函數 13 Time operator+ (const Time & t) const; // 使用過載的加法運運算元 14 void Show() const; 15 }; 16 17 Time::Time() 18 { 19 hours = minutes = 0; 20 } 21 22 Time::Time(int h, int m) 23 { 24 hours = h; 25 minutes = m; 26 } 27 28 void Time::AddMin(int m) 29 { 30 minutes += m; 31 hours += minutes / 60; 32 minutes %= 60; 33 } 34 35 void Time::AddHr(int h) 36 { 37 hours += h; 38 } 39 40 void Time::Reset(int h, int m) 41 { 42 hours = h; 43 minutes = m; 44 } 45 46 // 注意:不要返回指向區域性變數或臨時物件的參照。函數執行完畢後,區域性變數和臨時物件將消失,參照將指向不存在的資料。 47 Time Time::Sum(const Time& t) const 48 { 49 Time sum; 50 sum.minutes = minutes + t.minutes; 51 sum.hours = hours + t.hours + t.minutes / 60; 52 sum.minutes %= 60; 53 return sum; 54 } 55 56 Time Time::operator+(const Time& t) const 57 { 58 Time sum; 59 sum.minutes = minutes + t.minutes; 60 sum.hours = hours + t.hours + t.minutes / 60; 61 sum.minutes %= 60; 62 return sum; 63 } 64 65 void Time::Show() const 66 { 67 std::cout << hours << " hours, " << minutes << " minutes"; 68 } 69 70 int main() 71 { 72 using std::cout; 73 using std::endl; 74 Time planning; 75 Time coding(2, 40); 76 Time fixing(5, 55); 77 Time total; 78 79 cout << "planning time = "; 80 planning.Show(); 81 cout << endl; 82 83 cout << "coding time = "; 84 coding.Show(); 85 cout << endl; 86 87 cout << "fixing time = "; 88 fixing.Show(); 89 cout << endl; 90 91 // Sum函數 92 total = coding.Sum(fixing); 93 cout << "coding.sum(fixing) = "; 94 total.Show(); 95 cout << endl; 96 97 // 使用過載運運算元 98 total = coding + fixing; // 與下一句程式碼等效 99 total = coding.operator+(fixing); 100 cout << "coding + fixing = "; 101 total.Show(); 102 cout << endl; 103 104 return 0; 105 }
同Sum()一樣,opertor+()也是由Time物件呼叫的,它將第二個Time物件作為引數,並返回一個Time物件。呼叫operator+()可使用以下兩種等效的方式:
total = coding + fixing; total = coding.operator+(fixing);
operator+()函數的名稱使得可以使用函數表示法或運運算元表示法來呼叫它。需要注意的是,在運運算元表示法中,運運算元左側的物件是呼叫物件,運運算元右邊的物件是作為引數被傳遞的物件。
(1)過載後的運運算元必須至有一個運算元是使用者定義的型別,這將防止使用者為標準型別過載運運算元。比如不能將減法運運算元(-)過載為兩個double型別的和。
(2)使用運運算元時不能違反運運算元原來的句法規則。也不能修改運運算元的優先順序。
(3)不能建立新的運運算元,例如,不能定義ooperator**()函數來表示求冪。
(4)不能過載下面的運運算元:
sizeof | sizeof運運算元 |
. | 成員運運算元 |
.* | 成員指標運運算元 |
:: | 作用域解析運運算元 |
?: | 條件運運算元 |
# | 預處理命令:轉換為字串 |
## | 預處理命令:拼接 |
typeid | 一個RTTI運運算元 |
const_cast | 強制型別轉換運運算元 |
dynamic_cast | 強制型別轉換運運算元 |
reinterpret_cast | 強制型別轉換運運算元 |
static_cast | 強制型別轉換運運算元 |
(5)下邊運運算元只能通過成員函數進行過載
= | 賦值運運算元 |
() | 函數呼叫運運算元 |
[] | 下標運運算元 |
-> | 函數指標存取類成員的運運算元 |
附:可過載的運運算元:
+ | - | * | / | % | ^ |
& | | | ~= | ! | = | < |
> | += | -= | *= | /= | %= |
^= | &= | |= | << | >> | >>= |
<<= | == | != | <= | >= | && |
|| | ++ | -- | , | ->* | -> |
() | [] | new | delete | new[] | delete[] |
->* 和 -> 用法範例:
1 void (Test::*pfunc)() = &Test::func; 2 Test t1; 3 Test* t2 = new Test(); 4 5 t1.func(); // 正常呼叫類成員方法 6 (t1.*pfunc)(); // 通過函數指標呼叫類成員方法 7 8 t2->func(); // 正常呼叫類成員方法 9 (t2->*pfunc)(); // 通過函數指標呼叫類成員方法
通常我們只能通過公有類方法來存取類物件的私有部分,這種限制有時顯得過於嚴格,以致不適合特定的程式設計問題。C++提供了另一種形式的存取許可權:友元。友元有三種:友元函數、友元類和友元成員函數,通過讓他們成為類的友元,可以賦予他們與類的成員函數相同的存取許可權。
哪些函數、成員函數或類為友元是由類定義的,所以儘管友元被授予從外部存取類的私有部分的許可權,但它們並不與OOP的思想相悖。友元宣告可以位於公有、私有或保護部分,其所在的位置無關緊要。
友元函數是提供特殊存取特權的常規外部函數,它不是類的成員,但是其存取許可權與成員函數相同,有權存取該類的私有成員和受保護成員。
1 #include <iostream> 2 3 using namespace std; 4 class Point 5 { 6 friend void ChangePrivate(Point&); 7 public: 8 Point(void) : m_i(0) {} // 建構函式成員初始化列表 9 void PrintPrivate(void) { cout << m_i << endl; } 10 11 private: 12 int m_i; 13 }; 14 15 void ChangePrivate(Point& i) { i.m_i++; } 16 17 int main() 18 { 19 Point sPoint; 20 sPoint.PrintPrivate(); 21 ChangePrivate(sPoint); 22 sPoint.PrintPrivate(); 23 // Output: 0 24 // 1 25 }
將類作為友元,那麼友元類的所有方法都可以存取原始類的私有成員和保護成員。用法和友元函數類似,不多做介紹。
很多時候,並不是友元類中所有的成員都需要存取原始類的私有成員或受保護成員。這種情況下可以將類的成員宣告為原始類的友元。比如:
1 Class Tv; // 前向宣告,避免迴圈依賴 2 3 class Remote 4 { 5 public: 6 void set_chan(Tv & t, int c) {t.channel = c;} // 行內函式 7 ... 8 }; 9 10 Class Tv 11 { 12 private: 13 int channel; 14 public: 15 friend void Remote::set_chan(Tv & t, int c); 16 ... 17 };
注:上例中,Tv中包含了Remote的定義,這意味著Remot要定義在Tv之前;Remote的方法也提到了Tv物件,這意味著Tv要定義在Remote之前。為了避開這種迴圈依賴,可以在Remote定義之前加上 「class Tv;」 作為前向宣告(forward declaration)。
有的時候,會要求A類和B類能夠進行互動,即A類的某些方法能夠影響B類的物件,B類的某些方法也能影響A類的物件。這可以讓類彼此稱為對方的友元來實現,範例如下:
1 Class Tv 2 { 3 friend class Remote; 4 public: 5 void buzz(Remote & r); 6 ... 7 }; 8 9 class Remote 10 { 11 friend class Tv; 12 public: 13 void Bool volup(Tv & t) { t.volup(); } 14 ... 15 }; 16 17 inline void Tv::buzz(Remote & r) 18 { 19 ... 20 }
存在另一種情況,函數需要存取兩個(或多個)類的私有資料。邏輯上來講,該函數應該是兩個(或多個)類的成員函數,顯然這是不可能的。此時比較合理的方式就是將該函數作為兩個(或多個)類的友元。範例如下:
1 class B; // 前向宣告(forward declaration) 2 class A 3 { 4 friend void sync(B& a, const A& p); // sync a to p 5 friend void sync(A& P, const B& a); // sync p to a 6 }; 7 class B 8 { 9 friend void sync(B& a, const A& p); // sync a to p 10 friend void sync(A& P, const B& a); // sync p to a 11 }; 12 13 // 友元函數定義 14 inline void sync(B& a, const A& p) 15 { 16 ... 17 } 18 inline void sync(A& P, const B& a) 19 { 20 ... 21 }
圖中有四種類宣告:Base,Derived,aFriend、anotherFriend。僅有aFriend類能夠直接存取Base類(以及Base可能已繼承的所有成員)的私有成員。
char型別是整形的一種。用來處理字元和比short更小的整形。ASCII碼共128個字元,用char型別表示完全足夠,而像Unicode這種大型字元集,C++也支援用寬字元型別wchar_t來表示。
cout是ostream類的特定物件,put()是ostream的成員函數。只能通過類的特定物件來使用成員函數,所以cout.put()表示通過類物件cout來使用函數oput(),其中句點「.」稱為成員運運算元。
cout.put() 提供了一種顯示字元的方法,可以替代<<運運算元。char型別和cout.put()用法範例如下:
1 #include <iostream> 2 3 int main() 4 { 5 using namespace std; 6 char ch = 'M'; 7 int i = ch; 8 cout << "The ASCII code for " << ch << " is " << i << endl; 9 10 ch += 1; 11 i = ch; 12 cout << "The ASCII code for " << ch << " is " << i << endl; 13 14 cout << "Displaying char ch using cout.put(ch): "; 15 cout.put(ch); 16 17 cout.put('!'); 18 return 0; 19 }
輸出:
將字元用單引號''括起來。比如'A'表示65,即字元A的ASCII碼。
跳脫字元:
字元名稱 | ASCII符號 | C++程式碼 | 十進位制ASCII碼 | 十六進位制ASCII碼 |
換行符 | NL(LF) | \n | 10 | 0xA |
水平製表符 | HT | \t | 9 | 0x9 |
垂直製表符 | VT | \v | 11 | 0xB |
退格 | BS | \b | 8 | 0c8 |
回車 | CR | \r | 13 | 0xD |
振鈴 | BEL | \a | 7 | 0x7 |
反斜槓 | \ | \\ | 92 | 0x5C |
問號 | ? | \? | 63 | 0x3F |
單引號 | ' | \' | 39 | 0x27 |
雙引號 | " | \" | 34 | 0x22 |
跳脫字元作為字元常數時,應用單引號括起來;將它們放在字串中時,不要使用單引號。
換行符可代替endl,下邊三行程式碼都起到換行作用:
cout << endl; cout << '\n'; cout << "\n"; // 相當於'\n'在字串中,不需要加單引號
char在預設情況下既不是有符號,也不是無符號,是否有符號由C++實現決定。signed char表示範圍為-128~127,unsigned char表示範圍為0~255。
像漢字日文等,無法用一個8位元的位元組l來表示,這種情況,C++一般有兩種處理方式:
(1)將char定義為一個16位元或者更長的位元組;
(2)用8位元的char來表示基本字元集,另外使用wchar_t(寬字元型別)表示擴充套件字元集。wchar_t流的輸入輸出工具為wcin和wcout。可通過加字首L來指示寬字元常數和寬字串。
wchar_t blb = L'P' wcout << L"tall" << endl;
char16_T和char32_t都是無符號的,前者長16位元,後者長32位元。通常使用字首u表示char16_t字元常數和字串常數;用字首U表示char32_t字元常數和字串常數。
至此,string類中涉及到的一些C++基礎知識也介紹的差不多了,接下來我們就來看看string類到底是如何設計的。
1 #define _STD ::std:: 2 3 template <class _Elem, // _Elem是儲存在字串中的型別 4 class _Traits = char_traits<_Elem>, // _Traits引數是一個類,定義了型別要被表示為字串時,所必須具備的特徵 5 class _Alloc = allocator<_Elem>> // _Alloc是用於處理字串記憶體分配的類 6 class basic_string 7 { // null-terminated transparent array of elements 8 private: 9 friend _Tidy_deallocate_guard<basic_string>; 10 friend basic_stringbuf<_Elem, _Traits, _Alloc>; 11 12 using _Alty = _Rebind_alloc_t<_Alloc, _Elem>; 13 using _Alty_traits = allocator_traits<_Alty>; 14 15 ... 16 17 public: 18 // using源於C++11,等價於C++98的typedef,比如下邊第一行程式碼相當於:typedef _Traits traits_type; 19 using traits_type = _Traits; // _Traits是對應於特定型別(如char_traits<char>)的模板引數 20 using allocator_type = _Alloc; 21 22 using value_type = _Elem; 23 using size_type = typename _Alty_traits::size_type; // 根據儲存的型別返回字串的長度(無符號型別) 24 using difference_type = typename _Alty_traits::difference_type; // 用於度量字串中兩個元素之間的距離(size_type有符號版本) 25 using pointer = typename _Alty_traits::pointer; // 對於char具體化,pointer型別為char *,與基本指標有著相同的特徵 26 using const_pointer = typename _Alty_traits::const_pointer; 27 using reference = value_type&; // 對於char具體化,reference型別為char &,與基本參照有著相同的特徵 28 using const_reference = const value_type&; 29 30 using iterator = _String_iterator<_Scary_val>; // 迭代器型別 31 using const_iterator = _String_const_iterator<_Scary_val>; 32 33 using reverse_iterator = _STD reverse_iterator<iterator>; 34 using const_reverse_iterator = _STD reverse_iterator<const_iterator>; 35 36 ... 37 38 // 靜態常數npos,size_type是無符號的,因此將-1賦給npos相當於將最大的無符號值賦給它,這個值比可能的最大陣列索引大1 39 static constexpr auto npos{ static_cast<size_type>(-1) }; 40 };
上邊定義中用到了另外兩個模板類char_traits和allocator,前者又稱字元特性模板類,提供最基本的字元特性的統一的方法函數;後者是C++標準庫容器都具有一個預設的模板引數,通過使用自定義分配器構造容器可控制該容器的元素的分配和釋放。
即編譯器根據所需的型別,使用basic_string類別範本提供的處方生成具體的類定義,我們常用的string類便是這麼來的。可以看到,basic_string類別範本在具體化的過程中還使用了char_traits類別範本和allocator類別範本的具體化。而就具體化種類(隱式範例化、顯示範例化、顯示具體化)而言,這裡的具體化應屬於隱式範例化。string類就相當於basic_string類別範本關於char型別的隱式範例化。
using string = basic_string<char, char_traits<char>, allocator<char>>; using wstring = basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t>>; using u16string = basic_string<char16_t, char_traits<char16_t>, allocator<char16_t>>; using u32string = basic_string<char32_t, char_traits<char32_t>, allocator<char32_t>>;
string本質上就是basic_string類的char版本,basic_string怎麼用string就怎麼用。
string實際上是模板具體化basic_string<char>的一個typedef,同時省略了與記憶體管理相關的引數。
建構函式 | 描 述 |
string(const char * s) | 將string物件初始化為s指向的NBTS。NBTS(null-terminated string)表示以空字元結束的字串——傳統的C字串。 |
string(size_type n, char c) | 建立一個包含n個元素的string物件,其中每個元素都被初始化為字元c。size_type是一個依賴於實現的整形,在string中定義。 |
string(const string & str) | 將一個string物件初始化為string物件str(複製建構函式)。 |
string() | 建立一個預設的string物件,長度為0(預設建構函式)。 |
string(const char * s, size_type n) | 將string物件初始化為s指向的NBTS的前n個字元,即使超過了NBTS結尾。 |
template<class Iter> string(Iter begin, Iter end) |
將string物件初始化為區間[begin, end)內的字元,其中begin和end的行為就像指標,用於指定位置,範圍包括begin,但不包括end。 |
string(const string & str, string size_type pos = 0, suze_type n = npos) |
將一個string物件初始化為物件str中從位置pos開始到結尾的字元,或從位置pos開始的n個字元。string類將string::npos定義為字串的最大長度,通常為 unsigned int 的最大值。 |
string(string && str) noexcept | C++11新增,將一個string物件初始化為string物件str,並可能修改str(移動建構函式)。 |
string(initializer_list<char> il) | C++11新增,將一個string物件初始化為初始化列表il中的字元。 |
建構函式用法範例如下:
1 #include <string> 2 #include <iostream> 3 4 int main() 5 { 6 using namespace std; 7 8 string one("SXX Winner!"); // ctor 1,將string物件one初始化為常規的C-風格字串 9 cout << one << endl; 10 11 string two(20, '$'); // ctor 2,將string物件two初始化為由20個$字元組成的字串 12 cout << two << endl; 13 14 string three(one); // ctor 3,複製建構函式將string物件three初始化為one 15 cout << three << endl; 16 17 one += " Oops!"; 18 cout << one << endl; 19 20 two = "Sorry! That was "; 21 three[0] = 'Y'; 22 string four; // ctor 4,預設建構函式建立一個以後可以對其進行賦值的空字串 23 four = two + three; 24 cout << four << endl; 25 26 char alls[] = "All's well that ends well"; 27 string five(alls, 20); // ctor 5, 將five初始化為alls的前20個字元 28 cout << five << "!\n"; 29 30 string six(alls + 6, alls + 10); 31 cout << six << ", "; // ctor 6,將string物件six初始化為區間[6,10)內的字元 32 33 string seven(&five[6], &five[10]); // ctor 6, five[6]是一個char值,&five[6]是地址 34 cout << seven << "...\n"; 35 36 string eight(four, 7, 16); // ctor 7, 將物件four的部分內容複製到構造的物件中 37 cout << eight << " in motion!" << endl; 38 39 return 0; 40 }
對於ctor 5,當n超過了C-風格字串的長度,仍將複製請求數目的字元。比如上邊的例子中,如果用40代替20,將導致15個無用字元被複制到five的結尾處(即建構函式將記憶體中位於字串「All's well that ends well」後面的內容作為字元)。
第33行程式碼如果寫成 string seven(five + 6, five + 10) 是沒有意義的,物件名(不同於陣列名)不會被看作是物件的地址,因此five不是指標,five + 6也沒有意義;而five[6]是一個char值,所以&five[6]是地址。
方 法 | 返 回 值 |
begin() | 指向字串第一個字元的迭代器(如下圖示) |
cbegin() | 一個const_iterator,指向字串的第一個字元(C++11),作用和begin()類似,不同之處在於begin()可改元素值,cbegin()不可改 |
end() | 超尾值的迭代器,注意它返回的不是指向字串最後一個字元的迭代器,而是指向字串最後一個字元的下一位置(稱為超尾值)的迭代器 |
cend() | 為超尾值的const_iterator(C++11) |
rbegin() | 超尾值的反轉迭代器,即指向字串最後一個字元的迭代器 |
crbegin() | 為超尾值的反轉const_iterator(C++11) |
rend() | 指向第一個字元的反轉迭代器,也就是指向第一個字元的前一位置的迭代器 |
crend() | 指向第一個字元的反轉const_iterator(C++11) |
size() | 字串中的元素數,等於begin()到end()之間的距離 |
length() | 同size()。length()成員來自較早版本的string類,而size()則是為提供STL相容性而新增的 |
capacity() | 給字串分配的元素數。這可能大於實際的字元數,capacity() - size()的值表示在字串末尾附加多少字元后需要分配更多的記憶體 |
reverse() | 為字串請求記憶體塊,分配空間。很多C++實現分配一個比實際字串大的記憶體塊,為字串提供增大空間。如果字串不斷增大,超過了分配給它的記憶體塊大小,程式將分配一個大小為原來兩倍的新記憶體塊,以提供足夠的增大空間,避免不斷地分配新的記憶體塊而導致的效率低下。 |
max_size() | 字串的最大長度 |
data() | 一個指向陣列第一個元素的const charT*指標,其第一個size()元素等於*this控制的字串中對應的元素,其下一個元素為charT型別的charT(0)字元(字串末尾標記)。當string物件本身被修改後,該指標可能無效 |
c_str() | 一個指向陣列第一個元素的const charT*指標,其第一個size()元素等於*this控制的字串中對應的元素,其下一個元素為charT型別的charT(0)字元(字串尾標記)。當string物件本身被修改後,該指標可能無效 |
get_allocator() | 用於為字串object分配記憶體的allocator物件的副本 |
有些方法用來處理記憶體,如清除記憶體的內容,調整字串長度和容量。
方 法 | 作 用 |
void resize(size_type n) | 如果n>npos,將引發out_of_range異常;否則,將字串的長度改為n,如果n<size(),則截短字串,如果n>size(),則使用charT(0)中的字元填充字串 |
void resize(size_type n, charT c) | 如果n>npos,將引發out_of_range異常;否則,將字串的長度改為n,如果n<size(),則截短字串,如果n>size(),則使用字元c填充字串 |
void reverse(size_type res_arg = 0) | 將capacity()設定為大於或等於res_arg。由於這將重新分配字串,因此以前的參照、迭代器和指標將無效。 |
void shrink_to_fit() | 請求讓capacity()的值和size()相同(C++11新增) |
void clear() noexcept | 刪除字串中所有的字元 |
bool empty() const noexcept | 如果size()==0,則返回true |
有4種方法可以存取各個字元,其中兩種使用[]運演演算法,另外兩種使用at()方法:
1 // 能夠使用陣列表示法來存取字串的元素,可用於檢索或更改值 2 reference operator[](size_type pos); 3 // 可用於const物件,但只能用來檢索值 4 const_reference operator[](size_type pos) const; 5 // 功能同第一句程式碼,索引通過函數引數提供 6 reference at(size_type n); 7 // 功能同第二句程式碼,索引通過函數引數提供 8 const_reference at(size_type n) const;
at()方法執行邊界檢查,超界會引發out_of_range異常。operator[]()方法不進行邊界檢查。因此,可以根據安全性(使用at()檢測異常)和執行速度(使用陣列表示)選擇合適的方法。
string類提供6種搜尋函數,每個函數都有4個原型(過載)
(1)find():用於在字串中搜尋給定的子字串或字元。有4種過載的find()方法:
(2)rfind():查詢子字串或字元最後一次出現的位置。
(3)find_first_of():在字串中查詢引數中任何一個字元首次出現的位置。
string snake("cobra"); int where = snake.find_first_of("hark") // 返回r在「cobra」中的位置(即索引3)
(4)find_last_of():在字串中查詢引數中任何一個字元最後一次出現的位置。
string snake("cobra"); int where = snake.find_last_of("hark") // 返回a在「cobra」中的位置(即索引4)
(5)find_first_not_of():在字串中查詢第一個不包含在引數中的字元。
string snake("cobra"); int where = snake.find_first_not_of("hark") // 返回c在「cobra」中的位置(即索引0),因為「hark」中沒有c
(6)find_last_not_of():在字串中查詢最後一個不包含在引數中的字元。
string snake("cobra"); int where = snake.find_first_not_of("hark") // 返回b在「cobra」中的位置(即索引2),因為「hark」中沒有b
(1)過載的+=運運算元或append():將一個字串追加到另一個字串的後面。
(2)assign():該方法使得能夠將整個字串、字串的一部分或由相同字元組成的字元序列賦給string物件。原型之一如下
basic_string& assign(const basic_string& str)
(3)insert():將string物件、字串陣列或幾個字元插入到string物件中。
(4)erase():從字串中刪除字元;pop_back():刪除字串中的最後一個字元
(5)replace():替換字串中指定的內容
(6)copy():將string物件或其中的一部分複製到指定的字串陣列中;swap():使用一個時間恆定的演演算法來交換兩個string物件的內容
// copy()原型,s指向目標陣列,n是要複製的字串,pos指從string物件的什麼位置開始複製 size_type copy(charT* s, size_type n, size_type pos = 0) const; // swap()原型 void swap(basic_string& str);
注:copy()方法不追加空值字元,也不檢查目標陣列的長度是否足夠。
對於C-風格字串,有3中輸入方式:
char info[100]; cin >> info; // read a word cin.getline(info, 100); // read a line, discard \n cin.get(info, 100); // read a line, leave \n in queue
對於string物件,有兩種方式:
string stuff; cin >> stuff; // read a word getline(cin, stuff); // read a line, discard \n
兩個版本的getline()都有一個可選引數,用於指定使用哪個字元來確定輸入的邊界:
cin.getline(info, 100, ':'); // read up to :, discard : getline(cin, stuff, ':'); // read up to :, discard :
兩者之間的主要區別在於,string版本的getline()將自動調整目標string物件的大小,使之剛好能夠儲存輸入的字元;
另一個區別是,讀取C-風格字串的函數是istream類的方法,而string版本是獨立的函數。這就是C-風格字串輸入,cin是呼叫物件;而對於string物件輸入,cin是一個函數引數的原因。
2. C++ #define,typedef,using用法區別
5. C++ | 指向類成員變數的指標 ( .* 運運算元 與 ->* 運運算元)
8. 《C++ Primer Plus》相關
9. C++官方檔案