C++序列容器学习笔记

序列容器分类

  • array<T, N>[数组容器]

一个固定长度的序列,有N个T类型的对象,不能增加或删除元素。
array原理图

  • vector[向量容器]

一个可变长度的序列,用来存放T类型对象,可自动增加容量,只能在末尾高效地增加或删除元素。
vector

  • deque[双向队列容器]

一个可变长度的序列,用来存放T类型对象,可自动增加容量,能在两端增减或删除元素,但效率不高。
deque

  • list[链表容器]

一个可变长度的序列,用来存放T类型对象,可自动增加容量,能在任意位置高效增减或删除元素,只能从链表起始/结尾元素开始查找元素,因此在访问任意位置元素时效率不高。
list

array数组容器

初始化

  • 初始化一个可容纳100个double元素的array变量。
#include <array>

int main(int argc, char * argv[]) {
    std::array<double, 100> datas;
    return 0;
}
  • 初始化一个可容纳100个double元素的array变量,并初始化为0.0。
#include <array>

int main(int argc, char * argv[]) {
    std::array<double, 100> datas {};
    return 0;
}
  • 初始化一个可容纳100个double元素的array变量,并将前5个元素初始化为0.5、1.0、1.5、2.0、2.5,其余初始化为0.0。
#include <array>

int main(int argc, char * argv[]) {
    std::array<double, 100> datas {0.5, 1.0, 1.5, 2.0, 2.5};
    return 0;
}
  • 初始化一个可容纳100个double元素的array变量,并将元素初始化为0.5。
#include <array>

int main(int argc, char * argv[]) {
    std::array<double, 100> datas;
    datas.fill(0.5);
    return 0;
}

访问元素

  • 通过[index]的方式访问元素。
#include <iostream>
#include <array>

int main(int argc, char * argv[]) {
    std::array<double, 5> datas {0.5, 1.0, 1.5, 2.0, 2.5};

    double result = datas[0] + 2.0 * datas[3];
    std::cout << "result = " << result << std::endl;

    return 0;
}
// 输出结果
// result = 4.5
  • 通过at(index)的方式访问元素。
#include <iostream>
#include <array>

int main(int argc, char * argv[]) {
    std::array<double, 5> datas {0.5, 1.0, 1.5, 2.0, 2.5};

    double result = datas.at(0) + 2.0 * datas.at(3);
    std::cout << "result = " << result << std::endl;

    return 0;
}
// 输出结果
// result = 4.5

[index]at(index)两种方式都能访问到指定位置的元素,但是索引位置越界时,前者会进行越界检查。而后者会抛出std::out_of_rang异常错误使程序崩溃。由于[index]进行了越界检查,因此性能会比at(index)低一些

  • 通过size()获取容器中的元素数量。
#include <iostream>
#include <array>

int main(int argc, char * argv[]) {
    std::array<double, 5> datas {0.5, 1.0, 1.5, 2.0, 2.5};

    double sum = 0.0;
    for (int index = 0; index < datas.size(); ++index) {
        sum += datas.at(index);
    }

    std::cout << "sum = " << sum << std::endl;
    return 0;
}
// 输出结果
// sum = 7.5

对于array容器来说size()函数最大的用处可能就是用于条件循环遍历,因为array容器在初始化时明确声明容器大小的,切不可删除或者增加。

迭代器

  • 迭代器对象可以使用begin()end()返回,这两个函数前者返回容器的起始位置,后者返回容器的结束位置。
#include <iostream>
#include <array>

int main(int argc, char * argv[]) {
    std::array<double, 5> datas {0.5, 1.0, 1.5, 2.0, 2.5};
    double result = 0.0;
    std::array<double, 5>::iterator first = datas.begin();
    while (first != datas.end()) {
        result += *first++;
    }
    std::cout << "result = " << result << std::endl;
    return 0;
}
// 输出结果
// result = 7.5
  • 通过模板iota()函数和迭代器可以完成对容器的递增初始化。
#include <iostream>
#include <array>
#include <numeric>

int main(int argc, char * argv[]) {
    std::array<double, 5> datas {};
    std::iota(datas.begin(), datas.end(), 1);
    int index = 0;
    std::array<double, 5>::iterator first = datas.begin();
    while (first != datas.end()) {
        std::cout << "[" << index++ << "] = " << *first++ << std::endl;
    }
    return 0;
}
// 输出结果
// [0] = 1
// [1] = 2
// [2] = 3
// [3] = 4
// [4] = 5
  • 通过rbegin()rend()可以将容器反向遍历。
#include <iostream>
#include <array>
#include <numeric>

