每個容器類型都定義了一個默認構造函數。除array之外,其他容器的默認構造函數都會創建一個指定類型的空容器。且都能接受指定容器大小和元素初始值的參數。
容器定義和初始化(所以容器的初始化) |
C c; 默認構造函數。如果C是一個array,則c中元素按默認方式初始化,否則c為空 C c1(c2) c1初始化為c2的拷貝。c1和c2必須是相同類型(即,它們必須是相同的容器類型,且保存的是相同的元素類型;對於array類型,兩者還必須具有相同的大小) C c1=c2 C c{a,b,c...} c初始化為初始化列表中的元素的拷貝。列表中元素的類型必須與C的元素類型相容。對於array類型,列表中元素數目必須等於或小於array的大小,任何遺漏的元素都進行值初C c={a,b,c...} 始化
C c(b,e) c初始化為迭代器b和e指定范圍中的元素的拷貝。范圍中元素的類型必須與C的元素類型相容(array不適用)
只有順序容器(不包括array)的構造函數才能接受大小參數 C seq(n) seq包含n個元素,這些元素進行了值初始化;此構造函數是explicit的(string不適用) C seq(n,t) seq包含n個初始化為值t的元素 |
將一個容器初始化為另一個容器的拷貝
將一個新容器創建為另一個容器的拷貝的方法有兩種:可以直接拷貝整個容器,或者(array除外)拷貝由一個迭代器對指定的元素范圍。
為了創建一個容器為另一個容器的拷貝,兩個容器的類型及其元素類型必須匹配。不過,當傳遞迭代器參數來拷貝一個范圍時,就不要求容器類型是相同的了。而且,新容器和原容器中的元素類型也可以不同,只有能將拷貝的元素轉換為要初始化的容器的元素類型即可。
//每個容器有三個元素,用給定的初始化器進行初始化
list<string> authors={"Milton","Shakespeare","Austen"};
vector<const char *> articles={"a","an","the"};
list<string> list2(authors) ; //正確:類型匹配
deque<string> authList(authors); //錯誤:容器類型不匹配
vector<string> words(articles); //錯誤:元素類型必須匹配
//正確:可以將const char*元素轉換為string
forward_list<string> words(articles.begin(),articles.end());
注意:當將一個容器初始化為另一個容器的拷貝時,兩個容器的容器類型和元素類型都必須相同。
接受兩個迭代器的構造函數用這兩個迭代器表示我們想要拷貝的一個元素范圍。與以往一樣,兩個迭代器分別標記想要拷貝的第一個元素和尾元素之后的位置。新容器的大小與范圍中元素的數目相同。新容器中的每個元素都用范圍中對應元素的值進行初始化。
由於兩個迭代器表示一個范圍,因此可以使用這種構造函數來拷貝一個容器中的子序列。例如,假定迭代器it表示authors中的一個元素,我們可以編寫如下代碼
//拷貝元素,直到(但不包括)it指向的元素
deque<string> authList(authors.begin(),it);
列表初始化
在新標准中,我們可以對一個容器進行列表初始化
//每個容器有三個元素,用給定的初始化器進行初始化
list<string> authors={"Milton","Shakespeare","Austen"};
vector<const char *> articles={"a","an","the"};
當這樣做時,我們就顯式地指定了容器中每個元素的值。對於除array之外的容器類型,初始化列表還隱含地指定了容器的大小:容器將包含與初始值一樣多的元素。
與順序容器大小相關的構造函數
除了與關聯容器相同的構造函數外,順序容器(array除外)還提供另一個構造函數,它接受一個容器大小和一個(可選的)元素初始值。如果我們不提供元素初始值,則標志庫會創建一個值初始化器。
vector<int> ivec(10,-1); //10個int元素,每個都初始化為-1
list<string> svec(10,"hi"); //10個string 每個都初始化為"hi"
forward_list<int> ivec(10); //10個元素,每個都初始化為0
deque<string> svec(10); //10個元素,每個都是空string
如果元素類型是內置類型或者是具有默認構造函數的類類型,可以只為構造函數提供一個容器大小參數。如果元素類型沒有默認構造函數,除了大小參數外,還必須指定一個顯示的元素初始值。
只有順序容器的構造函數才接受大小參數,關聯容器並不支持
標准庫array具有固定大小
與內置數組一樣,標准庫array的大小也是類型的一部分。當定義一個array時,除了指定元素類型,還要指定容器大小:
array<int,42> //類型為:保存42個int的數組
array<string,10> //類型為:保存10個string的數組
為了使用array類型,我們必須同時指定元素類型和大小:
array<int,10>::size_type i; //數組類型包括元素類型和大小
array<int>::size_type j; //錯誤:array<int>不是一個類型
由於大小是array類型的一部分,array不支持普通的構造函數的行為。這些構造函數都會確定容器的大小,要么隱式的,要么顯式地。而允許用戶向一個array構造函數傳遞大小參數,最好情況下也是多余的,而且容易出錯。
array大小固定的特性也影響了它所定義的構造函數的行為。與其他容器不同,一個默認構造的array是非空的:它包含了與其大小一樣多的元素。這些元素都被默認初始化,就像一個內置數組中的元素那樣。如果我們隊array進行列表初始化,初始值的數目必須等於或小於array的大小。如果初始值數目小於array的大小,則它們被用來初始化array中靠前的元素,所有剩余元素都會進行值初始化。在這兩種情況下,如果元素類型是一個類類型,那么該類型必須有一個默認構造函數,以使值初始化能夠進行:
array<int,10> ia1; //10個默認初始化的int
array<int,10> ia2={0,1,2,3,4,5,6,7,8,9};
array<int,10> ia3={42}; //ia3[0]=42,其余元素為0
值得注意的是,雖然我們不能對內置數組類型進行拷貝或對象賦值,但是array並無此限制:
int digits[10]={0,1,2,3,4,5,6,7,8,9};
int cpy[10]=digits; //錯誤:內置數組不支持拷貝或賦值
array<int,10> digits={0,1,2,3,4,5,6,7,8,9};
array<int,10> copy=digits; //正確:只要數組類型匹配即可
與其他容器一樣,array也要去初始值的類型必須與要創建的容器類型相同,此外,array還要求元素類型和大小也都一樣,因為大小是array的一部分。
賦值和swap
下表列出的與賦值相關的運算符可用於所有類型,賦值運算符將其左邊容器中的全部元素替換為右邊容器中元素的拷貝:
c1=c2; //將c1的內容替換為c2中元素的拷貝
c1={a,b,c}; //賦值后,c1大小為3
與內置數組不同,標准庫array類型允許賦值。賦值號左右兩邊的運算對象必須具有相同的類型:
array<int,10> a1={0,1,2,3,4,5,6,7,8,9};
array<int,10> a2={0};
a1=a2;
a2={0}; //錯誤:不能將一個花括號列表賦予數組
由於右邊運算對象的對象可能與左邊運算對象的大小不同,因此,array類型不支持assign,也不允許用花括號包圍的值列表進行賦值。
容器賦值運算 |
c1=c2 將c1中元素替換為c2中元素的拷貝。c1和c2必須具有相同的類型 c={a,b,c...} 將c1中元素替換為初始化列表中元素的拷貝(array不適用) swap(c1,c2) 交換c1和c2中的元素,c1和c2必須具有相同的類型,swap通常比從c2向c1拷貝元素快很多 c1.swap(c2)
assign操作不適用於關聯容器和array seq.assign(b,e) 將seq中的元素替換為迭代器b和e所表示的范圍中的元素。迭代器b和e不能指向seq中的元素 seq.assign(il) 將seq中的元素替換為初始化列表il中的元素 seq.assign(n,t) 將seq中的元素替換為n個值為t的元素
賦值相關運算會導致指向左邊容器內部的迭代器、引用和指針失效。而swap操作將容器內容交換不會導致指向容器的迭代器、引用和指針失效(容器類型為array和string的情況例外) |
使用assign(僅順序容器)
賦值運算符要求左邊和右邊的運算對象具有相同的類型。它將右邊運算對象中所有元素拷貝到左邊運算對象中。順序容器(array除外)還定義了一個assign的成員,允許我們從一個不同但相容的類型賦值,或者從容器的一個子序列賦值。assign操作用參數所指的的元素(的拷貝)替換左邊容器中的所有元素。例如,我們可以用assign實現將一個vector中的一段char*值賦予一個list中的string:
list<string> names;
vector<const char*> oldstyle;
names=oldstyle; //錯誤:容器類型不匹配
//正確:可以將const char*轉換為string
names.assign(oldstyle.cbegin(),oldstyle.cend());
這段代碼中對assign的調用將names中的元素替換為迭代器指定的范圍中的元素的拷貝。assign的參數決定了容器中將有多少個元素以及它們的值都是什么。
assign的第二個版本接受一個整型值和一個元素值。它用指定數目且具有相同給定值的元素替換容器中原有的元素:
//等價於slist.clear()
//后跟slist.insert(slist.begin(),10,"Hiya!");
list<string> slist(1); //1個元素,為空string
slist.assign(10,"Hiya!"); //10個元素,每個都是"Hiya!"
使用swap
swap操作交換兩個相同類型容器的內容。調用swap之后,兩個容器中的元素將會交換:
vector<string> svec1(10); //10個元素的vector
vector<string> svec2(24); //24個元素的vector
swap(svec1,svec2);
調用swap后,svec1將包含24個string元素,svec2將包含10個string。除了array之外,交換兩個容器內容的操作保證會很快——元素本身並未交換,swap只是交換了兩個容器的內部數據結構。
注意:除array外,swap不對任何元素進行拷貝,刪除或插入操作,因此可以保證在常數時間內完成。
元素不會被移動的事實意味着,除string外,指向容器的迭代器、引用和指針在swap操作之后都不會失效。它們仍然指向swap操作之前所指向的那些元素。但是,在swap之后,這些元素已經屬於不同的容器了。例如,假定iter在swap之前指向svec1[3]的string,那么在swap之后它指向svec2[3]的元素。與其他容器不同,對一個string調用swap會導致迭代器、引用和指針失效。
與其他容器不同,swap兩個array會真正交換它們的元素。因此,交換兩個array所需的時間與array中元素的數目成正比。
因此,對於array,在swap操作之后,指針、引用和迭代器所綁定的元素保存不變,但是元素值已經與另一個array中對應元素的值進行了互換。
在新標准中,容器既提供了成員函數版本的swap,也提供了非成員函數版本的swap。