c++基础(三)——容器
阅读原文时间:2023年07月10日阅读:1

1. 顺序容器

  • vector和string将元素保存在连续的内存空间中。由于元素是连续存储的,由元素的下标来计算其地址是非常快速的。但是在这两种容器的中间位置添加或删除元素就非常耗时
  • list和forward_list两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这两个容器不支持元素的随机访问
  • deque是一个更为复杂的数据结构。与string和vector类似,deque支持快速的随机访问。在deque的中间位置添加或删除元素的代价(可能)很高。但是,在deque的两端添加或删除元素都是很快的,与list和forward_list添加删除元素的速度相当
  • forward_list没有size操作

  每个容器类型都定义了一个默认构造函数。除array之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。

  

1.1.1 将一个容器初始化为另一个容器的拷贝

  主要有两种方式,①直接拷贝整个容器,②(array除外)拷贝由一个迭代器对指定的元素的范围。

#include
#include
#include
#include
#include
#include
using namespace std;

int main() {
list authors = { "tom", "Mike", "Bob" };
vector articles = { "ad", "mi" ,"po" };

 list<string> authors2(authors);  
 //vector<string> articles2(articles);//错误,类型不一致

 /\*forward\_list  
     单向list,不能反向迭代(list可以)  
     不能使用size函数,但是可以使用迭代器的distance来计算  
   但是  
   forward\_list的操作比list容器块,且内存占用少  
 \*/

 forward\_list<string> articles3(articles.begin(), articles.end());  
 cout << distance(articles3.begin(), articles3.end()) << endl;   // 3d

 getchar();  
 return ;  

}

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

  只有顺序容器的构造函数才接受大小参数,关联容器不支持。

vector ivec (, -); //10个int元素,每个都初始化为-1
list svec(,"hi"); //10个string,每个够初始化为“hi"
forward_list ivec(); //10个元素,每个都初始化为0
deque svec(); //10个元素,每个都是空string

1.1.3 标准库array具有固定大小

  array 是提供的新容器,比内置数组更安全。两者一定区别如下:

// 内置数组初始化
int a[];
int b[] = { ,, };
int c[] = { ,, };//其他值默认初始化 0
// 访问数组下标时,可以非 size_type 类型,只要不越界。但是容器不行
int *ptr = &a[];
cout << ptr[-] << endl;

// 数组容器
array a1;
array b1 = { ,,, };
// 但是容器能拷贝复制,只要容器 类型+大小 一致
array c1 = b1;
//a1 = b1; //但是不能直接拷贝
// 容器的遍历遵循普通容器遍历方式

1.2.1 向顺序容器添加元素

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

  • 除了push_back之外,listforward_listdeque 还支持名为 push_front 的操作

  • 在容器特定位置添加元素:

      vecotr,deque,list,string 支持 insert(iter, value) 的操作(forward_list 有自己的 insert 的操作),所以,即使 vector 没有 push_front 操作,但是可:

    vector vct;
    vct.insert(vct.begin(), "test"); // vector 不插入末尾,则效率很慢

  • 插入范围元素:

// 接受一个元素数目和元素值
vct.insert(vct.end(), , "qaq"); //在end处插入,个数为4,初始化为qaq的字符串

// 接受一对迭代器
vct.insert(vct.end(), vct2.end() - , vct2.end());//插入位置,vct2给定范围

// 接受位置和列表
vct.insert(vct.end(), { "", "xswl" });

// 插入的列表不能指向目标容器
// vct.insert(vct.end(), vct.begin(), vct.end()); // 错误

  • 使用 insert 的返回值。insert 返回值为指向这个新元素

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

  • emplace

    //在c的末尾构造一个Sales_data对象
    //使用三个参数的Sales_data构造函数
    c.emplace_back("978-9897897987", , 15.99);
    //错误:没有接受三个参数的push_back版本
    c.push_back("978-9897897987", , 15.99);
    //正确:创建一个临时的Sales_data对象传递给push_back
    c.push_back(Sales_data("978-9897897987", , 15.99));

1.2.2 访问元素

 包括 array 在内的每个顺序容器中,都有一个 front 成员函数,而除 forward_list 之外,所有顺序容器都有 back 成员函数。 

