想要學號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():
不會做任何清理工作。