c++學習筆記(八、STL基礎、string、vector)

終於到了STL了,c++標準模板庫,這個纔是c++和c的最大區別,也是c++的終極boss。刷c++這個副本刷到這裏,也快結束了,這個這個副本的最大的一個boss。打敗了這個boss就可以獲得技能書了。(c++技能書)

8.1 STL理論基礎

8.1.1 STL概念

STL(standard Template library,標準模板庫),是惠普實驗室開發的一系列軟件統稱。現在只要出現在c++中,但是引入c++之前該技術已經存在很久了。

STL從廣義上分:容器(container)算法(algorithm)迭代器(iterator),容器和算法之間通過迭代器進行無縫連接。STL幾乎所有代碼都使用模板類和模板函數,這比傳統的由函數和類組成的庫提高了更好的代碼重用機會。

STL具有高可重用性,高性能,高移植性,跨平臺的優點:

  • 高可重用性(幾乎所有代碼都是使用類模板和函數模板)
  • 高性能(如map可以高效地從十萬條記錄裏面查出指定的記錄)
  • 高移植性()
  • 跨平臺

下面的容器介紹都是從這個網站學習過來的,要想看詳細信息,去這個網站學習學習。
http://c.biancheng.net/view/343.html

8.1.2 容器

容器(container)用於存放數據的類模板。可變長數組、鏈表、平衡二叉樹等數據結構在 STL 中都被實現爲容器。

程序員使用容器時,即將容器類模板實例化爲容器類時,會指明容器中存放的元素是什麼類型的。

容器中可以存放基本類型的變量,也可以存放對象。對象或基本類型的變量被插入容器中時,實際插入的是對象或變量的一個複製品。

順序容器
順序容器有以下三種:可變長動態數組 vector、雙端隊列 deque、雙向鏈表 list。

它們之所以被稱爲順序容器,是因爲元素在容器中的位置同元素的值無關,即容器不是排序的。將元素插入容器時,指定在什麼位置(尾部、頭部或中間某處)插入,元素就會位於什麼位置。

關聯容器
關聯容器有以下四種:set、multiset、map、multimap。關聯容器內的元素是排序的。插入元素時,容器會按一定的排序規則將元素放到適當的位置上,因此插入元素時不能指定位置。

#include <vector>
int main(int argc, char **argv)
 {
	 //定義一個容器v
     vector<int> v;   

     //往容器添加數據
     v.push_back(10);
     v.push_back(20);
     v.push_back(30);
     v.push_back(40);

	//容器的操作方法,不僅僅只有一個,有很多個,可以看看我昨天轉載的博客
	//裏面就有這些方
	//https://blog.csdn.net/C1033177205/article/details/104120275
     return 0;
 }

8.1.3 迭代器

要訪問順序容器和關聯容器中的元素,需要通過“迭代器(iterator)”進行。迭代器是一個變量,相當於容器和操縱容器的算法之間的中介。迭代器可以指向容器中的某個元素,通過迭代器就可以讀寫它指向的元素。從這一點上看,迭代器和指針類似。

迭代器按照定義方式分成以下四種。

  1. 正向迭代器,定義方法如下:
    容器類名::iterator 迭代器名;

  2. 常量正向迭代器,定義方法如下:
    容器類名::const_iterator 迭代器名;

  3. 反向迭代器,定義方法如下:
    容器類名::reverse_iterator 迭代器名;

  4. 常量反向迭代器,定義方法如下:
    容器類名::const_reverse_iterator 迭代器名;

迭代器的功能分類
不同容器的迭代器,其功能強弱有所不同。容器的迭代器的功能強弱,決定了該容器是否支持 STL 中的某種算法。例如,排序算法需要通過隨機訪問迭代器來訪問容器中的元素,因此有的容器就不支持排序算法。