int main(int argc, char * argv[]) {
    std::array<double, 5> datas {};
    std::iota(datas.begin(), datas.end(), 1);
    int index = 0;
    std::array<double, 5>::reverse_iterator first = datas.rbegin();
    while (first != datas.rend()) {
        std::cout << "[" << index++ << "] = " << *first++ << std::endl;
    }
    return 0;
}
// 输出结果
// [0] = 5
// [1] = 4
// [2] = 3
// [3] = 2
// [4] = 1

容器比较

  • 使用比较运算符可以对两个array容器进行比较。
#include <iostream>
#include <array>
#include <numeric>

int main(int argc, char * argv[]) {
    std::array<double, 5> datas_5_f {};
    std::iota(datas_5_f.begin(), datas_5_f.end(), 1);

    std::array<double, 5> datas_5_s {};
    std::iota(datas_5_s.begin(), datas_5_s.end(), 1);

    std::array<double, 5> datas_5_e {};
    std::iota(datas_5_e.rbegin(),  datas_5_e.rend(), 1);

    std::array<double, 6> datas_5_d {};
    std::iota(datas_5_d.begin(), datas_5_d.end(), 1);

    std::cout << "datas_5_f == datas_5_s ? " << (datas_5_f == datas_5_s ? "是" : "否") << std::endl;
    std::cout << "datas_5_f == datas_5_e ? " << (datas_5_f == datas_5_e ? "是" : "否") << std::endl;
    // std::cout << "datas_5_f == datas_5_d ? " << (datas_5_f == datas_5_d ? "是" : "否") << std::endl; 不能比较

    return 0;
}

注意大小不一致的数组容器不能比较。

vector向量容器

初始化

  • 初始化一个空的vector容器,并通过reserve()函数可以向一个空的容器分配指定个数的容量。
#include <vector>

int main(int argc, char * argv[]) {
    std::vector<double> values;
    values.reserve(20);
    return 0;
}

如果当前容中的元素个数大于20元素,则reserve()无效。

  • 使用初始化列表来指定初始值以及元素个数。
#include <vector>

int main(int argc, char * argv[]) {
    std::vector<double> values {0.5, 0.7, 0.9, 1.1};
    return 0;
}
  • 使用构造方法初始化容器大小。
#include <vector>

int main(int argc, char * argv[]) {
    std::vector<double> values(20);
    return 0;
}

使用这种方法时可以将其中元素都初始化为0。注意在变量后边使用{}()是不一样的,如下所示:

#include <vector>

int main(int argc, char * argv[]) {
    std::vector<int> values {10}; // 初始化一个具体元素,容量是1
    std::vector<int> values (10); // 初始化10个默认元素,容量是10
    return 0;
}
  • 使用构造方法初始化容器大小,并指定默认值。
#include <vector>

int main(int argc, char * argv[]) {
    std::vector<double> values(20, 0.5);
    return 0;
}

使用构造方法初始化的时候,指定的容量大小和默认初始值不用必须是常量,可以经过表达式计算的结果。

  • 使用std::beginstd::end用一个元素类型相同,但容器类型不必相同进行初始化。
#include <vector>
#include <array>

int main(int argc, char * argv[]) {
    std::array<double, 5> datas {0.5, 1.0, 1.5, 2.0, 2.5};
    std::vector<double> values {std::begin(datas), std::end(datas)};
    return 0;
}

容量和扩容

vector的容量是指在不分配更多内存的情况下可以保存的最多元素。

  • 通过size()可获取容器中当前存在的实际元素数量,通过capacity()可以获取容器可以存放的最大元素数量。
#include <vector>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<double> datas {0.5, 1.0, 1.5, 2.0, 2.5};
    datas.reserve(6);

    std::cout << "vector size = " << datas.size() << std::endl;
    std::cout << "vector capacity = " << datas.capacity() << std::endl;
    return 0;
}
// 输出结果
// vector size = 5
// vector capacity = 6
  • 动态扩容

当元素实际数量超过当前容器最大存储范围后,容器自动增加最大容量常见的一般说是2倍,但每次增加的容量取决于当前STL算法的实现
vector扩容

访问元素

  • 通过[index]at[index]获取数据,这两个方法的使用与数组容器一致,不同的是[index]也存在容器越界异常。
#include <vector>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<double> datas {0.5, 1.0, 1.5, 2.0, 2.5};

    std::cout << datas[0] << std::endl;
    std::cout << datas.at(0) << std::endl;
    // std::cout << datas[6] << std::endl; 抛出异常

    return 0;
}
  • 通过front()back()分别返回第一个元素最后一个元素引用
#include <vector>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<double> datas {0.5, 1.0, 1.5, 2.0, 2.5};

    std::cout << datas.front() << std::endl;
    std::cout << datas.back() << std::endl;
    
    return 0;
}
  • 通过data()获取执行容器元素存储内存的首地址。
#include <vector>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<double> datas {0.5, 1.0, 1.5, 2.0, 2.5};

    std::cout << datas.data() << std::endl;
    std::cout << datas.front() << std::endl;

    return 0;
}
// 输出结果
// 000002A07695BEA0
// 0.5

