> 文章列表 > c++之顺序容器

c++之顺序容器

c++之顺序容器

顺序容器

sequential container

  • 向容器中添加或从容器中删除元素的代价
  • 非顺序访问容器中元素的代价

string和vector中元素保存在连续的内存空间,由于元素时连续存储的,所有下表计算地址非常快,当从容器中间位置添加或删除元素非常耗时。

list和forward_list连个容器在任意位置添加和删除操作很快,但不支持随机访问。

deque与string和vector类似,支持快速随机访问。与string和vector一样,在deque的中间位置添加或删除元素代价很高。在deque的两端添加或删除元素很快。

array与内置数组相比更安全,更容易使用。它与内置数组类似,array对象大小固定,不支持添加和删除元素以及改变容器大小的操作。

forward_list没有size操作。

选择容器的原则:

  • 优先选vector。

  • 如果程序要求随机访问元素,使用vector后deque。

  • 如果程序要求在容器中间插入或删除元素,使用list或forward_list。

  • 如果程序在头尾插入或删除元素,但不在中间位置进行插入或删除,使用deque。

  • 如果程序只有读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素。

    使用vector,然后重排容器元素,避免在中间位置添加元素。

    如果必须在中间位置插入元素,先用list,然后将list拷贝到vector。

顺序容器概览

迭代器

  • 如果begin与end相等,则范围为空
  • 如果begin与end不等,则范围至少包含一个元素,且biegin指向范围中的第一个元素
  • 我们可以对begin递增若干次,使得begin==end

容器类型成员

元素类型 value_type

vector<int> c{1,2,3,3,4,5,6,8};
vector<int>::value_type tt;
tt = c[0]

元素的左值类型reference

begin和end成员

当不需要写访问时,应使用cbegin和cend。

list<string> a = {"mm","ll","kk"};
list<string>::iterator it1 = a.begin(); //显式指定类型
list<string>::const_iterator it2 = a.begin();
auto it3 = a.begin();//仅当a是const时,it3是const_iterator
auto it4 = a.cbegin();//it8是const_iterator

容器定义和初始化

将一个容器初始化为另一个容器的拷贝
方法1:直接拷贝整个容器,元素类型和容器类型必须相同

list<string> a = {"mm","ll","kk"};
vector<const char*> ar = {"a","b","c"};
list<string> b(a); //正确

方法2:(array除外)拷贝由一个迭代器对指定的元素范围。不要求容器类型相同。

forward_list<string> words(ar.begin(),ar.end());

迭代器表示拷贝的第一个元素和尾元素之后的位置。

//拷贝元素,直到(但不包括)it指向的元素

deque<string> aut(aut.begin(),it);

列表初始化

显式的指定了容器中每个元素的值,隐式的指定了容器大小。

list<string> a = {"mm","ll","kk"};

与顺序容器大小相关的构造函数

另一个构造函数,接受一个容器大小和一个(可选)元素初始值。

vector<int> ivec(10,-1); //10个int元素,每个初始化为-1
deque<string> svec(10); //10个元素,每个都是空string

标准库array具有固定大小

定义array时,除了指定元素类型,还要指定容器大小:

array<int,42>  //类型为:保存42个int的数组

为了使用array类型,必须同时指定元素类型和大小:

array<int,10>::size_type i ; //数组类型包括元素类型和大小

对array初始化时,初始化的数目必须等于或小于array的大小。

array<int,10> ia1;
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 digs[3] = {1,2,3};
int copy[3] = digs; //错误:内置数组不支持拷贝或赋值
array<int,3> digs = {1,2,3};
array<int,3> copy = digs; //正确

赋值和swap

赋值运算符适用于所有容器。

c1 = c2; //将c1的内容替换为c2中元素的拷贝
c1 = {a,b,c} //赋值后,c1大小为3
array<int,10> a1 = {1,2,3,4,5,6,7,8};
array<int,10> a2 = {0};
a1 = a2;
a2 = {0}; //错误,不能将一个花括号列表赋予数组

使用assign(仅顺序容器)

赋值运算符要求左边和右边的运算对象具有相同的类型。它将右边运算对象中所有元素拷贝到左边运算对象中。

list<string> names;
vector<const char*> oldstyle;
names = oldstyle; //错误:容器类型不匹配
names.assign(oldstyle.begin(),oldstyle.end());

用法2:

list<string> slist1(1);
slist1.assign(10,"hiya");

使用swap

swap操作交换两个相同类型容器的内容。元素本身并未交换,swap只是交换了两个容器的内部数据结构。

vector<string> svec1(10);
vector<string> svec2(10);
swap(svec1,svec2);

除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。

与其他容器不同,swap两个array会真正交换他们的元素。

容器大小

每个容器有3个大小相关的操作,size、empty和max_size。forward_list不支持size。

关系运算符

关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。

vector<int> v1 = {1,3,5,7,9,12}
vector<int> v2 = {1,3,9}
vector<int> v3 = {1,3,5,7}
vector<int> v1 = {1,3,5,7,9,12}
v1 < v2 //true v1和v2在元素[2]处不同,v1[2]小于等于v2[2]
v1 < v3 //false 所有元素相等,v1元素多于v3
v1 == v4 //true
v1 == v3

只有当元素类型也定义比较运算符时,才能使用关系运算符。

顺序容器操作

向顺序容器添加元素

使用push_back

除array和forward_list之外,每个顺序容器(包括string类型)都支持push_back。

由于string是一个字符容器,也可以用push_back在string末尾添加字符:

void pluralize(size_t cnt,string &word)
{if(cnt > 1)word.push_back('s'); //等价于 word += 's'
}

使用push_front
list、forwoard_list和deque容器支持push_front。

list<int> ilist;
for(size_t ix = 0; ix != 4; ++ix)ilist.push_front(ix);

在容器中的特定位置添加元素

insert允许我们在容器中任意位置插入0个或多个元素。vector、deque、list和string都支持insert成员。forward_list提供特殊版本的insert成员。

slist.insert(iter,"Hello!")//将hello添加到iter之前的位置。

vector不支持push_front,但可以插入到begin之前,速度很慢

vector<string> svec;
svec.insert(svec.begin(),"Hello!");
list<string> slist;
slist.insert(slist.begin(),"Hello!"); //等价于slist.push_front("Hello!")

插入范围内元素

insert除第一个参数外,接受一个元素数目和一个值。

svec.insert(svec.end(),10,"Anna"); //将10个元素插入到svec的末尾。

接受的一对迭代器或一个初始化列表。

vector<string> v = {"a","b","c","d"};
slist.insert(slist.begin(),v.end()-2,v.end());
slist.insert(slist.end(),{"e","f","g"});
slist.insert(slist.begin(),slist.begin(),slist.end());//错误,不能指向slist

使用insert的返回值

通过使用insert的返回值,可以在容器中一个特定位置反复插入元素。

list<string> lst;
auto iter = lst.begin();
while(cin >> word)iter = lst.insert(iter,word); //等价于push_front

使用emplace

emplace_front、emplace和emplace_back构造而不是拷贝元素。分别对应push_front、insert和push_back。

允许将元素放在容器头部、一个给定位置之前或容器尾部。

c.emplace_back("888",25,15.99);
c.push_back("999",35,25.99);//错误
c.push_back(Sales_data("000",35,43.44));

emplace_back会在容器管理的内存空间中直接创建对象,而调用push_back会创建一个局部临时对象,并将其压入容器中。

c.emplace_back(); //使用Sales_data的默认构造函数
c.empalce(iter,"99999"); //使用Sales_data(string)

传递给emplace函数的参数必须与元素类型的构造函数相匹配。