常用的迭代器按功能強弱分爲輸入、輸出、正向、雙向、隨機訪問五種,這裏只介紹常用的三種。

  1. 正向迭代器。假設 p 是一個正向迭代器,則 p 支持以下操作:++p,p++,*p。此外,兩個正向迭代器可以互相賦值,還可以用==和!=運算符進行比較。

  2. 雙向迭代器。雙向迭代器具有正向迭代器的全部功能。除此之外,若 p 是一個雙向迭代器,則–p和p–都是有定義的。–p使得 p 朝和++p相反的方向移動。

  3. 隨機訪問迭代器。隨機訪問迭代器具有雙向迭代器的全部功能。若 p 是一個隨機訪問迭代器,i 是一個整型變量或常量,則 p 還支持以下操作:
    p+=i:使得 p 往後移動 i 個元素。
    p-=i:使得 p 往前移動 i 個元素。
    p+i:返回 p 後面第 i 個元素的迭代器。
    p-i:返回 p 前面第 i 個元素的迭代器。
    p[i]:返回 p 後面第 i 個元素的引用。

此外,兩個隨機訪問迭代器 p1、p2 還可以用 <、>、<=、>= 運算符進行比較。p1<p2的含義是:p1 經過若干次(至少一次)++操作後,就會等於 p2。其他比較方式的含義與此類似。

對於兩個隨機訪問迭代器 p1、p2,表達式p2-p1也是有定義的,其返回值是 p2 所指向元素和 p1 所指向元素的序號之差(也可以說是 p2 和 p1 之間的元素個數減一)。

表1:不同容器的迭代器的功能

容器 迭代器功能
vector 隨機訪問
deque 隨機訪問
list 雙向
set / multiset 雙向
map / multimap 雙向
stack 不支持迭代器
queue 不支持迭代器
priority_queue 不支持迭代器

下面寫個簡單的例子:

#include <vector>
int main(int argc, char **argv)
{
    vector<int> v;   //定義一個容器v

    //往容器添加數據
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    v.push_back(40);

    //使用迭代器遍歷
    //1.直接用迭代器遍歷
    for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << " ";    //*it 就代表着是迭代器裏面的內容,迭代器就像一個指針
    }
    cout << endl;

    for(int i=0; i<v.size(); i++) {
        cout << v[i] << " ";      //可以像數組那樣遍歷
    }
    cout << endl;
    //2.先使用stl函數for_each,這個函數功能等下講

    return 0;
}

這裏我學習寫的遍歷就兩種把,其他的看卡上面的鏈接,很多內容的,還有迭代器的輔助函數,不能抄太多是吧,你們自己去看。

8.1.4 算法

STL 提供能在各種容器中通用的算法(大約有70種),如插入、刪除、查找、排序等。算法就是函數模板。算法通過迭代器來操縱容器中的元素。

許多算法操作的是容器上的一個區間(也可以是整個容器),因此需要兩個參數,一個是區間起點元素的迭代器,另一個是區間終點元素的後面一個元素的迭代器。例如,排序和查找算法都需要這兩個參數來指明待排序或待查找的區間。

這裏我自己找了個函數舉例:(終於不要擔心說抄襲了,哈哈哈)

//我在algorithm頭文件中找到一個比較簡單執行迭代器模板函數
//並且我很高興,可以看到這個模板,不過那些多餘的東西,我也看不懂,不過可以直接忽略
//_Frist _Last 是傳遞迭代器的,開始指針,和結束指針。
//_Func  傳遞的是操作函數,函數內部不做處理,需要傳遞一個回調函數處理   

// FUNCTION TEMPLATE for_each
template <class _InIt, class _Fn>
_CONSTEXPR20 _Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
    _Adl_verify_range(_First, _Last);
    auto _UFirst      = _Get_unwrapped(_First);
    const auto _ULast = _Get_unwrapped(_Last);
    for (; _UFirst != _ULast; ++_UFirst) {
        _Func(*_UFirst);
    }

    return _Func;
}

下面是使用這個函數模板:

