注:原創不易,轉載請務必注明原作者和出處,感謝支持!
注:內容來自某培訓課程,不一定完全正確!
一 STL基本概念
STL(Standard Template Library)標准模板庫,最早是惠普實驗室開發的一系列軟件的統稱,現在主要出現在C++中,但是在引入C++之前該技術已經存在很長的時間了。
STL從廣義上分為:容器(container),算法(alogrithm)和迭代器(iterator)。容器和算法之間通過迭代器進行無縫連接。STL幾乎所有的代碼都采用了模板類或者模板函數,這相比傳統的由函數和類組成的庫來說提供了更好的代碼重用機會。
在C++標准庫當中,隸屬於STL的占到了80%以上。在C++標准庫中,STL被組織成以下13個頭文件:
<algorithm>
<deque>
<functional>
<iterator>
<vector>
<list>
<map>
<memory>
<numeric>
<queue>
<set>
<stack>
<utility>
STL的優點有哪些?
(1)STL是C++的一部分,因此不用額外安裝什么,它被內建在你的編譯器之內。
(2)STL的一個重要的特點是數據結構和算法的分離。盡管這是個簡單的概念,但是這種分離確實使得STL變得非常通用。例如在STL的vector容器中,可以放入元素,基礎數據類型變量,元素的地址;STL的sort()排序函數可以用來操作vector, list等容器。
(3)程序員可以不用思考STL具體的實現過程,只要能夠熟練使用STL就OK了。這樣他們就可以把精力放在程序開發的別的方面。
(4)STL具有高可重用性,高性能,高移植性,跨平台的優點
- 高可重用性:STL中幾乎所有的代碼都采用了模板類和模板函數的方式實現,這相比於傳統的由函數和類組成的庫來說提供了更好的代碼重用的機會。
- 高性能:如map可以高效地從十萬條記錄里查找出指定的記錄,因為map是采用紅黑樹的變體實現的。(紅黑樹是平衡二叉樹的一種)
- 高移植性:在項目A上用STL編寫的模塊,可以直接移植到項目B上
- 跨平台:windows的visual studio上編寫的代碼可以在Mac OS的XCode上直接編譯
C++中的容器是指用來存放數據的類模板。容器分為兩種,分別是序列式容器和關聯式容器。它們的區別如下:
序列式容器:容器的元素的位置是由進入容器時機和地點來決定的
關聯式容器:容器有其自身的規則,進入容器元素的位置不是由進入的時機和地點決定的
迭代器可以理解為指針,對指針的操作基本都可以對迭代器操作。實際上,迭代器是一個類模板,這個類模板封裝了一個指針。迭代器一般用來遍歷容器中的元素。
算法可以理解為解決問題有限步驟。STL提供了大約100個實現算法的模板函數,比如算法for_each將為指定序列中的每個元素調用指定的函數等。這樣一來,只要我們熟悉了STL之后,許多代碼可以被大大的化簡,只需要通過調用一兩個算法函數,就可以完成所需要的功能並大大地提升效率。
從下面這個小例子可以窺見容器、迭代器和算法三者之間的關系。
// 算法,負責統計某個元素的個數
int MyCount(int *begin, int *end, int val)
{
int cnt = 0;
while (begin != end)
{
if (*begin == val)
++cnt;
++begin;
}
return cnt;
}
int main()
{
// 數組,容器
int arr[] = { 0, 7, 5, 4, 9, 2, 0 };
// 迭代器,開始和結束指針
int *pbegin = arr;
int *pend = &(arr[sizeof(arr) / sizeof(int)]);
int num = MyCount(pbegin, pend, 0);
cout << "num = " << num << endl;
getchar();
return 0;
}
下面的案例揭示了STL的基本使用語法。
void PrintVector(int v)
{
cout << v << endl;
}
// STL基本語法
void Test()
{
// 實例化一個包含int類型元素的容器
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
// STL提供的for_each算法
// 容器提供迭代器
vector<int>::iterator pBegin = v.begin();
vector<int>::iterator pEnd = v.end();
for_each(v.begin(), v.end(), PrintVector);
}
下面的案例演示了容器包含自定義類型的應用案例。
class Person
{
public:
Person() = default;
Person(int age, int id) : age(age), id(id) {}
void Show()
{
cout << "(age, id) = (" << age << ", " << id << ")" << endl;
}
private:
int age;
int id;
};
void Test2()
{
vector<Person> v;
v.push_back(Person(10, 20));
v.push_back(Person(30, 40));
v.push_back(Person(50, 60));
for (vector<Person>::iterator it = v.begin(); it != v.end(); ++it)
{
it->Show();
}
}
二 string容器
string的特性
說到string的特性,就不得不和char *
類型的字符串進行對比:
(1)char *
是一個指針,而string是一個類。string封裝了char *
,管理這個字符串,是一個char *
型的容器。
(2)string封裝了很多實用的成員方法。查找find,拷貝copy,刪除delete,替換replace和插入insert等
(3)string不用考慮內存釋放和越界問題。string管理char *
所分配的內存。每一次string的復制,取值都由string類負責維護,不用擔心復制越界和取值越界等。
string轉成char *
,實用成員方法c_str()
。char *
轉string,直接將char *
傳入string的構造方法中即可生成相應的string對象。
下面是string的初始化,賦值和取值操作
// string的初始化
void Test1()
{
string s1; // 無參構造,為空字符串
string s2(10, 'a');
string s3("hello");
string s4(s3); // 拷貝構造
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
}
// string的賦值操作
void Test2()
{
string s1;
string s2("app");
s1 = s2; // 重載等號運算符
cout << s1 << endl;
s1 = 'a';
cout << s1 << endl;
s1.assign("jkl"); // 賦值的成員方法assign
cout << s1 << endl;
}
// 取值操作
void Test3()
{
string s1 = "abcdefg";
// 重載[]操作符
for (int i = 0; i < s1.size(); ++i)
{
cout << s1[i] << endl;
}
// 成員方法at()
for (int i = 0; i < s1.size(); ++i)
{
cout << s1.at(i) << endl;
}
// 兩者區別
// []方式,如果訪問越界,程序直接崩潰
// at()方式,如果訪問越界,程序拋出out_of_range異常
try
{
// 直接奔潰
// cout << s1[100] << endl;
// 拋出異常
cout << s1.at(100) << endl;
}
catch (...) // 捕獲所有異常
{
cerr << "越界了!" << endl;
}
}
string的拼接操作
// 重載+=操作符
string &operator+=(const string &str);
string &operator+=(const char *str);
string &operator+=(const char c);
// append()方法
// 把字符串s連接到當前字符串尾部
string &append(const char *s);
// 把字符串s的前n個字符連接到當前字符串結尾
string &append(const char *s, int n);
// 同operator+=()
string &append(const string &str);
// 把字符串s中從pos開始的n個字符串連接到當前字符串結尾
string &append(const string &s, int pos, int n);
// 在當前字符串結尾添加n個字符c
string &append(int n, char c);
下面是字符串拼接的應用案例。
// string的拼接
void Test4()
{
string s1 = "abcd";
string s2 = "1111";
s1 += "abcd";
s1 += s2;
cout << s1 << endl;
string s3 = "2222";
s2.append(s3);
cout << s2 << endl;
string s4 = s2 + s3;
cout << s4 << endl;
}
string查找與替換
// 查找str第一次出現的位置,從Pos位置開始查找
int find(const string &str, int pos = 0) const;
// 查找s第一次出現的位置,從pos位置開始查找
int find(const char *s, int pos = 0) const;
// 從pos位置查找s的前n個字符第一次位置
int find(const char *s, int pos, int n) const;
// 查找字符c第一次出現的位置
int find(const char c, int pos = 0) const;
// 從pos位置開始查找str最后一次出現的位置
int rfind(const string &str, int pos = npos) const;
// 從pos位置開始查找s最后一次出現的位置
int rfind(const char *s, int pos = npos) const;
// 從pos查找s的前n個字符最后一次位置
int rfind(const char *s, int pos, int n) const;
// 查找字符c最后一次出現的位置
int rfind(const char c, int pos = 0) const;
// 替換從pos開始n個字符為字符串str
string &replace(int pos, int n, const string &str);
// 替換從pos開始的n個字符為字符串s
string &replace(int pos, int n, const char *s);
下面是字符串查找和替換的應用案例。
// 查找操作
void Test5()
{
string s1 = "abcdefghifgjklmn";
// 查找第一次出現的位置
int pos = s1.find("fg");
cout << "pos = " << pos << endl;
// 查找最后一次出現的位置
int rpos = s1.rfind("fg");
cout << "rpos = " << rpos << endl;
}
// string的替換
void Test6()
{
string s1 = "abcdefg";
s1.replace(0, 2, "111111");
cout << s1 << endl;
}
string比較
類似strcmp()
,string提供了成員方法compare()用於進行string的比較
int compare(const string &s) const;
int compare(const char *s) const;
string子串
// 返回由pos開始的n個字符串組成的字符串
string substr(int pos = 0, int n = npos) const;
string插入和刪除
// 插入字符串
string &insert(int pos, const char *s);
string &insert(int pos, const string &str);
// 在指定位置插入n個字符c
string &insert(int pos, int n, char c);
// 刪除從pos開始的n個字符
string &erase(int pos, int n = npos);
下面是string插入和刪除的應用案例。
// string插入和刪除
void Test7()
{
string s = "abcdefg";
s.insert(3, "111");
cout << s << endl;
s.erase(3, 3);
cout << s << endl;
}
三 vector容器
3.1 vector動態增長原理
當插入新元素的時候,如果空間不足,那么vector會重新申請更大的一塊內存空間,將原空間數據拷貝到新的空間,釋放舊空間。再把新元素插入到新申請的空間。
3.2 vector構造函數
// 采用模板實現類實現,默認構造函數
vector<T> v;
// 將v[begin(), end())區間中的元素作為初值進行初始化
vector(v.begin(), v.end());
// 將n個elem元素作為初值進行初始化
vector(n, elem);
// 拷貝構造函數
vector(const vector &vec);
下面是vector初始化的應用案例
// 初始化
void Test1()
{
// 默認構造
vector<int> v1;
int arr[] = { 10, 20, 30, 40, 50 };
vector<int> v2(arr, arr + sizeof(arr) / sizeof(int));
vector<int> v3(v2.begin(), v2.end());
vector<int> v4(v3);
printVector(v2);
printVector(v3);
printVector(v4);
}
3.3 vector常用賦值操作
assign(beg, end);
assign(n, elem);
// 重載等號運算符
vector &operator=(const vector &vec);
// 將vec與本身的元素互換
swap(vec);
下面是賦值操作的應用案例
// 常用賦值操作
void Test2()
{
int arr[] = { 10, 20, 30, 40, 50 };
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));
// 成員方法進行賦值
vector<int> v2;
v2.assign(v1.begin(), v1.end());
// 重載=號賦值
vector<int> v3;
v3 = v2;
// 將v1和v4的數據進行交換
vector<int> v4;
v4.push_back(100);
v4.push_back(200);
v4.push_back(300);
v4.swap(v1);
printVector(v1);
printVector(v4);
}
3.4 vector大小操作
// 返回容器中元素的個數
size()
// 判斷容器是否為空
empty();
// 重新制定容器長度為num,如果容器變長,則以默認值填充新位置。如果容器變短,則末尾超出容器長度的元素將被刪除
resize(int num);
// 容器的容量
capacity();
// 容器預留len個元素長度,預留位置不初始化,元素不可訪問
reserve(int len);
// 常用大小操作
void Test3()
{
int arr[] = { 100, 200, 300, 400 };
vector<int> v4(arr, arr + sizeof(arr) / sizeof(int));
cout << "v4.size() = " << v4.size() << endl;
if (v4.empty())
{
cout << "v4為空!" << endl;
}
printVector(v4);
v4.resize(2);
printVector(v4);
// 設定填充值為1
v4.resize(6, 1);
printVector(v4);
cout << "v4的容量:" << v4.capacity() << endl;
}
3.5 vector數據存取操作和插入刪除
// 返回索引idx所指的數據,如果idx越界,則拋出out_of_range異常
at(int idx);
// 返回索引idx所指的數據,越界時,運行直接報錯
operator[]
// 返回容器中的第一個數據元素
front();
// 返回容器中的最后一個數據元素
back();
// 往迭代器指向位置pos插入count個元素ele
insert(const_iterator pos, int count, T ele)
// 尾部插入元素
push_back();
// 刪除最后一個元素
pop_back();
// 刪除迭代器從start到end之間的元素
erase(const_iterator start, const_iterator end);
// 刪除迭代器指向的元素
erase(const_iterator pos);
// 清空容器中的所有元素
clear();
下面是vector的插入和刪除的應用案例
// 插入和刪除
void Test4()
{
vector<int> v;
v.push_back(10);
v.push_back(20);
// 頭插法
v.insert(v.begin(), 30);
v.insert(v.begin(), 40);
v.insert(v.end(), 50);
// vector支持隨機訪問,在位置v.begin()+2處插入60
v.insert(v.begin() + 2, 60);
printVector(v);
v.erase(v.begin());
printVector(v);
v.pop_back();
printVector(v);
v.clear();
cout << "v.size() = " << v.size() << endl;
}
3.6 使用swap()收縮空間
// swap()收縮空間
void Test5()
{
vector<int> v;
for (int i = 0; i < 100000; ++i)
v.push_back(i);
cout << "v.size() = " << v.size() << endl;
cout << "v.capacity() = " << v.capacity() << endl;
// swap()收縮空間,匿名對象隨后會被釋放空間
vector<int>(v).swap(v);
cout << "v.size() = " << v.size() << endl;
cout << "v.capacity() = " << v.capacity() << endl;
}
3.7 使用reserve()預留空間提高程序效率
reserve和resize有啥區別?
reserve是容器預留空間,但在空間內不真正創建元素對象,所以在沒有添加新對象之間,不能引用容器內的元素。resize是改變容器的大小,且在創建對象,因此,調用這個函數之后,就可以引用容器內的對象了。
// reserve預留空間
void Test6()
{
int num = 0;
int *addr = nullptr;
vector<int> v;
for (int i = 0; i < 100000; ++i)
{
v.push_back(i);
if (addr != &(v[0]))
{
++num;
addr = &(v[0]);
}
}
cout << "申請了" << num << "次內存!" << endl;
// 如果你知道容器大概要存儲多少內存空間,你可以使用reserve()預留空間
// 減少內存申請的次數以提高程序運行效率
num = 0;
addr = nullptr;
vector<int> u;
u.reserve(100000);
for (int i = 0; i < 100000; ++i)
{
u.push_back(i);
if (addr != &(u[0]))
{
++num;
addr = &(u[0]);
}
}
cout << "申請了" << num << "次內存!" << endl;
}