迭代器

  • 通过back_inserter()获取一个向后插入的迭代器。
#include <vector>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<double> datas {0.5, 1.0, 1.5, 2.0, 2.5};

    auto vector_it = datas.begin();
    while (vector_it != datas.end()) {
        std::cout << *vector_it++ << std::endl;
    }

    return 0;
}

添加元素

  • 通过push_back()向容器的尾端添加新元素。
#include <vector>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<double> datas {0.5, 1.0, 1.5, 2.0, 2.5};
	
    datas.push_back(3.0);
    
    auto vector_it = datas.begin();
    while (vector_it != datas.end()) {
        std::cout << *vector_it++ << std::endl;
    }

    return 0;
}
  • 通过emplace_back()添加元素,该函的参数是可变长参数,可变参数的内容与要插入的元素的构造相匹配。
#include <vector>
#include <string>
#include <iostream>

class Person {
public:
    Person(int _age, std::string _name) {
        std::cout << "年龄 = " << _age << std::endl;
        std::cout << "姓名 = " << _name << std::endl;
    }
};

int main(int argc, char * argv[]) {
    std::vector<Person> infos;
    infos.emplace_back(28, "SJQ");
    return 0;
}

push_back()函数底层调用的也是emplace_back()函数,但直接调用push_back()函数会进行move()操作。因此,使用emplace_back()效率会高一些。
push_back函数底层实现

  • 通过emplace_back()函数在指定位置插入元素,该函数第一个参数是一个迭代器,该迭代器指向的位置就是插入元素的位置。
#include <utility>
#include <vector>
#include <string>
#include <iostream>

class Person {
public:
    Person(int _age, std::string _name) {
        age_ = _age;
        name_ = std::move(_name);
    }
    int             age_;
    std::string     name_;
};

int main(int argc, char * argv[]) {
    std::vector<Person> infos;

    infos.emplace_back(20, "S");
    infos.emplace_back(22, "Q");
    infos.emplace(++infos.begin(), 21, "J"); // 在第一个元素后边插入元素

    auto vector_it = infos.begin();
    while (vector_it != infos.end()) {
        std::cout << (*vector_it++).name_;
    }

    return 0;
}
// 输出结果
// SJQ
  • 通过insert()函数插入一个或者多个元素,该函数的第一个参数是插入的迭代器位置,后边的参数是具体元素或者是一个范围迭代器。
#include <vector>
#include <string>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<int> infos {2, 10};
    std::vector<int> temps {4, 6, 8};

    infos.insert(--infos.end(), temps.begin(), temps.end());

    auto vector_it = infos.begin();
    while (vector_it != infos.end()) {
        std::cout << (*vector_it++) << " ";
    }

    return 0;
}
// 输出结果
// 2 4 6 8 10 

删除元素

  • 通过clear()函数可以将容器中的全部元素清空,但不影响容器的容量。
#include <vector>
#include <string>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<int> infos {2, 4, 6, 8, 10};
	infos.clear();
    
    std::cout << "vector size = " << infos.size() << std::endl;
    std::cout << "vector capacity = " << infos.capacity() << std::endl;

    return 0;
}
  • 通过pop_back()函数删除容器结尾的元素。
#include <vector>
#include <string>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<int> infos {2, 4, 6, 8, 10};
    
    infos.pop_back();
    
    std::cout << "vector size = " << infos.size() << std::endl;
    std::cout << "vector capacity = " << infos.capacity() << std::endl;

    return 0;
}
  • 通过shrink_to_fit()函数删除多余的容量
#include <vector>
#include <string>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<int> infos {2, 4, 6, 8, 10};
    infos.reserve(10);
    
    std::cout << "vector size = " << infos.size() << std::endl;
    std::cout << "vector capacity = " << infos.capacity() << std::endl;
    
    infos.shrink_to_fit();
    
    std::cout << "vector size = " << infos.size() << std::endl;
    std::cout << "vector capacity = " << infos.capacity() << std::endl;

    return 0;
}
  • 通过erase()函数删除一个元素。
#include <vector>
#include <string>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<int> infos {2, 4, 6, 8, 10, 12, 14, 16, 18};
    auto iter_1 = infos.erase(std::begin(infos) + 1);

    auto vector_it = infos.begin();
    while (vector_it != infos.end()) {
        std::cout << (*vector_it++) << " ";
    }

    return 0;
}
// 输出结果
// 2 6 8 10 12 14 16 18
  • 通过erase()函数删除一个范围内的元素。
