每个容器类型都定义了一个默认构造函数。除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。