#include <vector>
#include <algorithm>
int main(int argc, char **argv)
{

    vector<int> v;   //定義一個容器v

    //往容器添加數據
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    v.push_back(40);

    //使用迭代器遍歷
    //1.直接用迭代器遍歷
    for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << " ";    //*it 就代表着是迭代器裏面的內容,迭代器就像一個指針
    }
    cout << endl;

    for(int i=0; i<v.size(); i++) {
        cout << v[i] << " ";      //可以像數組那樣遍歷
    }
    cout << endl;


    //2.先使用stl函數for_each,這個函數功能等下講
    vector<int>::iterator i_begin = v.begin();
    vector<int>::iterator i_end = v.end();
     //之前忽略了一點,模板函數不需要填<>裏的參數,編譯器會根據參數,自己生產一個新的函數,然後提高匹配調用
    for_each<>(i_begin, i_end, show);    
    for_each(i_begin, i_end, show);

    return 0;
}

8.1.5 練習一下迭代器

1.容器存放person類的指針,並且遍歷出來
2.容器嵌套容器 一個容器作爲另一個容器的元素(這個沒寫完)

using namespace std;

class Person
{
public:

    Person(char *name, int age)
    {
        //this->name = name;
        strcpy(this->name, name);
        this->age = age;
    }

    void show() {
        cout << "名字" << name << "年齡" << age << endl;
    }

    char name[64];
    int  age;
private:
};

//家族類
class Family
{
public:
    char name[60];
private:
};

/**
    * @brief  創建test對象
    * @param  
    * @retval 
    */ 
#include <vector>
#include <algorithm>
int main(int argc, char **argv)
{
    //這是存儲一個普通的類對象
    vector<Person> v;

    Person p1("mama", 40);
    Person p2("me", 22);
    Person p3("lisi", 22);
    Person p4("zhaowu", 22);
    
    v.push_back(p1);
    v.push_back(p2);
    v.push_back(p3);
    v.push_back(p4);

    for(vector<Person>::iterator it = v.begin(); it != v.end(); it++){
       (*it).show();
    }

    //下面是存儲類對象的指針
    vector<Person*> p_v;

    Person *p_p1 = new Person("mama", 40);
    Person *p_p2 = new Person("me", 22);
    Person *p_p3 = new Person("lisi", 22);
    Person *p_p4 = new Person("zhaowu", 22);
    
    p_v.push_back(p_p1);
    p_v.push_back(p_p2);
    p_v.push_back(p_p3);
    p_v.push_back(p_p4);

    for(vector<Person*>::iterator it = p_v.begin(); it != p_v.end(); it++){
       (*it)->show();
    }

    delete p_p1;
    delete p_p2;
    delete p_p3;
    delete p_p4;

    //容器嵌套容器   一個容器作爲另一個容器的元素
    vector<vector<Person>> v3; 

    

    return 0;
}

8.2 string容器

8.2.1 string的特性

我們講的第一個容器是string,就是字符串的容器,我們在c語言中,描述字符串都是用字符數組或者char* 來表示字符串,下面來看看char *和string的比較:

  1. char *是一個指針,string是一個類
  2. string封裝了很多方法。(越用越爽的方法)
  3. 不用考慮內存釋放和越界。(都是string管理和維護)

8.2.2 string構造函數

#include <string>
int main(int argc, char **argv)
{
    string s1;
    string s2(10, 'a');
    string s3("hello c++");
    string s4(s3);   //拷貝構造

    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
    cout << s4 << endl;


    return 0;
}

看代碼就懂,多說無益
在這裏插入圖片描述

8.2.3 string賦值語句

int main(int argc, char **argv)
{
    string s1;
    string s2(10, 'a');
    
    s1 = "hello c++";
    cout << s1 << endl;
    s1 = s2;
    cout << s1 << endl;
    s1 = 'a';
    cout << s1 << endl;
    //成員方法
    s1.assign("jj");
    cout << s1 << endl;

    //cout << s1 << endl;
    // cout << s2 << endl;
    // cout << s3 << endl;
    // cout << s4 << endl;


    return 0;
}

string把=操作符重載了,可以直接賦值。
在這裏插入圖片描述

8.2.4 string取值語句

