STL學習筆記(一)

想要學號C++,掌握STL是必不可少的。在網上也查詢了一些STL的推薦書籍,決定按照主流的學習路線循序漸進。學習順序:
1. 《C++標準程序庫:自修教程與參考手冊》
2. 《STL源碼剖析》
那麼現在開始記錄我的《C++標準程序庫:自修教程與參考手冊》學習過程啦!

基本型別的顯式初始化
如果使用不含參數的、明確的構造函數調用方式,基本型別就會被初始化爲零,具體方法:

template<typename T>
void f()
{
    T x = T();
    ...
}

上面這個函數就會保證 x 被初始化爲零。

關鍵字explicit
explicit作用:禁止單參構造函數被用於自動型別轉換,實例:

class Stack{
    explicit Stack(int size);
    ...
};

Stack s1(40);       //1.ok
Stack s2 = 40;      //2.error

如果我們不加explicit關鍵字的話,1、2兩行都是正確的,第二行會把40轉換爲有40個元素的stack,並賦值給s2,這顯然不是我們想要的結果。
而我們加了關鍵字expicit之後,第二行就會編譯出錯啦。

template constructor
template consturctor 是模板成員的一種特殊情況,通常用於“在賦值對象時實現隱式型別轉換”,但是當賦值的形參型別完全符合時,還是會調用拷貝構造函數,實例如下:

template<class T>
class MyClass{
public:
    template<class U>
    MyClass(const MyClass<U>& u);
};

void func()
{
    MyClass<double> x1;
    MyClass<double> x2(x1);   //calls copy constuctor
    MyClass<int> x3(x1);      //calls template constuctor
}

algorithm頭文件
algorithm 是c++特有的STL模板的算法頭文件,包含了一些特定的算法函數,而且是全局的。

空間配置器:allocator
配置器使得諸如共享內存、垃圾回收、面向對象數據庫等特定的內存模型保持一致的接口。而我們之所以叫空間配置器是因爲,不僅只有內存操作,也包括磁盤操作等空間。
allocator 是c++標準庫中定義的一個缺省配置器:

namespace std{
    template<class T>
    class allocator;
}

通用工具

1. pairs
對組pairs是c++用來管理,將兩個值視爲一個單元。例如,map和multimap中的key/valude就是通過pairs管理的。
注意:pairs的定義是一個struct而不是class,所以它的成員都是public的,外部都可以使用。
make_pair:
make_pair是一種便捷函數,使我們無需輸入型別就可以定義pair,它的代碼實現是這樣的:

template<class T, class W>
pair<T,W> make_pair(const T& x, const W& y){
    return pair<T,W>(x,y);
}

這樣我們在定義pair的時候可以方便的:

make_pair(40, 'c');

而不用麻煩的:

pair<int,char>(40, 'c');

但是make_pair也有侷限,比如我們輸入一個浮點數的時候,他會默認認爲是double類型的,但是有時候我們使用 copy constructor 的時候型別是非常嚴格的,如果我們定義的是float,而傳進去的是 double 那就會調用 template constructor了。
注意:c++中,凡是提到必須返回兩個值函數,一般都用pairs

2. Class auto_ptr
auto_ptr是一種智能類型的指針,它的出現是爲了防止“被拋出異常的時候發生資源泄露”。
我們在使用普通指針的時候,異常發生時,程序直接退出,不會調用delete,從而造成資源泄露,我們一般通過 try()…catch()來解決:

void func(){
    MyClass* p = new MyClass;
    try(){
        ...
    }
    catch(){
        delete p;
        throw;
    }
    delete p;
}

這樣做顯然很麻煩,而auto_ptr的出現就解決了這個問題,它是對象的擁有者,所以auto_ptr結束的時候,它的對象也會銷燬,資源自然也就回收了。無論是正常還是異常退出,只要函數退出,它就會被銷燬。我們看看它的使用:

#include<memory>    //這個是auto_ptr的頭文件

void func()
{
    auto_ptr<MyClass> p(new MyClass);
}

但是我們也要注意auto_ptr的定義方式:

auto_ptr<MyClass> p(new MyClass);   //ok
auto_ptr<MyClass> p = new MyClass;  //error

auto_ptr同樣也可以使用迭代器operator*來指向對象,operator->指向成員,但是諸如operator++之類的指針的算數操作都是沒有定義的。
我們前面說過auto_ptr銷燬的時候,它所指向的對象也會跟着銷燬,這就要求了:一個對象同一時刻只能被一個auto_ptr所擁有。而這個擁有權也是可以轉移的,就是在拷貝的時候:

auto_ptr<MyClass> p(new MyClass);
auto_ptr<MyClass> p1;
p1 = new MyClass;   //error
p1 = p;             //ok

p1=p; 這行中,p會將擁有權轉移給p1,並且如果p1之前有擁有權的話,會先銷燬之前的對象。而p會指向NULL。
auto_ptr也可以作爲參數傳遞給函數,那麼它的擁有權也就轉移給了函數,當函數體執行完畢就會銷燬對象,所以如果想要防止這種情況發生,就:

const auto_ptr<MyClass> p(new MyClass);

這樣我們在傳遞p指針的時候就會發生編譯錯誤。這裏的const並不意味着不能改變對象,而是不能改變擁有權。
注意這時的 p 相當於:T* const p,而不是const T* p。

auto_ptr作爲成員?
有一個知識點需要注意:析構函數只有在構造函數全部執行完畢之後,才能調用。所以如果構造函數中有兩次new,但只執行一個之後就拋出異常了,那麼不會調用析構,從而就會發生泄漏。解決方法就是:使用auto_ptr成員,因爲拷貝構造函數的存在,auto_ptr應設爲const(形參中也要是const,否則編譯錯誤)

3. 數值極限

#include<iostream>
#include<string>
#include<limits>   //頭文件
using namespace std;


int main()
{
    cout<<"numeric_limits<int>::min()= "<<numeric_limits<int>::min()<<endl;     //int的最小值
    cout<<"numeric_limits<int>::max()= "<<numeric_limits<int>::max()<<endl;     //int的最大值
    cout<<"numeric_limits<short>::min()= "<<numeric_limits<short>::min()<<endl;     //short
    cout<<"numeric_limits<short>::max()= "<<numeric_limits<short>::max()<<endl;
    cout<<"numeric_limits<double>::min()= "<<numeric_limits<double>::min()<<endl;   //double
    cout<<"numeric_limits<double>::max()= "<<numeric_limits<double>::max()<<endl;

    cout<<"numeric_limits<int>::is_signed()= "<<numeric_limits<int>::is_signed<<endl;    //是否有正負號
    cout<<"numeric_limits<string>::is_specialized()= "<<numeric_limits<string>::is_specialized<<endl;    //是否定義了數值極限
    system("pause");
    return 0;
}

結果:
這裏寫圖片描述

4. 輔助函數
STL算法程序庫(“algoithm”)中有三個輔助函數:min(),max(),swap()分別是找到兩值中較小、較大、和兩個值的交換。
其中,值得強調的是在對象的成員需要進行交換時,使用swap()函數會非常的方便,不需要反覆的複製操作,下面我們看一下實例:

#include <iostream>
using namespace std;

class CAA
{
public:
    CAA(char* str, int num){
        m_str = new char[strlen(str)+1];
        strcpy(this->m_str, str);
        this->m_num = num;
    }
    ~CAA(){
        delete[] m_str;
        m_str = NULL;
    }
    void Swap(CAA& newObject){
        swap(this->m_str, newObject.m_str);
        swap(this->m_num, newObject.m_num);
    }
    void Show(){
        cout << this->m_str << " " << this->m_num << endl;
    }
private:
    char* m_str;
    int m_num;
};

inline void Swap(CAA& object_A, CAA& object_B)
{
    object_A.Swap(object_B);
}

int main()
{
    CAA object_A("hello", 1);
    CAA object_B("world", 2);
    object_A.Show();
    object_B.Show();

    Swap(object_A, object_B);
    cout << "-----------swap()-----------" << endl;
    object_A.Show();
    object_B.Show();

    system("pause");
    return 0;
}

輸出結果:
這裏寫圖片描述

5. 比較操作符
有四個模板函數定義了:!=, >, >=, <= ,他們都是利用操作符==和<實現的。(這裏思考一下是如何實現的),他們都包含在 utility 頭文件中,我們只要先定義==和<,再加上rel_ops命名空間,他們就會獲得定義了。實例:

#include <utility>

class CAA{
public:
    bool operator == (const CAA& a)const;
    bool operator < (const CAA& a)const;
};

using namespace std::rel_ops;

注意:因爲rel_ops是std的子空間,所以我們直接 using namespace std;也是可以的。

6. exit() 和 abort()
共同點
1. 可以在任何位置終止程序而不需要返回main()函數;
2. 他們都不會銷燬局部變量,所以爲了保證局部對象的析構函數能夠調用,應該使用異常(exception)或正常返回機制。
不同點
1. exit():
首先銷燬所有 static 對象,然後清空所有 buffer, 關閉所有 I/O 通道(channels),然後終止程序。
2. abort():
不會做任何清理工作。

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