關於類模板
類模板的定義以關鍵字template 開始,後面是用尖括號括起來的參數表,類型參數由typename 或class 加上一個標識符構成 例如
template<class elemType>
class list_item;
關鍵字typename 與class 可以互換。typename 是標準C++中新引入的 這種寫法更利於記憶,但是在本書寫作時 對typename 的支持沒有class
廣泛,由於這個原因 我們使用關鍵字class關於命名空間
更通用的解決方案是使用C++名字空間機制,名字空間使得庫廠商可以封裝全局名字以防止名字衝突。另外,名字空間也提供了訪問符號,從而允許在我們的程序中使用這些名字,例如 C++標準庫被包裝在名字空間std 中 本書第二版的代碼也被放到一個唯一的名字空間中。
namespace Primer_Third_Edition
{
template<typename elemType>
class list_item{ ... };
template<typename elemType>
class list{ ... };
// ...
}
如果用戶希望練習我們的list 類 那麼他可以這樣寫
// 我們的 list類頭文件
#include "list.h"
// 使定義對程序可見
using namespace Primer_Third_Edition;
// ok: 訪問我們的 list
list< int > ilist;
// ...
抽象容器
順序容器[sequence container] 擁有由單一類型元素組成的一個有序集合,兩個主要的順序容器是list 和vector(第三個順序容器爲雙端隊列deque,發音爲 deck。它提供了與vector 相同的行爲,但是對於首元素的有效插入和刪除提供了特殊的支持。)
關聯容器[associative container]支持查詢一個元素是否存在,並且可以有效地獲取元素兩個基本的關聯容器類型是map[映射]和set[集合]。map 是一個鍵/值[key/value] 對。鍵[key] 用於查詢,而值[value]包含我們希望使用的數據。
vector,deque與list
vector 表示一段連續的內存區域 每個元素被順序存儲在這段內存中
deque 也表示一段連續的內存區域,但是與vector 不同的是,它支持高效地在其首部插入和刪除元素。它通過兩級數組結構來實現,一級表示實際的容器,第二級指向容器的首和尾。
list 表示非連續的內存區域,並通過一對指向首尾元素的指針雙向鏈接起來,從而允許向前和向後兩個方向進行遍歷。在list 的任意位置插入和刪除元素的效率都很高,指針必須被重新賦值,但是不需要用拷貝元素來實現移動;另一方面,它對隨機訪問的支持並不好訪問一個元素需要遍歷中間的元素。另外,每個元素還有兩個指針的額外空間開銷 。
vector如何增長的
#include <vector>
#include <iostream>
int main()
{
vector< int > ivec;
cout << "ivec: size: " << ivec.size()
<< " capacity: " << ivec.capacity() << endl;
for ( int ix = 0; ix < 24; ++ix ) {
ivec.push_back( ix );
cout << "ivec: size: " << ivec.size()
<< " capacity: " << ivec.capacity() << endl;
}
}
在ivec的定義之後,它的長度和容量都是0。但是在插入第一個元素之後,ivec的容量是256,長度爲1。這意味着在ivec 下一次需要增長之前,我們可以向它加入256 個元素,當我們插入第256 個元素時,vector以下列方式重新自我增長:它分配雙倍於當前容量的存儲區,把當前的值拷貝到新分配的內存中,並釋放原來的內存。
容量是指在容器下一次需要增長自己之前能夠被加入到容器中的元素的總數。容量只與連續存儲的容器相關。例如vector,deque 或string。list 不要求容量。
正如稍後我們將要看到的 同list 相比 數據類型越大越複雜 則vector 的效率也就越低
下表 各種數據類型 它們的長度 及其相關vector 的初始容量
數據類型 | 長度(字節) | 初始插入後的容量 |
int | 4 | 256 |
double | 8 | 128 |
simple class | 12 | 85 |
String | 12 | 85 |
large simple class | 8000 | 1 |
large complex class | 8000 | 1 |
無論是list 還是vector,對於已定義拷貝構造函數的類來說,插入這樣的類的元素都需要調用拷貝構造函數。拷貝構造函數用該類型的一個對象初始化該類型的另一個對象——這正說明了在簡單類和string 的鏈表之間插入代價的區別。簡單類對象和大型簡單類對象通過按位拷貝插入,一個對象的所有位被拷貝到第二個對象的位中;而string 類對象和大型複雜類對象通過調用拷貝構造函數來插入。
另外 隨着每次重新分配內存 vector 必須爲每個元素調用拷貝構造函數,而且在釋放原來的內存時,它要爲每個元素調用其相關類型的析構函數。vector 的動態自我增長越頻繁,元素插入的開銷就越大。
reserve()操作允許程序員將容器的容量設置成一個顯式指定的值
vector< string > svec;
svec.reserve( 32 ); // 把容量設置爲 32
但根據經驗發現,用一個非1 的缺省值來調整vector 的容量看起來總會引起性能退化。例如,對於string 和double 型的vector,通過reserve()增加容量導致很差的性能;另一方面,加大型複雜類的容量會大大改善性能。(注,非簡單類 8000字節大小,並且帶有構造函數和析構函數 )