#include <string>
 int main(int argc, char **argv)
 {
     string s1;
     string s2(10, 'a');
     
     s1 = "hello c++";
     
     for(int i=0; i<s1.size(); i++) {
         cout << s1[i] << " ";
     }
     cout << endl;

     for(int i=0; i<s1.size(); i++) {
         cout << s1.at(i) << " ";
     }
     cout << endl;


     //cout << s1 << endl;
     // cout << s2 << endl;
     // cout << s3 << endl;
     // cout << s4 << endl;


     return 0;
 }

取值操作的時候,c++也把[]操作符重載了,所以可以直接用[]取值,at這個是函數方法取值,這兩個取值的區別就是:
[]取值 如果訪問越界了 就直接掛了
at方式取值,訪問越界,會拋異常out_of_range

8.2.5 string拼接

#include <string>
int main(int argc, char **argv)
{
    string s1;
    string s2(10, 'a');
    
    s1 = "hello";

    s1 += "c++";
    cout << s1 << endl;
    s1 += s2;
    cout << s1 << endl;

    s1.append("haha");
    cout << s1 << endl;

    string s3 = s1 + s2;    //可以兩個字符串相加
    cout << s3 << endl;

    return 0;
}

結果:
在這裏插入圖片描述
下面是拼接的函數集:
在這裏插入圖片描述

8.2.6 string查找和替換

#include <string>
int main(int argc, char **argv)
 {
     string s1;
     string s2(10, 'a');
     
     s1 = "hello c++";

     int pos = s1.find("c++");
     cout << pos << endl;

     return 0;
 }

函數集:
在這裏插入圖片描述

8.2.7 string比較操作

不寫了偷懶。
在這裏插入圖片描述

8.2.8 string獲取子字符串

在這裏插入圖片描述

8.2.9 string插入和刪除

在這裏插入圖片描述

想不到一個string字符串這麼多函數,不過用法都挺簡單的,我們看下一容器。

8.3 vector容器

8.3.1 vector介紹

vector 是順序容器的一種。vector 是可變長的動態數組,支持隨機訪問迭代器,所有 STL 算法都能對 vector 進行操作。要使用 vector,需要包含頭文件 vector。

vector容器是單口容器,push_back pop_back都是在末端操作,這樣就減少容器內部數據的移動,提高效率。

vector動態增長的原理,當原來空間不足的時候,要重新申請一個比原來空間的大小X2的空間,然後把原來的數據拷貝到新的空間裏,釋放原理空間的內存,基本所有的動態數組都是這樣實現擴容。(Java的也是)

8.3.2 vector構造函數

void show(vector<int>& v) {
 for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
            cout << *it << " ";
    }
    cout << endl;
}

#include <vector>
int main(int argc, char **argv)
{
    //vector構造函數
    vector<int> v1;   //默認構造
    int arr[] = {10, 20, 30, 40};
    vector<int> v2(arr, arr + sizeof(arr)/sizeof(arr[0]));
    vector<int> v3(v2.begin(), v2.end());
    vector<int> v4(v3);

    show(v1);
    show(v2);
    show(v3);
    show(v4);

    return 0;
}

上面寫了幾種構造方法,結果:
在這裏插入圖片描述
下面是函數集:
在這裏插入圖片描述

8.3.3 vector常用賦值操作

void show(vector<int>& v) {
    for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
            cout << *it << " ";
    }
    cout << endl;
}

#include <vector>
int main(int argc, char **argv)
{
    //vector賦值操作
    vector<int> v1;
    int arr[] = {10, 20, 30, 40};
    vector<int> v2(arr, arr + sizeof(arr)/sizeof(arr[0]));
    vector<int> v3;
    int arr1[] = {100, 200, 300, 400};
    vector<int> v4(arr1, arr1 + sizeof(arr1)/sizeof(arr1[0]));
    //成員方法
    v1.assign(v2.begin(), v2.end());
    //重載=操作符
    v3 = v2;
    //v4和v1交換
    v4.swap(v1);

    show(v1);
    show(v2);
    show(v3);
    show(v4);

    return 0;
}

感覺都差不多,swap方式等下會有妙用,底層實現的交換,其實只是交換了兩個指向內存的指針而已,結果:
在這裏插入圖片描述
函數集:
在這裏插入圖片描述

8.3.4 vector大小操作

