天天看點

字元串、向量和數組---C++ 基礎

前言

上一章,複習了基本資料類型,趁熱打鐵,繼續把字元串、向量(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[];