前言
上一章,複習了基本資料類型,趁熱打鐵,繼續把字元串、向量(vector)和數組搞定。
字元串是由字元組成,也是序列結構,是以,本章其實設計到的資料結構是序列。那麼就需要考慮是否能夠随機通路,如何周遊,如何進行一些基本操作,以及這些基本操作的效率問題。
整個系列的複習會将所有涉及到的stl進行分析,工作量可能會大,但是複習嘛,總是一步一個腳印。廢話不多說,開始幹貨。
3.1 using聲明命名空間
命名空間這個東西使得大的項目的名稱管理的效率得到了提升。如果需要使用庫函數,那麼需要加上命名空間才能使用相應的名稱。而using就使得不用每個名稱前都需要加命名空間。
#include <iostream>
using std::cin; // 一般來說,大家都喜歡之間using namespace std,這樣就不用對每個對象都使用using了
int main() {}
既然提到了#include,我覺得有必要複習一下預處理器指令,以及宏,這個還是非常重要,我在複習設計模式的,涉及到的一個問題,c++如何實作反射?,詳細請見部落格另一篇文章《C++反射的實作》
注意:
頭檔案裡不應該包含using聲明,因為,頭檔案的内容會拷貝到所有引用它的檔案中,如果頭檔案裡有某個聲明,那麼每個使用了這個頭檔案的檔案都會有這個聲明,名稱污染。。。不知哪聽來的
3.2 string類型
此處的string類型是标準庫中的string,定義在std命名空間裡。導入的頭檔案是string,而不是string.h,後者是c标準下的字元串數組的庫函數集合。
#include <string>
// 注意,不是string.h,如果想要使用c下的字元串處理函數,使用後者
using namespace std;
string類型是可變長的字元序列。
- 初始化
string s1; // 預設初始化,空串
string s2 = s1; // s2 是 s1 的副本
string s2(s1); // 直接初始化
string s3("hiya");
string s3 = "hiya"; // s3 是 字面值的副本
string s4(, 'c'); // s4 "cccccccccc"
- 操作
os << s // 寫入輸出流
is >> s // 從流讀出字元串,以空白分隔
getline(is, s) // 從流中讀出一行
s.empty()
s.size()
s[n]
s1 + s2
s1 = s2
s1 == s2
s1 != s2
<, <=, >, >=
s.push_back(char s)
s.pop_back()
s.begin()
s.end()
s.rbegin()
s.rend()
s.reverse()
s.clear()
s.back()
s.front()
s += "hello"
s.find(str, pos) // 沒找到傳回-, 傳回string::size_type類型,保證能夠在目前機器上存儲string對象大小
注意:
string.size() 傳回的是size_type類型,這是一個無符号類型,是以,在比較時要特别注意隐式轉換(有符号類型會隐式轉換成無符号類型),我們知道找不到傳回的是size_t 類型的 -1,這個數其實是可能的最大數。
另外string類型的加法,必須要有一個運算對象是string,(字元串字面值不是字元串string類型)
注意:
s = "hello" + "," + s; // 錯誤,因為"hello"是一個字元串字面值,不是字元串
- 處理string對象中的字元
#include <cctype>
string s = "hello world";
for (auto &c : s) { // 要修改字元,必須要使用引用
cout << isalnum(c) << endl;
tolower(c);
}
char a = 'a';
isalnum(a);
isalpha(a);
isdigit(a);
isspace(a);
tolower(a);
toupper(a);
3.3 标準庫類型vector
vector是标準庫裡的一類容器,表示對象的有序集合(不包含引用)。
- 定義和初始化vector對象
vector其記憶體占用空間是隻增不減,erase和clear都不能減少vector占用的記憶體。所有記憶體空間在vector析構的時候回收。
#include <vector>
using namespace std;
vector<T> v1;
vector<T> v2(v1);
vector<T> v2 = v1;
vector<T> v3(n, val);
vector<T> v4(n);
vector<T> v5{a, b, c};
vector<T> v5 = {a, b, c};
vector<string> v6 = {, "1"}; // 十個string對象
vector<int> v7 = {, }; // 兩個對象
- 操作
vector<int > arr;
arr.push_back();
arr.pop_back();
arr.empty();
arr.size();
arr.front();
arr.back();
vector<int> foo(, );
foo.swap(bar); // swap 是常數時間複雜度(僅僅交換了指針),是以常常用來清空vector,進行記憶體釋放
for (auto &e : arr) {
e = ; // 可以進行修改,但不要對arr進行添加或者删除
}
前面提到了vector怎樣清除記憶體占用的問題。這裡就要用到臨時對象以及析構的概念了。
vector<int> arr(, );
...
//建立臨時對象,然後調用swap函數,臨時對象會立即調用析構函數,進行記憶體釋放
vector<int>().swap(arr);
a.pop_back();
vector<int>(arr).swap(arr); // 釋放一個
注意:
vector 使用下标通路元素,必須是已經存在的下标,不要放低級錯誤
3.4 疊代器介紹
疊代器在标準庫中是一種通路容器對象更加通用的方式,隻有少數幾個容器是支援下标通路的,目前已知的有vector、string、deque、map、unordered_map。
- 使用疊代器
auto b = v.begin();
auto e = v.end(); // 記住auto的方法,非常簡便,不用寫過多的類型
*iter // 傳回對象引用
iter -> mem // 取名為mem的成員
++ iter
-- iter
iter1 == iter2
iter1 != iter2
注意:
再次提醒,任何改變容器對象數量,都會使得疊代器失效
- 疊代器運算
iter + n
iter - n
iter += n
iter -= n
iter1 - iter2
>, >=, <, <=
auto mid = vi.begin() + v.size() / ; // 取中點
3.5 數組
數組類似vector,但是數組的大小在定義時就已經确定了。
注意:
數組不允許使用 auto 關鍵字由初始化清單來推斷類型
- 定義和初始化
int a[]; // 預設初始化
// 顯示初始化
int arr[] = {, }; // 1 1 0 0 0
int arr[] = {, } // 1 1
int arr[] = {, , } // 錯誤,初始值過多
int arr[] = {} // 0 ...
// 字元數組,特殊在最後的空字元
char a1[] = {'a', 'b', 'c'} // 最後沒有空字元
char a2[] = {'a', '\0'} // 最後有顯示的空字元
char a3[] = "hello"; // 自動添加末尾空字元
char a4[] = "hello word" // 錯誤,沒有空間存放空字元
- 了解複雜數組
int &ref[] = ptr; // 錯誤,沒有引用數組
int *ptr[]; // ptr是一個數組,包含個int *類型指針的數組
int (*ptr)[]; // ptr是一個指針,指向含有個int類型整數的數組
int (&ptr_ref)[]; // ptr_ref是一個引用,綁定的是含有個int類型整數的數組
- C 風格字元串(安全風險,需要自己管理大小)
C 風格的字元串要求字元串數組要以空字元’\0’結尾。
#include <string.h>
strlen(p);
strcmp(p1, p2); // 傳回比較值,+、-、0
strcat(p1, p2); // p2 附加到p1 ,傳回p1
strcpy(p1, p2); // p2 拷貝給p1,傳回p1
3.6 多元數組
嚴格來說,c++沒有多元數組,通常說的多元數組都是數組的數組。是以,初始化、通路時都可以仔細了解一下。
int ia[3][4]; // 預設初始化,大小為3的數組,每個元素是4個整數的數組
int arr[100][200] = {0} // 初始化為0
int ia[3][4] = {
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10 , 11}
};
// 等價于
int ia[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
// 顯示初始化每行首元素
int ia[3][4] = {{0}, {3}, {4}};
// 顯示初始化第一行,其他的值初始化
int ia[3][4] = {0, 3, 6, 9};
- 指針和多元數組
時刻記住,多元數組實際上是數組的數組。
int ia[3][4];
int (*p)[4] = ia; // p 指向第一行的數組
p = &ia[2]; // p 指向ia的末尾行
// 值得注意的是,p是一個指向有四個整數的數組的指針,而數組ia就是一個指針,是以p是一個指針的指針
// 比如要找ia[1][1]
*(*(ia + 1) + 1);
*(ia[1] + 1);
*(&ia[0][0] + 4 * 1 + 1);
a // 等價于a[0],儲存的是指向第一行的數組的指針, 也就是a[0][0]
a + 1 // 等價于a[1], 儲存的是指向第二行的數組的指針, 也就是a[1][0]
- 可以使用typedef和using簡化多元數組的指針
using int_array = int[];
typedef int int_array[];