這個操作就不寫了,看看就懂了,哈哈哈。
在這裏插入圖片描述

8.3.5 vector數據存儲

這幾個跟string差不多,不寫了,vector也支持直接[]操作。
在這裏插入圖片描述

8.3.6 vector插入和刪除

void show(vector<int>& v) {
   for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
            cout << *it << " ";
    }
    cout << endl;
}

#include <vector>
int main(int argc, char **argv)
{
    //vector賦值操作
    vector<int> v1;
    int arr[] = {10, 20, 30, 40};
    vector<int> v2(arr, arr + sizeof(arr)/sizeof(arr[0]));

    //添加操作
    v2.push_back(55);    //直接插到末尾
    
    v2.insert(v2.begin(), 33);    //這個按位置插入,這個這個位置是迭代器的指針
    v2.insert(v2.end(), 77);      //尾插

    v2.insert(v2.begin() + 2, 99);   //vector支持隨機訪問

    //支持下標操作的,一般都支持隨機訪問
    //也就是迭代器可以直接 +2 +3  -2 -4操作

    show(v2);

    //刪除操作
    v2.pop_back();          //尾部刪除

    v2.erase(v2.begin());      //指定位置刪除,這個是刪除頭

    show(v2);

    v2.erase(v2.begin() + 2, v2.end());      //刪除一個區域

    show(v2);

    v2.clear();

    show(v2);

    return 0;
}

上面代碼都把操作過了一遍,下面是運行的結果:
在這裏插入圖片描述
函數集:
在這裏插入圖片描述

8.3.7 巧用swap收縮vector空間

vector容器在我們添加元素的時候,是容量是不斷增長的,如果我們刪除元素之後,容量會不會減少呢?下面我們看看例子:

 #include <vector>
int main(int argc, char **argv)
 {
     vector<int> v1;
     
     //不斷的增長元素
     for(int i=0; i<10000; i++)  {
         v1.push_back(i);
     }

     cout << "size = " << v1.size() << endl;
     cout << "capacity = " << v1.capacity() << endl;

     //突然減少元素
     v1.resize(10);

     cout << "size = " << v1.size() << endl;
     cout << "capacity = " << v1.capacity() << endl;


     return 0;
 }

結果會怎麼樣呢?
在這裏插入圖片描述
很不幸的說,元素減少,容量不會減少,這樣的話,好像浪費了很多的空間,不過我們也有巧門,來減少容量。

//收縮空間
vector<int>(v1).swap(v1);

 cout << "size = " << v1.size() << endl;
 cout << "capacity = " << v1.capacity() << endl;

就加了這一句代碼,就可以成功的減少了空間,我們先來看結果:
在這裏插入圖片描述
是不是空間減少了,原理呢?

其實也挺簡單的,就是可能自己想不到,vector(v1).swap(v1);這句代碼,我們先分開
vector(v1) 這一部分是說明要申請一個vector的匿名對象,然後調用拷貝構造函數,用v1做模板,重新生成一個和v1一樣的匿名對象,然後這個匿名對象和真正的v1進行交換,也就是真正的v1的指針已經指向了匿名對象的那塊內存了,匿名對象的指針指向1萬多的空間的內存,然後匿名對象沒有被使用,就被釋放掉了,跟着匿名對象被釋放的是那1萬多的內存,這時候的v1的內存就是新開闢的那塊,所以用這種方法,確實很巧妙。

8.3.8 reserve申請空間

resize 和 reserve區別:
reserve是容器預留的空間,但在空間內不真正創建元素對象,所以在沒有添加新的對象之前,不能引用容器內的元素。
resize 是改變容器的大小,且在創建對象,調用這個函數之後,就可以引用容器內的對象了。

因爲在上面我們不斷的添加元素的時候,容器會因爲容量不足,進而不斷申請內存,申請完內存之後,又要拷貝,拷貝完了,又要釋放掉原來的內存,這樣一折騰,是不是感覺效率很低。所以如果我們知道了容器大概要存儲的元素個數,那麼我們就可以用reserve預留空間,而不用在添加的時候去折騰。 調用方法看上面的大小操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章