#include <vector>
#include <string>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<int> infos {2, 4, 6, 8, 10, 12, 14, 16, 18};
    auto iter = infos.erase(std::begin(infos) + 1, std::end(infos) - 1);

    auto vector_it = infos.begin();
    while (vector_it != infos.end()) {
        std::cout << (*vector_it++) << " ";
    }

    return 0;
}
// 输出结果
// 2 18
  • 删除后返回的迭代器是删除元素所在位置的下一个元素位置。
#include <vector>
#include <string>
#include <iostream>

int main(int argc, char * argv[]) {
    std::vector<int> infos {2, 4, 6, 8, 10, 12, 14, 16, 18};

    auto iter = infos.erase(std::begin(infos) + 5);

    while (iter != infos.end()) {
        std::cout << (*iter++) << " ";
    }

    return 0;
}
// 输出结果
// 14 16 18

deque双向队列容器

初始化

deque容器的初始化方法与vector容器的初始化方法基本一致,只是没有vector的容量初始化。

#include <deque>

int main(int argc, char * argv[]) {
    std::deque<double> values_1 {0.5, 0.7, 0.9, 1.1};
    std::deque<double> values_2(20);
    std::deque<double> values_3(20, 0.5);
    
    std::array<double, 5> arrays {0.5, 1.0, 1.5, 2.0, 2.5};
    std::deque<double> values_4 {std::begin(datas), std::end(datas)};
    return 0;
}

访问元素

  • 通过[index]at[index]获取数据,这两个方法的使用与向量容器一致
#include <deque>
#include <iostream>

int main(int argc, char * argv[]) {
    std::deque<double> values {0.5, 1.0, 1.5, 2.0, 2.5};

    std::cout << values[0] << std::endl;
    std::cout << values.at(0) << std::endl;
    // std::cout << values[6] << std::endl; 抛出异常

    return 0;
}
  • 通过front()back()分别返回第一个元素最后一个元素引用
#include <deque>
#include <iostream>

int main(int argc, char * argv[]) {
    std::deque<double> values {0.5, 1.0, 1.5, 2.0, 2.5};

    std::cout << values.front() << std::endl;
    std::cout << values.back() << std::endl;
    
    return 0;
}

添加和删除元素

  • push_front()pop_front()可以在容器的头部插入元素,deque容器同样有push_back()pop_back(),使用方法与vector一致。
#include <deque>
#include <iostream>

int main(int argc, char * argv[]) {
    std::deque<double> values;

    values.push_front(1.2);
    values.push_back(2.2);
    values.push_front(1.4);
    values.push_back(2.4);

    auto cur_it = values.begin();

    while (cur_it != values.end()) {
        std::cout << (*cur_it++) << " ";
    }
    return 0;
}
// 输出结果
// 1.4 1.2 2.2 2.4
#include <deque>
#include <iostream>

int main(int argc, char * argv[]) {
   	std::deque<double> values;
    // ......
    values.pop_front();
    values.pop_back();
    
    auto cur_it = values.begin();

    while (cur_it != values.end()) {
        std::cout << (*cur_it++) << " ";
    }
    
    return 0;
}
// 输出结果
// 1.2 2.2
  • 通过emplace_back()emplace_back()函数,前者和vector容器一样在指定位置之后插入一个元素,后者则是在指定位置之前插入一个元素。
#include <deque>
#include <iostream>

int main(int argc, char * argv[]) {
    std::deque<double> values {1.0, 1.5, 2.0, 2.5};

    values.emplace_front(1.3);
    values.emplace_back(1.5);

    auto cur_it = values.begin();

    while (cur_it != values.end()) {
        std::cout << (*cur_it++) << " ";
    }
    return 0;
}

list链表容器

初始化

list容器的初始化方法与vector容器和deque容器的初始化方法基本一致。

#include <list>

int main(int argc, char * argv[]) {
    std::list<double> values_1 {0.5, 0.7, 0.9, 1.1};
    std::list<double> values_2(20);
    std::list<double> values_3(20, 0.5);
    
    std::array<double, 5> arrays {0.5, 1.0, 1.5, 2.0, 2.5};
    std::list<double> values_4 {std::begin(datas), std::end(datas)};
    return 0;
}

访问元素

list容器既没有at(index)也没有[index],可以通过迭代器遍历访问。

  • 通过begin()end()函数进行正向迭代。
#include <list>
#include <iostream>

int main(int argc, char * argv[]) {
    std::list<double> values {1.0, 1.5, 2.0, 2.5};

    auto cur_it = values.begin();

    while (cur_it != values.end()) {
        std::cout << (*cur_it++) << " ";
    }

    return 0;
}
  • 通过rbegin()rend()函数进行逆序迭代。
#include <list>
#include <iostream>

int main(int argc, char * argv[]) {
    std::list<double> values {1.0, 1.5, 2.0, 2.5};

    auto cur_it = values.rbegin();

    while (cur_it != values.rend()) {
        std::cout << (*cur_it++) << " ";
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值