// 解引用一个迭代器时,要判空
if (!lst.empty()) {
auto val1 = *lst.begin(), val2 = lst.front();

     auto last = lst.end();  
     auto val3 = \*(--last);// forward\_list 迭代器不能递减  
     auto val4 = lst.back();// forward\_list 不支持  
 }

  下面再看:

/*
front back 都是**返回引用
** */

int main() {

 // 数组容器  
 array<int, > a1;  
 array<int, > b1 = { ,,, };

 b1.front() = ; // b1 初始值改变  
 int &c = b1.back();  
 c = ;  // b1 末尾值改变

 // auto 会吃引用  
 auto a2 = a1.front();  
 a2 = ; // 无效,可通过 auto &a2 

 getchar();  
 return ;  

}

1.2.3 删除元素

  vector 和 string 不支持 push_front 所以不支持 pop_front。同时 vct.clear() 等同于 vct.erase(vct.begin(), vct.end())。

1.2.4 删除插入更新迭代器

  insert 之后返回的是新插入的元素的迭代器,erase之后返回的是被删除的后一个元素的迭代器(此时注意end)。

int main() {
// 删除所有偶数元素,复制所有奇数元素
vector v = { ,,,,,,,,, };
auto iter = v.begin();

 while (iter != v.end()) {  
     if (\*iter % ) {  
         iter = v.insert(iter, \*iter);  
         iter += ;  
     }  
     else {  
         iter = v.erase(iter);  
     }  
 }

 getchar();  
 return ;  

}

  不要保存容器的end迭代器,当容器增加或者删除元素时,end迭代器也会失效,所以应该通过 v.end() 来判断,而不应保存至某一个局部变量。

1.2.5 构造 string 的其他方法

  

int main() {
char ch[] = { 'a','b','c','','' };//不空字符结尾
string s(ch, );
cout << s << endl; //abc12

 string s2(s, ); // (string,pos) 从pos位置开始拷贝  
 cout << s2 << endl;// bc12

 string s3(s, , ); // (string,start,length)  
 cout << s3 << endl; // bc

 /\*  
 通常当我们从一个 const char\* 创建 string 时,指针指向的数组必须以空字符('/0')结尾,拷贝  
 操作遇到空字符时停止。  
 如果我们传递给构造函数拷贝长度,则无所谓  
 \*/  
 const char \*cc = "qaq23333";//以空字符结尾  
 string s4(cc);  
 string s5(ch); // ch 不是以空字符结尾,即未定义

 cout << s4 << "\\t" << s5 << endl;//打印s5出现奇怪字符

 getchar();  
 return ;  

}

1.2.6 string操作——substr,insert,erase

#define Log(s) cout << s << endl;
int main() {

 string str("233qaq");

 /// substr  
 string s2 = str.substr();  
 Log(s2); // 3qaq  
 s2 = str.substr(, );  
 Log(s2);//

 /// insert  
 str.insert(, s2); // 下表版本插入  
 Log(str); //23233qaq  
 str.insert(str.end(), 'h');// 迭代器版本插入  
 Log(str); //23233qaqh  
 str.insert(, s2, , );// 插入3  
 Log(str); //33233qaqh  
 str.insert(, "hi");  
 Log(str); //hi33233qaqh

 ///  erase  
 string str("This is an example sentence.");  
 str.erase(, );        // 从下标10开始,erase 8位  
 Log(str); // "This is an sentence."  
 str.erase(str.begin() + );   // begin迭代器开始第九位  
 Log(str); // "This is a sentence."  
 str.erase(str.begin() + , str.end() - );  
 Log(str)// "This sentence."

 getchar();  
 return ;  

}

1.2.7 string操作——append,replace

#define Log(s) cout << s << endl;
int main() {
string s("hello"), s2 = s;
s.insert(s.size(),",world");
s2.append(",world"); // 同上,效果一致
Log(s);
Log(s2);

 s.erase(, ); // 从下标5开始,erase 6 位  
 s.insert(, ",Bob");  
 s2.replace(, , ",Bob"); // 效果同上两句话一致  
 Log(s);  
 Log(s2);

 getchar();  
 return ;  

}