STL(標(biāo)準(zhǔn)模板庫(kù)),是目前C++內(nèi)置支持的library。它的底層利用了C++類模板和函數(shù)模板的機(jī)制,由三大部分組成:容器、算法和迭代器。
目前STL有六大組件
- 容器 container
- 算法 algorthm
- 迭代器 iterator
- 仿函數(shù) function object
- 適配器 adaptor
- 空間配置器 allocator
下面,我們會(huì)一一進(jìn)行介紹。
STL初探
容器是STL中很重要的一種數(shù)據(jù)結(jié)構(gòu)。常見(jiàn)的容器包括
- vector容器
- deque雙端數(shù)組
- stack棧模型
- queue隊(duì)列模型
- list鏈表模型
- priotriy_queue優(yōu)先級(jí)隊(duì)列
- set與multiset容器
- map與multimap容器
除了容器,STL還封裝了強(qiáng)大的算法,能夠?qū)崿F(xiàn)查找、刪除、替換、刪除等很多常見(jiàn)操作。后面會(huì)重點(diǎn)講解。
另外,迭代器也是STL重要的一環(huán),通過(guò)迭代器,我們可以很方便對(duì)容器中的元素進(jìn)行遍歷,以及操作容器。后面我們會(huì)穿插講解迭代器。
STL中的string
string
是STL的字符串類型,在C語(yǔ)言中,我們通常用char *
或者char[]
字符數(shù)組來(lái)表示字符串。C++的string
和C語(yǔ)言的char *
有什么區(qū)別呢?
-
string
是一個(gè)類,char *
是指向字符的指針 -
string
封裝了char *
,管理這個(gè)字符串,是一個(gè)char *
類型的容器 -
string
不用考慮內(nèi)存釋放和數(shù)組越界 -
string
提供了一些列的字符串操作函數(shù)
string的構(gòu)造函數(shù)
既然string是一個(gè)類,那么也就有構(gòu)造函數(shù),我們研究下string的構(gòu)造函數(shù)。
#include <iostream>
using namespace std;
int main(int argc, const char * argv[]) {
//通過(guò)const char * 初始化
string s1 = "aaaa";
//構(gòu)造函數(shù)初始化
string s2("bbbbb");
//通過(guò)拷貝構(gòu)造函數(shù)來(lái)初始化對(duì)象s3
string s3 = s2;
//用10個(gè)'a'字符來(lái)初始化字符串
string s4(10, 'a');
return 0;
}
字符串的遍歷
字符串的遍歷,有三種遍歷的方式
- 數(shù)組方式遍歷,通過(guò)[]操作符遍歷 (不會(huì)拋出異常)
- at()方法遍歷,根據(jù)index取值 (會(huì)拋出異常)
- 通過(guò)STL迭代器遍歷
int main(int argc, const char * argv[]) {
//創(chuàng)建字符串對(duì)象
string str("abcdefg");
//數(shù)組形式遍歷
for (int i = 0; i < str.length(); i++) {
cout<< str[i] << endl;
}
//at方法遍歷
for (int i = 0; i < str.length(); i++) {
cout << str.at(i) << endl;
}
//迭代器遍歷
for (string::iterator it = str.begin(); it != str.end(); it++) {
cout << *it << endl;
}
return 0;
}
數(shù)組方式和at方法方式,有一個(gè)明顯的不同
- 數(shù)組方式,如果出現(xiàn)越界或者其他錯(cuò)誤,不會(huì)拋出異常,程序直接終端。
- at()方法遍歷,出現(xiàn)越界或其他錯(cuò)誤,會(huì)拋出異常,程序可以處理異常。
迭代器其實(shí)可以看作是一個(gè)字符的指針,上個(gè)例子中string::iterator it = str.begin()
就是定義一個(gè)string類型的迭代器,指向str
的第一次位置。*it
就表示當(dāng)前的字符。注意str.end()
表示字符串最后一個(gè)字符的后面一個(gè)位置。如果it == str.end()
就表示已經(jīng)遍歷到終點(diǎn)了。
string與char *的轉(zhuǎn)換
string提供了成員函數(shù)c_str
來(lái)將string對(duì)象轉(zhuǎn)化成const char *
。string提供了copy(buf,size,begin)
成員函數(shù)來(lái)講string從begin
位置開(kāi)始的size
個(gè)字符拷貝到buf
中。需要注意的是:
- 如果buf容納不下,會(huì)越界
- 拷貝過(guò)去后,不會(huì)轉(zhuǎn)變成C風(fēng)格的字符串,也就是不會(huì)在buf后面添加'\0'
int main(int argc, const char * argv[]) {
//1 string轉(zhuǎn)char *
string str("aaaaaa");
const char *p = str.c_str();
//2 char *轉(zhuǎn)string
const char *p1 = "12345";
string str2 = p1;
//3 string拷貝到buf[]中
char buf[128] = {0};
//從0開(kāi)始,拷貝3個(gè)字符到buf指向的內(nèi)存空間
//如果buf空間容納不下,會(huì)越界
//拷貝過(guò)去時(shí),不會(huì)給buf末尾添加\0
str.copy(buf, 3, 0);
return 0;
}
string的拼接
string為我們提供了兩種字符串拼接方式,一種是重寫(xiě)了 +
操作符,我們可以直接將連個(gè)字符串相加,類似于java的語(yǔ)法。另一種是string提供了成員函數(shù)append()
供我們拼接連個(gè)字符串.
int main(int argc, const char * argv[]) {
string s1 = "123456";
string s2 = "abcdef";
//直接使用加號(hào)運(yùn)算符拼接
string s3 = s1 + s2;
//使用成員函數(shù)拼接
string s4 = s1.append(s2);
cout<<s3<<endl;
cout<<s4<<endl;
return 0;
}
string的查找和替換
string類提供了find
函數(shù),用來(lái)查找字符串中指定的字符。提供了replace
函數(shù),用來(lái)替換字符串中指定位置的字符串。
replace
函數(shù)是,先刪除指定位置,指定長(zhǎng)度的字符,然后在當(dāng)前指定位置插入新的字符。
int main(int argc, const char * argv[]) {
string s1 = "hello hello hello hello hello hello 1234 7876";
//從0位置開(kāi)始查找第一個(gè)hello出現(xiàn)的首位位置
size_t index1 = s1.find("hello",0);
cout << index1 << endl;
//查找第一個(gè)hello出現(xiàn)時(shí)的首位位置
size_t index2 = s1.find_first_of("hello");
cout << index2 << endl;
//查找最后一個(gè)hello出現(xiàn)時(shí)的末尾位置
size_t index3 = s1.find_last_of("hello");
cout << index3 << endl;
//求hello出現(xiàn)的次數(shù),以及對(duì)應(yīng)的下標(biāo)
int count = 0;
size_t offindex = s1.find("hello",0);
while (offindex != string::npos) { //如果 offindex != -1
//找到了
cout << "索引:" << offindex <<endl;
count++;
offindex++;
offindex = s1.find("hello", offindex);
}
//把hello替換成welcome
size_t offindex1 = s1.find("hello", 0);
while (offindex1 != string::npos) {
//從offindex1的位置開(kāi)始刪除5個(gè)位置,并插入新的字符串welcome
s1.replace(offindex1, strlen("hello"), "welcome");
//從后面的位置開(kāi)始
offindex1 += strlen("welcome");
//繼續(xù)查找
offindex1 = s1.find("hello", offindex1);
}
cout << "替換后的字符串:" << s1 <<endl;
return 0;
}
string區(qū)間刪除和插入
string提供了insert
和erase
分別實(shí)現(xiàn)插入和刪除操作。
插入:pos位置插入字符串s,返回新的string。
string &insert(int pos, const char *s)
string &insert(int pos, const string &s)
插入:pos位置插入n個(gè)字符c,返回string。
string &insert(int pos, int n, char c)
刪除:刪除從pos位置開(kāi)始的n個(gè)字符,返回新的string
string &erase(int pos, int n)
刪除:刪除指定迭代器的位置,返回當(dāng)前迭代器位置
string::iterator erase(string::iterator it)
刪除:刪除迭代器之間的字符,左閉右開(kāi)區(qū)間
string::iterator erase(string::iterator beginIt, string::iterator endIt)
int main(int argc, const char * argv[]) {
string s1 = "hello1 world!";
//1 刪除字符串中的'1'
//---通過(guò)find函數(shù),查找'1'所在的迭代器位置
string::iterator it = find(s1.begin(), s1.end(), '1');
//---刪除
if (it != s1.end()) {
s1.erase(it);
}
cout << s1 << endl;
//2 刪除起始迭代器位置的字符
s1.erase(s1.begin(), s1.begin() + 3);
cout << s1 << endl;
//3 在0位置插入"AAA"
s1.insert(0, "AAA");
cout << s1 << endl;
return 0;
}
string算法相關(guān)
目前常見(jiàn)的string的算法是大小寫(xiě)轉(zhuǎn)換。一般使用函數(shù)transform
來(lái)進(jìn)行轉(zhuǎn)換。
int main(int argc, const char * argv[]) {
string s1 = "abcdefg";
string s2 = "AEDLFLKJDLKJFL";
//小寫(xiě)全部轉(zhuǎn)換成大寫(xiě),轉(zhuǎn)換的結(jié)果放在s1.begin()的位置,后面的操作需要強(qiáng)制轉(zhuǎn)換成指定的函數(shù)類型
transform(s1.begin(), s1.end(), s1.begin(), (int (*)(int))toupper);
cout << s1 <<endl;
//大寫(xiě)全部轉(zhuǎn)換成小寫(xiě)
transform(s2.begin(), s2.end(), s2.begin(), (int (*)(int))tolower);
cout << s2 <<endl;
return 0;
}
STL中的vector容器
vector
是將元素放到動(dòng)態(tài)數(shù)組中加以管理的容器。vector
容器可以隨機(jī)存取元素,也就是說(shuō)支持[]
運(yùn)算符和at
方式存取。
-
vector
在尾部添加或者移除元素非常快,在中間操作非常耗時(shí),因?yàn)樗枰苿?dòng)元素
vector的基本用法
既然vector是容器,那么就可以向這個(gè)容器添加刪除元素。
基本用法:
-
front()
返回頭部元素的引用,可以當(dāng)左值 -
back()
返回尾部元素的引用,可以當(dāng)左值 -
push_back()
添加元素,只能尾部添加 -
pop_back()
移除元素,只能在尾部移除
int main(int argc, const char * argv[]) {
//定義一個(gè)vector容器
vector<int> v1;
//插入元素(尾部插入)
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
//迭代器遍歷打印
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
cout << *it << " ";
}
cout << endl;
//修改頭部元素的值(front()返回是引用,可以當(dāng)左值)
v1.front() = 44;
//輸出頭部元素
cout<< "頭部元素:" << v1.front() << endl;
//修改尾部的值(back()返回是引用,可以當(dāng)左值)
v1.back() = 99;
//輸出尾部元素
cout << "尾部元素" << v1.back() <<endl;
//刪除元素(從尾部刪除)
v1.pop_back();
//迭代器遍歷打印
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
cout << *it << " ";
}
cout << endl;
return 0;
}
vector的初始化
vector有4種方式初始化,有直接初始化,也要通過(guò)拷貝構(gòu)造函數(shù)初始化。
int main(int argc, const char * argv[]) {
//直接構(gòu)造函數(shù)初始化
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
//通過(guò)拷貝構(gòu)造函數(shù)初始化
vector<int> v2 = v1;
//使用部分元素來(lái)構(gòu)造
vector<int> v3(v1.begin(), v1.begin() + 1);
vector<int> v4(v1.begin(), v1.end());
//存放三個(gè)元素,每個(gè)元素都是9
vector<int> v5(3,9);
return 0;
}
vector的遍歷
vector的遍歷有多種方式,可以根據(jù)[]
或者迭代器遍歷。
需要主要的是:
-
[]
方式,如果越界或出現(xiàn)其他錯(cuò)誤,不會(huì)拋出異常,可能會(huì)崩潰,可能數(shù)據(jù)隨機(jī)出現(xiàn) -
at
方式,如果越界或出現(xiàn)其他錯(cuò)誤,會(huì)拋出異常,需要捕獲異常并處理 - 迭代器提供了逆向遍歷,可以通過(guò)迭代器來(lái)實(shí)現(xiàn)逆向遍歷,當(dāng)然上面兩種方式也可以
int main(int argc, const char * argv[]) {
//創(chuàng)建vector
vector<int> v1;
//插入元素
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
//遍歷-[]取值
for (int i = 0; i < v1.size(); i++) {
cout << v1[i] << " ";
}
cout << endl;
//遍歷-at取值
for (int i = 0; i < v1.size(); i++) {
cout << v1.at(i) << " ";
}
cout << endl;
//遍歷-迭代器遍歷
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
cout << *it << " ";
}
cout << endl;
//遍歷-迭代器逆向遍歷
for (vector<int>::reverse_iterator it = v1.rbegin(); it != v1.rend(); it++) {
cout << *it << " ";
}
cout << endl;
//測(cè)試越界
cout << "[]越界:" << v1[20] << endl; //不會(huì)拋出異常,可能會(huì)崩潰,可能會(huì)亂碼
cout << "at越界:" << v1.at(20) << endl; //會(huì)拋出異常,需要捕獲異常
return 0;
}
vector的push_back強(qiáng)化
push_back是在當(dāng)前vector的內(nèi)存末尾拷貝元素進(jìn)入容器。注意這個(gè)地方可能產(chǎn)生淺拷貝,所以容器中的對(duì)象要支持拷貝操作。另外,如果vector初始化了個(gè)數(shù),而不初始化具體的值,push_back也只會(huì)在最后面追加。
int main(int argc, const char * argv[]) {
//初始化10個(gè)元素的容器
vector<int> v(10);
//打印容器大小
cout << v.size() << endl;
//push_back添加元素
v.push_back(100);
//打印容器大小
cout << v.size() << endl;
//遍歷后的結(jié)果是 0 0 0 0 0 0 0 0 0 0 100
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
return 0;
}
vector的元素刪除
vector的刪除,是根據(jù)位置進(jìn)行刪除,如果想要?jiǎng)h除某個(gè)元素,需要找到當(dāng)前元素的迭代器位置,再進(jìn)行刪除。
erase(iterator)
函數(shù),刪除后會(huì)返回當(dāng)前迭代器的下一個(gè)位置。
int main(int argc, const char * argv[]) {
//1 創(chuàng)建容器并初始化
vector<int> v1(10);
for (int i = 0; i < v1.size(); i++) {
v1[i] = i;
}
//2 區(qū)間刪除
//--2.1 刪除前3個(gè)元素
v1.erase(v1.begin(), v1.begin() + 3);
//--2.2 刪除指定位置的元素
v1.erase(v1.begin() +3);
//3 根據(jù)元素的值進(jìn)行刪除,刪除值為2的元素
v1.push_back(2);
v1.push_back(2);
vector<int>::iterator it = v1.begin();
while (it != v1.end()) {
if (*it == 2) {
it = v1.erase(it); //刪除后,迭代器指針會(huì)執(zhí)行下一個(gè)位置并返回。
}else{
it++;
}
}
//4 遍歷打印
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
cout << *it << " ";
}
cout << endl;
return 0;
}
vector的插入元素
vector提供了insert
函數(shù),結(jié)合迭代器位置插入指定的元素。
如果迭代器位置越界,會(huì)拋出異常。
int main(int argc, const char * argv[]) {
//初始化vector對(duì)象
vector<int> v1(10);
//在指定的位置插入元素10的拷貝
v1.insert(v1.begin() + 3, 10);
//在指定的位置插入3個(gè)元素11的拷貝
v1.insert(v1.begin(), 3, 11);
//遍歷
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
cout << *it << " ";
}
cout << endl;
return 0;
}
STL中的deque容器
deque是一個(gè)雙端數(shù)組容器:可以在頭部和尾部操作元素。
-
push_back
從尾部插入元素 -
push_front
從頭部插入元素 -
pop_back
從尾部刪除元素 -
pop_front
從頭部刪除元素
知識(shí)點(diǎn):
distance
函數(shù)可以求出當(dāng)前的迭代器指針it距離頭部的位置,也就是容器的指針用法:
distance(v1.begin(), it)
int main(int argc, const char * argv[]) {
//定義deque對(duì)象
deque<int> d1;
//尾部插入元素
d1.push_back(10);
d1.push_back(20);
d1.push_back(30);
//頭部插入元素
d1.push_front(1);
d1.push_front(2);
d1.push_front(3);
//尾部刪除元素
d1.pop_back();
//頭部刪除元素
d1.pop_front();
//修改頭部和尾部的值
d1.front() = 111;
d1.back() = 222;
//查找元素為1的下標(biāo)
//通過(guò)distance求取下標(biāo)
deque<int>::iterator it = d1.begin();
while (it != d1.end()) {
if (*it == 1) {
cout << "下標(biāo):" << distance(d1.begin(), it) << endl;
}
it++;
}
//遍歷
for (deque<int>::iterator it = d1.begin(); it != d1.end(); it++) {
cout << *it << " ";
}
cout << endl;
return 0;
}
STL中的stack棧容器
在數(shù)據(jù)結(jié)構(gòu)中,棧是一種先入后出的容器,增加元素叫壓棧或者入棧。移除元素通常叫做出棧。
STL提供的stack容器,也是這種基本類型。這里我們演示一下基本元素類型和復(fù)雜元素類型。
▽ 基礎(chǔ)數(shù)據(jù)類型的stack
int main(int argc, const char * argv[]) {
//定義stack對(duì)象
stack<int> s1;
//入棧
s1.push(1);
s1.push(2);
s1.push(3);
s1.push(4);
//打印棧頂元素,并出棧
while (!s1.empty()) {
//取出棧頂元素
cout << "當(dāng)前棧頂元素" << s1.top() << endl;
//獲取棧的大小
cout << "當(dāng)前棧的大小" << s1.size() << endl;
//出棧
s1.pop();
}
return 0;
}
▽ 復(fù)雜數(shù)據(jù)類型的stack
//定義類
class Teacher {
public:
char name[32];
int age;
void printT()
{
cout << "age = " << age << endl;
}
};
int main(int argc, const char * argv[]) {
Teacher t1, t2, t3;
t1.age = 22;
t2.age = 33;
t3.age = 44;
//定義棧容器
stack<Teacher> s1;
//入棧
s1.push(t1);
s1.push(t2);
s1.push(t3);
//出棧并打印
while (!s1.empty()) {
//打印棧頂元素
Teacher tmp = s1.top();
tmp.printT();
//出棧
s1.pop();
}
return 0;
}
STL中的queue隊(duì)列容器
隊(duì)列是一種數(shù)據(jù)結(jié)構(gòu),具備隊(duì)頭和隊(duì)尾。常見(jiàn)的有FIFO(先入先出)隊(duì)列等。
#include <queue>
void main()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
cout << "對(duì)頭元素" << q.front() <<endl;
cout << "隊(duì)列的大小 " << q.size() <<endl;
while (!q.empty())?{
int tmp = q.front();
cout << tmp << " ";
q.pop();
}
}
class Teacher
{
public:
int age;
char name[32];
void printT()
{
cout << "age :" << age <<endl;
}
}
void main()
{
Teacher t1,t2,t3;
t1.age = 31;
t2.age = 32;
t3.age = 33;
queue<Teacher > q;
q.push(t1);
q.push(t2);
q.push(t3);
while (!q.empty())?{
Teacher tmp = q.front();
tmp.printT();
q.pop();
}
}
STL中的list容器
list容器具有如下特性:
- 可以在頭部和尾部插入和刪除元素
- 不能隨機(jī)訪問(wèn)元素,也就是迭代器只能只能++,不能一次性跳轉(zhuǎn)
list的基本操作
int main(int argc, const char * argv[]) {
//創(chuàng)建list對(duì)象
list<int> l;
//尾部添加元素
for (int i = 0; i < 10; i++) {
l.push_back(i);
}
//頭部添加元素
l.push_front(111);
//遍歷
for (list<int>::iterator it = l.begin(); it != l.end(); it++) {
cout << *it << " ";
}
cout << endl;
//list不能隨機(jī)訪問(wèn)
list<int>::iterator it = l.begin();
it++;
it++;
cout << *it <<endl;
// it = it + 1; //編譯報(bào)錯(cuò),不能隨機(jī)訪問(wèn)
// it = it + 5; //編譯報(bào)錯(cuò),不能隨機(jī)訪問(wèn)
return 0;
}
list的刪除
list提供了兩個(gè)函數(shù)用來(lái)刪除元素,分別是erase
和remove
。
-
erase
是通過(guò)位置或者區(qū)間來(lái)刪除,主要結(jié)合迭代器指針來(lái)操作 -
remove
是通過(guò)值來(lái)刪除
int main(int argc, const char * argv[]) {
//創(chuàng)建list對(duì)象
list<int> l;
//添加數(shù)據(jù)
for (int i = 0; i < 10; i++) {
l.push_back(i);
}
l.push_back(100);
l.push_back(100);
//刪除頭部元素
l.erase(l.begin());
//刪除某個(gè)區(qū)間
list<int>::iterator it = l.begin();
it++;
it++;
it++;
l.erase(l.begin(), it);
//移除值為100的所有元素
l.remove(100);
//遍歷
for (list<int>::iterator it = l.begin(); it != l.end(); it++) {
cout << *it << " ";
}
cout << endl;
return 0;
}
STL中的priority_queue優(yōu)先級(jí)隊(duì)列容器
優(yōu)先級(jí)隊(duì)列分為:最小值優(yōu)先隊(duì)列和最大值優(yōu)先隊(duì)列。
此處的最大值、最小值是指隊(duì)頭的元素(增序、降序)。默認(rèn),是創(chuàng)建最大值優(yōu)先級(jí)隊(duì)列。
定義優(yōu)先級(jí)的方法:
-
priority_queue<int>
默認(rèn)定義int類型的最大值隊(duì)列 -
priority_queue<int, vector<int>, less<int>>
定義int型的最大值優(yōu)先隊(duì)列 -
priority_queue<int, vector<int>, greater<int>>
定義int型的最小值隊(duì)列
上面的定義中,less
和greater
相當(dāng)于謂詞,是預(yù)定義好的排序函數(shù),我們稱之為“仿函數(shù)”。后面會(huì)介紹它的用法。
void main()
{
//定義優(yōu)先級(jí)隊(duì)列(默認(rèn)是最大值優(yōu)先級(jí)隊(duì)列)
priority_queue<int> p1;
//定義一個(gè)最大優(yōu)先級(jí)隊(duì)列
//less是提前定義好的預(yù)定義函數(shù) 相當(dāng)于謂詞
priority_queue<int, vector<int>, less<int>> p2;
//定義一個(gè)最小值優(yōu)先級(jí)隊(duì)列v
priority_queue<int, vector<int>, greater<int>> p3;
//給默認(rèn)的最大優(yōu)先級(jí)隊(duì)列入棧
p1.push(33);
p1.push(11);
p1.push(55);
p1.push(22);
//打印最大優(yōu)先級(jí)的對(duì)頭元素
cout<<"對(duì)頭元素:"<< p1.top() <<endl;
cout<<"隊(duì)列的大小:"<< p1.size() <<endl;
//給最小值優(yōu)先級(jí)隊(duì)列入棧
p3.push(33);
p3.push(11);
p3.push(55);
p3.push(22);
//打印最小優(yōu)先級(jí)隊(duì)列的對(duì)頭元素
cout<<"對(duì)頭元素:"<< p3.top() <<endl;
cout<<"隊(duì)列的大小:"<< p3.size() <<endl;
}
STL中的set和multiset集合容器
set是一個(gè)集合,Objective-C
語(yǔ)言中也有集合的概念。C++中的集合與OC中的集合也有不同的地方。
- C++的set容器,其中包含的元素是唯一的,而且是有序的。
- C++的set容器,是按照順序插入的,不能在指定位置插入。
- C++的set容器,其結(jié)構(gòu)是紅黑二叉樹(shù),插入數(shù)據(jù)的效率比vector快
set元素的插入和刪除
set提供了insert
和erase
函數(shù),用來(lái)對(duì)元素進(jìn)行插入和刪除操作。
- 基礎(chǔ)類型數(shù)據(jù),如果插入的是重復(fù)的元素,則插入失敗,返回值是一個(gè)pair類型
- pair類型類似于swift語(yǔ)言中的元組的概念,通過(guò)其判斷是否插入成功
- 復(fù)雜類型數(shù)據(jù),需要通過(guò)仿函數(shù)來(lái)確定元素的順序,進(jìn)入判斷是否是重復(fù)元素。在“自定義對(duì)象的排序”里面講解。
void main()
{
set<int> set1;
//插入元素
for (int i = 0; i<5; i++) {
int tmp = rand();
set1.insert(tmp);
}
//重復(fù)插入元素(會(huì)插入不成功,下一節(jié)會(huì)分析如果根據(jù)返回值判斷是否插入成功)
set1.insert(100);
set1.insert(100);
set1.insert(100);
set1.insert(100);
for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
cout << *it <<" ";
}
//刪除集合
while(!set1.empty())
{
//獲取頭部
set<int>::iterator it = set1.begin();
//打印頭部元素
cout << *it << endl;
//從頭部刪除元素
set1.erase(set1.begin());
}
}
普通數(shù)據(jù)類型的排序
set容器是有序的集合,默認(rèn)的順序是從小到大的。
創(chuàng)建集合的方式:
-
set<int>
創(chuàng)建默認(rèn)的從小到大的int類型的集合 -
set<int,less<int>>
創(chuàng)建一個(gè)從小打到大的int類型的集合 -
set<int,greater<int>>
創(chuàng)建一個(gè)從大到小的int類型的集合
上面的less和greater就是仿函數(shù),集合會(huì)根據(jù)這個(gè)仿函數(shù)的返回值是否為真類進(jìn)行排序。
//仿函數(shù)的原型,下面是C++提供的默認(rèn)的greater的仿函數(shù)(刪除了宏定義后的)
struct greater
{
bool operator()(const int &left, const int &right) const
{
//如果左值>右值,為真。從大到小的排列
return left > right;
}
};
我們可以測(cè)試下,添加進(jìn)set集合的元素確實(shí)是有順的。
void main()
{
//默認(rèn),從小到大
set<int> set1;
//從小到大--默認(rèn)就是
set<int, less<int>> set2;
//從大到小
set<int, greater<int>> set3;
//添加元素
for (int i = 0; i < 5; i++) {
int tmp = rand();
set3.insert(tmp);
}
//遍歷
for (set<int>::iterator it = set3.begin(); it != set3.end(); it++) {
cout<< *it << " ";
}
}
自定義對(duì)象的排序
上面,我們看到了基礎(chǔ)的數(shù)據(jù)類型的集合,是可以排序的。但是,如果集合里面放的是特殊的自定義對(duì)象,該如何滿足set有序的特性呢?
通過(guò)上面的例子,我們知道,基礎(chǔ)數(shù)據(jù)類型的set是有序的關(guān)鍵原因是greater和less仿函數(shù)。所以,自定義對(duì)象的有序也是通過(guò)我們自定義仿函數(shù)來(lái)保證的。
仿函數(shù),之所以叫仿函數(shù),是因?yàn)樗瘮?shù)很像,但并不是一個(gè)函數(shù)。它的結(jié)果如下,只要我們實(shí)現(xiàn)了這個(gè)仿函數(shù),我們也可以對(duì)自定義對(duì)象進(jìn)行排序。
//定義仿函數(shù)的結(jié)構(gòu)體
struct FunctionName
{
//重載了()運(yùn)算符,實(shí)現(xiàn)兩個(gè)自定義對(duì)象的比較
bool opeartor() (Type &left, Type &right)
{
//左值大于右值,從大到小的順序
if(left > right)
return true;
else
return false;
}
};
下面,我們自定義一個(gè)Student對(duì)象,根據(jù)年齡進(jìn)行排序,將對(duì)象加入到set集合中,并進(jìn)行打印。
如果仿函數(shù)實(shí)現(xiàn)了根據(jù)年齡進(jìn)行排序,因?yàn)閟et是元素唯一的,所以在插入對(duì)象的時(shí)候,如果年齡是重復(fù)的,則插入不進(jìn)去了。
//定義student對(duì)象
class Student {
public:
Student(const char *name, int age)
{
strcpy(this->name, name);
this->age = age;
}
public:
char name[64];
int age;
};
//提供仿函數(shù),用于自定義對(duì)象的set進(jìn)行排序,要寫(xiě)一個(gè)仿函數(shù),用來(lái)排序
struct FuncStudent
{
//重載了括號(hào)操作符,用來(lái)比較大小
bool operator() (const Student &left, const Student &right)
{
//如果左邊比右邊小,從小到大按照年齡排序
if(left.age < right.age)
return true;
else
return false;
}
};
void main()
{
Student s1("s1",32);
Student s2("s2",22);
Student s3("s3",44);
Student s4("s4",11);
Student s5("s5",22);
//創(chuàng)建集合,采用從小到大的排序
set<Student, FuncStudent> set1;
//插入數(shù)據(jù)
set1.insert(s1);
set1.insert(s2);
set1.insert(s3);
set1.insert(s4);
//插入不進(jìn)去(年齡有重復(fù)的,所以插不進(jìn)去了),要通過(guò)返回值來(lái)確保是否插入成功
set1.insert(s5);
//遍歷
for (set<Student>::iterator it = set1.begin(); it != set1.end(); it++) {
cout << it->age << "\t" << it->name <<endl;
}
}
pair類型的返回值
pair類型,就類似于Swift語(yǔ)言中的“元組”的概念,這個(gè)類型包含了多個(gè)數(shù)據(jù)類型,在函數(shù)返回的時(shí)候,可以同時(shí)返回多個(gè)值。
我們來(lái)看一下pair類型的定義它實(shí)際上是一個(gè)結(jié)構(gòu)體。它包含了兩個(gè)屬性,first
和second
。
template <class _T1, class _T2>
struct pair
{
typedef _T1 first_type;
typedef _T2 second_type;
_T1 first;
_T2 second;
}
上面的例子中,我們知道set集合中的元素是唯一的,重復(fù)的元素插入會(huì)失敗。如果判斷是否插入成功,我們可以通過(guò)insert
函數(shù)的返回值來(lái)判斷,它的返回值是一個(gè)pair
類型。我們來(lái)看一下insert
函數(shù)的原型:
pair<iterator,bool> insert(const value_type& __v)
返回的是pair<iterator, bool>
類型,pair的第一個(gè)屬性表示當(dāng)前插入的迭代器的位置,第二個(gè)屬性表示插入是否成功的bool值。所以,我們可以通過(guò)第二個(gè)屬性來(lái)判斷元素是否插入成功。
//pair的使用判斷set的insert函數(shù)的返回值
void test3()
{
Student s1("s1",32);
Student s2("s2",22);
Student s3("s3",44);
Student s4("s4",11);
Student s5("s5",22);
//創(chuàng)建集合,采用從小到大的排序
set<Student, FuncStudent> set1;
//插入數(shù)據(jù),接收返回值
pair<set<Student, FuncStudent>::iterator, bool> pair1 = set1.insert(s1);
if (pair1.second == true) {
cout << "插入s1成功" <<endl;
}else{
cout << "插入s1失敗" <<endl;
}
}
set容器數(shù)據(jù)的查找
set容器提供了多個(gè)函數(shù)用來(lái)查找元素
-
iterator find(const key_type& __k)
find函數(shù)查找元素為k的迭代器位置 -
iterator lower_bound(const key_type& __k)
lower_bound函數(shù)查找小于等于元素k的迭代器位置 -
iterator upper_bound(const key_type& __k)
upper_bound函數(shù)查找大于元素k的迭代器位置 -
pair<iterator,iterator> equal_range(const key_type& __k)
equal_range函數(shù)返回一個(gè)pair類型,第一個(gè)屬性表示大于等于k的迭代器位置,第二個(gè)是大于k的迭代器位置
void test4()
{
set<int> set1;
for (int i = 0; i < 10; i++) {
set1.insert(i+1);
}
//遍歷
for (set<int>::iterator it = set1.begin(); it != set1.end(); it++) {
cout << *it <<endl;
}
//查找元素是5的迭代器位置
set<int>::iterator it0 = set1.find(5);
cout << "it0:" << *it0 <<endl;
//查找小于等于5的元素迭代器位置
set<int>::iterator it1 = set1.lower_bound(5);
cout << "it1:" << *it1 <<endl;
//查找大于5的元素迭代器位置
set<int>::iterator it2 = set1.upper_bound(5);
cout << "it2:" << *it2 <<endl;
//返回的pair第一個(gè)迭代器是>=5,另一個(gè)是>5
pair<set<int>::iterator, set<int>::iterator> mypair = set1.equal_range(5);
set<int>::iterator it3 = mypair.first;
set<int>::iterator it4 = mypair.second;
cout << "it3:" << *it3 <<endl;
cout << "it4:" << *it4 <<endl;
}
multiset容器
multiset容器,與set容器相似,但是multiset容器中的元素可以重復(fù)。另外,他也是自動(dòng)排序的,容器內(nèi)部的值不能隨便修改,因?yàn)橛许樞虻摹?/p>
void test5()
{
//定義multiset
multiset<int> set1;
//從鍵盤(pán)不停的接收值
int tmp = 0;
printf("請(qǐng)輸入multiset集合的值:");
scanf("%d", &tmp);
while (tmp != 0) {
set1.insert(tmp);
scanf("%d", &tmp);
}
//迭代器遍歷
for (multiset<int>::iterator it = set1.begin(); it != set1.end(); it++) {
cout<< *it <<" ";
}
cout <<endl;
//刪除
while (!set1.empty()) {
multiset<int>::iterator it = set1.begin();
cout << *it << " ";
set1.erase(it);
}
}
STL中的map和multimap映射容器
map和multimap是一個(gè)鍵值映射的容器。map中的鍵值對(duì)都是唯一的,但是multimap中一個(gè)鍵可以對(duì)應(yīng)多個(gè)值。
- map和multimap是關(guān)聯(lián)式容器,鍵值成對(duì)存在
- map和multimap是紅黑變體的平衡二叉樹(shù)結(jié)構(gòu)
- map只支持唯一的鍵值對(duì),集合中的元素是按照一定的順序排列的
- multimap中的鍵可以出現(xiàn)多次
- map和multimap的元素插入過(guò)程是按照順序插入的
map元素的增刪改查
map元素的插入,有兩種方式:
- 調(diào)用insert函數(shù)插入pair類型的鍵值對(duì)
- 直接使用[]來(lái)對(duì)鍵進(jìn)行復(fù)制,類似于Objective-C中的NSMutableDictionary賦值一樣。
map的insert函數(shù)返回的是pair類型,pair的第二個(gè)參數(shù)表示是否插入成功。如果插入的元素鍵名相同,則插入失敗。
map元素的刪除,跟上面其他的容器一樣,都是直接調(diào)用erase函數(shù).
int main()
{
map<int, string> map1;
//insert方法插入
//--1 通過(guò)pair<int, string>(1,”chenhua“) 構(gòu)造pair元素
map1.insert(pair<int, string>(1,"chenhua"));
//--2 通過(guò)make_pair構(gòu)造pair元素
map1.insert(make_pair(2,"mengna"));
//--3 通過(guò)value_type構(gòu)造pair元素
map1.insert(map<int, string>::value_type(3,"chenmeng"));
//[]直接插入
map1[4] = "menghua";
//重復(fù)插入(插入會(huì)不成功)
pair<map<int, string>::iterator, bool> pair1 = map1.insert(make_pair(2, "haha"));
if (pair1.second) {
cout << "重復(fù)插入成功" << endl;
}else{
cout << "重復(fù)插入失敗" << endl;
}
//元素的修改
//map[1] = "22"的方式,如果不存在鍵則插入,存在鍵則修改
map1[2] = "haha";
//元素的刪除
//--刪除值為"haha"的元素
for (map<int, string>::iterator it = map1.begin(); it != map1.end(); it++) {
if (it->second.compare("haha") == 0) {
map1.erase(it);
}
}
//遍歷
for (map<int, string>::iterator it = map1.begin(); it != map1.end(); it++) {
cout << it->first << "\t" << it->second << endl;
}
return 0;
}
map元素的查找
map提供了兩個(gè)函數(shù)進(jìn)行key的查找:find和equal_range。
int main()
{
//定義map
map<int ,string> map1;
map1[1] = "chenhua";
map1[2] = "mengna";
//查找key=100的鍵值對(duì)
map<int, string>::iterator it = map1.find(100);
if (it != map1.end()) {
cout << "存在key=100的鍵值對(duì)";
}else{
cout << "不存在" << endl;
}
//查找key = 1000的位置
//返回兩個(gè)迭代器,第一個(gè)表示<=1000的迭代器位置,第二個(gè)是>1000的迭代器位置
pair<map<int, string>::iterator, map<int, string>::iterator> mypair = map1.equal_range(1000);
if (mypair.first == map1.end()) {
cout << "大于等于5的位置不存在" << endl;
}else{
cout << mypair.first->first << "\t" << mypair.first->second << endl;
}
if (mypair.second == map1.end()) {
cout << "大于5的位置不存在" << endl;
}else{
cout << mypair.second->first << "\t" << mypair.second->second << endl;
}
return 0;
}
multimap容器
multimap容器,與map容器的唯一區(qū)別是:multimap支持多個(gè)鍵值。
由于支持多個(gè)鍵值,multimap提供了cout函數(shù)來(lái)計(jì)算同一個(gè)key的元素個(gè)數(shù)。
class Person {
public:
string name; //姓名
int age; //年齡
string tel; //電話
double sal; //工資
};
void test()
{
Person p1,p2,p3,p4,p5;
p1.name = "王1";
p1.age = 31;
p2.name = "王2";
p2.age = 31;
p3.name = "張3";
p3.age = 31;
p4.name = "張4";
p4.age = 31;
p5.name = "錢(qián)5";
p5.age = 31;
multimap<string, Person> map2;
//sale部門(mén)
map2.insert(make_pair("sale", p1));
map2.insert(make_pair("sale", p2));
//development部門(mén)
map2.insert(make_pair("development", p3));
map2.insert(make_pair("development", p4));
//Finanncial部門(mén)
map2.insert(make_pair("Finanncial", p5));
//遍歷
for (multimap<string, Person>::iterator it = map2.begin(); it != map2.end(); it++) {
cout << it->first << "\t" << it->second.name << endl;
}
//按部門(mén)顯示員工信息
int developNum = (int) map2.count("development");
cout << "development部門(mén)人數(shù):" << developNum << endl;
multimap<string,Person>::iterator it2 = map2.find("development");
int tag = 0;
while (it2 != map2.end() && tag < developNum) {
cout << it2->first << "\t" << it2->second.name <<endl;
it2 ++;
tag ++;
}
//把a(bǔ)ge=32 修改name= 32
for (multimap<string, Person>::iterator it = map2.begin(); it != map2.end(); it++) {
if (it->second.age == 32) {
it->second.name = "32";
}
}
}
int main(int argc, const char * argv[]) {
test();
return 0;
}
STL容器的通用性探究
到這里,STL的容器我們基本講解完畢了。STL的容器主要利用了C++的模板特性來(lái)實(shí)現(xiàn)。需要注意:
- 容器緩存了節(jié)點(diǎn),節(jié)點(diǎn)類要確保支持拷貝(否則出現(xiàn)淺拷貝問(wèn)題,導(dǎo)致崩潰)
- 容器中的一般節(jié)點(diǎn)類,需要提供拷貝構(gòu)造函數(shù),并重載等號(hào)操作符(用來(lái)賦值)
- 容器在插入元素時(shí),會(huì)自動(dòng)進(jìn)行元素的拷貝。
針對(duì)容器,容器之間也支持拷貝。所以需要注意:
- 除了queue和stack外,每個(gè)容器都提供了可返回迭代器的函數(shù),運(yùn)用返回的跌打器就可以訪問(wèn)元素
- 通常STL不會(huì)拋出異常,要求使用者確保傳入正確的參數(shù)
- 每個(gè)容器都提供了一個(gè)默認(rèn)構(gòu)造函數(shù)和一個(gè)默認(rèn)拷貝構(gòu)造函數(shù)
STL容器的元素拷貝
下面,我們演示一下,如果容器元素如果沒(méi)有實(shí)現(xiàn)拷貝構(gòu)造函數(shù),出現(xiàn)淺拷貝后的崩潰問(wèn)題。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Student {
public:
Student(const char *name, int age)
{
cout << "構(gòu)造函數(shù)" << endl;
//分配內(nèi)存空間
m_name = new char[strlen(name) + 1];
//值拷貝
strcpy(m_name, name);
m_age = age;
}
~Student()
{
printf("%p 指向的空間 調(diào)用析構(gòu)函數(shù)\n", m_name);
if (m_name != NULL) {
delete []m_name;
m_age = 0;
}
}
private:
char *m_name;
int m_age;
};
int main()
{
Student s1("chenhua",24);
vector<Student> v1;
v1.push_back(s1);
return 0;
}
上面的代碼段,運(yùn)行后的結(jié)果如下:
構(gòu)造函數(shù)
0x100302a00 指向的空間 調(diào)用析構(gòu)函數(shù)
0x100302a00 指向的空間 調(diào)用析構(gòu)函數(shù)
運(yùn)行后,打印出結(jié)果后并報(bào)錯(cuò)。報(bào)錯(cuò)原因是同一個(gè)內(nèi)存空間被釋放了2次,導(dǎo)致的崩潰。其根本原因是,v1將s1拷貝到容器,由于Student沒(méi)有重寫(xiě)拷貝構(gòu)造函數(shù),從而出現(xiàn)了淺拷貝,只拷貝了地址。釋放的時(shí)候毫無(wú)疑問(wèn)出現(xiàn)錯(cuò)誤。
如果我們給Student重寫(xiě)了拷貝構(gòu)造函數(shù)和重載了等號(hào)操作符,則上面的錯(cuò)誤就不會(huì)出現(xiàn)。
//重寫(xiě)拷貝構(gòu)造函數(shù)
Student(const Student &obj)
{
//分配內(nèi)存空間
m_name = new char[strlen(obj.m_name) + 1];
//值拷貝
strcpy(m_name, obj.m_name);
m_age = obj.m_age;
}
//重載等號(hào)運(yùn)算符
Student & operator=(const Student &obj)
{
//釋放舊值
if (m_name != NULL) {
delete [] m_name;
m_age = 0;
}
//分配內(nèi)存空間并賦值
m_name = new char[strlen(obj.m_name) + 1];
strcpy(m_name, obj.m_name);
m_age = obj.m_age;
return *this;
}
STL容器的比較
STL提供了很多容器,每種容器有其自身的特點(diǎn),我們?cè)撊绾问褂盟鼈兡兀?/p>
vector | deque | list | set | mutliset | map | multimap | |
---|---|---|---|---|---|---|---|
內(nèi)存結(jié)構(gòu) | 單端數(shù)組 | 雙端數(shù)組 | 雙向鏈表 | 二叉樹(shù) | 二叉樹(shù) | 二叉樹(shù) | 二叉樹(shù) |
隨機(jī)存取 | 是 | 是 | 否 | 否 | 否 | 對(duì)key而言是 | 否 |
查找速度 | 慢 | 慢 | 非常慢 | 快 | 快 | 對(duì)key而言快 | 對(duì)key而言快 |
插入刪除 | 尾端 | 頭尾兩端 | 任何位置 | - | - | - | $1 |