【深蓝学院C++】第9章 容器 笔记

第9章 序列与关联容器

1.概述

(1)容器:一种特殊的类型,其对象可以放置其它类型的对象(元素)

需要支持的操作:对象的添加、删除、索引、遍历

有多种算法可以实现容器,每种方法各有利弊

(2)容器分类

1序列容器:其中的对象有序排列,使用整数值进行索引

2关联容器:其中的对象顺序并不重要,使用键进行索引

3适配器:调整原有容器的行为,使得其对外展现出新的类型、接口或返回新的元素

4生成器:构造元素序列

(3)迭代器:用于指定容器中的一段区间,以执行遍历、删除等操作

获取迭代器: ©begin/©end ; ©rbegin/©rend

迭代器分类:分成 5 类( category ),不同的类别支持的操作集合不同

#include <iostream>
#include <vector>

int main()
{
    std:::vector<int> x{1, 2, 3};
    auto b = x.begin();
    auto e = x.end();    //[b, e)
    for (auto ptr = b; ptr < e; ptr++)
    {
        std::cout << *ptr << ' ';
    }
}

2.序列容器

(1)C++ 标准库中提供了多种序列容器模板

1 array :元素个数固定的序列容器

2 vector :元素连续存储的序列容器

3 forward_list / list :基于链表 / 双向链表的容器

4 deque : vector 与 list 的折中

5 basic_string :提供了对字符串专门的支持

(2)注意点

1需要使用元素类型来实例化容器模板,从而构造可以保存具体类型的容器。

2不同的容器虽然所提供的接口大致相同,但根据容器性质的差异,其内部实现与复杂度不同。

3对于复杂度过高的操作,提供相对较难使用的接口或者不提供相应的接口

(3)array 容器模板

1 具有固定长度的容器,其内部维护了一个内建数组,与内建数组相比提供了复制操作。

#include <iostream>
#include <array>
int main()
{
    std::array<int, 3> a;
    std::array<int, 3> b = a;
}

2 提供的接口

1)构造(与数组类似)

2)成员类型: value_type 等

#include <iostream>
#include <array>
#include <type_traits>
int main()
{
    std::array<int, 3> a;
    std::cout << std::is_same_v<std::array<int, 3>::value_type, int> ;
}

3)元素访问: [] , at , front , back , data

#include <iostream>
#include <array>

int main()
{
    std::array<int, 3> a = {123};
    std::cout << a[0] << std::endl;  
    std::cout << a.at(0) << std::endl;    //大于有效长度程序会崩溃
    std::cout << a.front() << std::endl; 
}

4)容量相关(平凡实现): empty , size , max_size

#include <iostream>
#include <array>

int main()
{
    std::array<int, 3> a = {123};
    std::cout << a.size() << std::endl;  
    std::cout << a.empty() << std::endl; 
}

5)填充与交换: fill , swap

#include <iostream>
#include <array>

int main()
{
    std::array<int, 3> a;
    a.fill(100);
    std::cout << a[0] << ' '
              << a[1] << ' '
              << a[2] << ' ' << endl;
}

6)比较操作: <=>

7)迭代器

(4)vector 容器模板:元素可变

1提供的接口

1)与 array 很类似,但有其特殊性

2)容量相关接口: capacity / reserve / shrink_to_fit

#include <iostream>
#include <vector>

int main()
{
    std:::vector<int> a;
    a.reserve(1024);
    for (int i = 0; i < 1024; i++)
    {
        a,push_back(i);
    }
}

3)附加元素接口: push_back / emplace_back

#include <iostream>
#include <vector>
#include <string>
int main()
{
    std:::vector<std::string> a;
    a.push_back("hello");
    a.emplace_back("hello");    //直接在内存中构造string
}

4)元素插入接口: insert / emplace

5)元素删除接口: pop_back / erase / clear

2注意点

vector 不提供 push_front / pop_front ,可以使用 insert / erase 模拟,但效率不高

swap 效率较高

写操作可能会导致迭代器失效

(5)list 容器模板:双向链表

1与 vector 相比, list

1)插入、删除成本较低,但随机访问成本较高

2)提供了 pop_front / splice 等接口

3)写操作通常不会改变迭代器的有效性

(6)forward_list 容器模板:单向链表

1目标:一个成本较低的线性表实现

2其迭代器只支持递增操作,因此无 rbegin/rend

3不支持 size

4不支持 pop_back / push_back

5XXX_after 操作

(7)deque容器模板: vector 与 list 的折中

push_back / push_front 速度较快

在序列中间插入、删除速度较慢

(8)basic_string 容器模板:实现了字符串相关的接口

使用 char 实例化出 std::string

提供了如 find , substr 等字符串特有的接口

提供了数值与字符串转换的接口

针对短字符串的优化

3.关联容器

(1)使用键进行索引

1 set / map / multiset / multimap

2unordered_set / unordered_map / unordered_multiset / unordered_multimap

set / map / multiset / multimap 底层使用红黑树实现

unordered_xxx 底层使用 hash 表实现

#include <iostream>
#include <map>

int main()
{
    std::map<char, int> m{{'a', 3}, {'b', 4}};
    std::cout << m['a'] << std::endl;
}

(2)set

1通常来说,元素需要支持使用 < 比较大小,或者采用自定义的比较函数来引入大小关系

#include <iostream>
#include <set>

int main()
{
    std::set<int> s{3, 100, 45, 8};
    //std::set<int, std::greater<int>> s{3, 100, 45, 8};
    for (auto ptr = s.begin();ptr != s.end(); ptr++)
    {
        std::cout << *ptr << std::endl;
    }
}
#include <iostream>
#include <set>
struct Str
{
    int x;
};
bool Mycomp(const Str& val1, const Str& val2)
{
    return val1.x < val2.x;
}
int main()
{
    std::set<Str, decltype(&Mycomp)> s({Str{3}, Str{5}}, Mycomp);
}

2插入元素: insert / emplace / emplace_hint

#include <iostream>
#include <set>
struct Str
{
    int x;
};
bool Mycomp(const Str& val1, const Str& val2)
{
    return val1.x < val2.x;
}
int main()
{
    std::set<Str, decltype(&Mycomp)> s({Str{3}, Str{5}}, Mycomp);
    s.insert(Str{100});
    s.enplace(100);
}

3删除元素: erase

4访问元素: find / contains

#include <iostream>
#include <set>

int main()
{
    std::set<int> s{3, 100, 45, 8};
    auto ptr = s.find(3);
    if (ptr != s.end())
        std::cout << *ptr << std::endl;

    std::cout << s.contains(100) << std::endl;
}

5修改元素: extract

注意: set 迭代器所指向的对象是 const 的,不能通过其修改元素

(3)map

1树中的每个结点是一个 std::pair

#include <iostream>
#include <map>

int main()
{
    std::map<char, int> m{{'a', 3}, {'b', 4}};
    for (auto ptr = m.begin();ptr != m.end(); ptr++)
    {
        auto p = *ptr;    //std::pair<const char, int>
        std::cout << p.first << ' ' << p.second << std::endl;
    }
    //基于range-based-for
    for (auto p : m)
    {
        std::cout << p.first << ' ' << p.second << std::endl;
    }
    //或者
    for (auto [k, v] : m)
    {
        std::cout << k << ' ' << v << std::endl;
    }
}

2键 (pair.first) 需要支持使用 < 比较大小,或者采用自定义的比较函数来引入大小关系

3访问元素: find / contains / [] / at

#include <iostream>
#include <map>

int main()
{
    std::map<int, bool> m;
    m.insert(std::pair<const int, bool>(3, true));
    auto ptr = m.find(3);
    std::cout << ptr->first << '' << ptr->second;
    std::cout << m[3] << std::endl;
}

4注意

map 迭代器所指向的对象是 std::pair ,其键是 const 类型

[] 操作不能用于常量对象

#include <iostream>
#include <map>
void fun(const std::map<int, int>& m)
{
    auto ptr = m.find(3);
    if (ptr != m.end())
    {
        std::cout << ptr->second;
    }
}
int main()
{
    std::map<int, bool> m;
    m.insert(std::pair<const int, bool>(3, true));
    fun(m);
}

(4)multiset / multimap

​ 1与set / map类似,但允许重复键

#include <iostream>
#include <set>

int main()
{
    //std::set<int> s{1, 2, 4, 1};
    std::multiset<int> s{1, 2, 4, 1};
    for (auto i : s)
    {
        std::cout << i << std::endl;
    }
}

​ 2元素访问

​ 1)find 返回首个查找到的元素

#include <iostream>
#include <set>

int main()
{
    std::multiset<int> s{1, 2, 4, 1};
    auto ptr = s.find(1);
    std::cout << *ptr << std::endl;
    std::cout << *(++ptr) << std::endl;
}

​ 2)count返回元素个数

#include <iostream>
#include <set>

int main()
{
    std::multiset<int> s{1, 2, 4, 1};
    std::cout << s.count(1) << std::endl;
}

​ 3)lower_bound / upper_bound / equal_range 返回查找到的区间

#include <iostream>
#include <set>

int main()
{
    std::multiset<int> s{1, 2, 4, 1};
    auto b = s.lower_bound(1);
    auto e = s.upper_bound(1);
    for (auto ptr = b; ptr != e;++ptr)
    {
        std::cout << *ptr << std::endl;
    }
    //或者
    auto p = s.equal_range(1);
    for (auto ptr = p.first; ptr != p.second; ++ptr)
    {
        std::cout << *ptr << std::endl;
    }
}

(5)unordered_set / unordered_map / unordered_multiset / unordered_multimap

​ 1与 set / map 相比查找性能更好,但插入操作一些情况下会慢

​ 2键需要支持两个操作

​ 1)转换为 hash 值

​ 2)判等

#include <iostream>
#include <unordered_set>

int main()
{
    std::unordered_set<int> s{3, 1, 5, 4, 1};
    for (auto p : s)
    {
        std::cout << p << std::endl;
    }
}

4.适配器与生成器

(1)类型适配器

​ 1basic_string_view ( C++17 )

​ 可以基于 std::string , C 字符串,迭代器构造

​ 提供成本较低的操作接口

​ 不可进行写操作

#include <iostream>
#include <string_view>

void fun(std::string_view str)
{
    if (!str.empty())
        std::cout << str[0] << std::endl;
}

int main()
{
    fun("12345");   //c
    fun(std::string("12345"));  //c++
}
#include <iostream>
#include <string>
#include <string_view>

void fun(std::string_view str)
{
    std::cout << str << std::endl;
}

int main()
{
    fun("12345");   //c

    std::string s("12345");
    fun(std::string_view(s.begin(), s.begin() + 3));  //c++
}
#include <iostream>
#include <string_view>

std::string_view fun(std::string_view input)
{
    return input.substr(0, 3);
}

int main()
{
    std::string s("12345");
    auto res = fun(s);
    std::cout << res << std::endl;
}

​ 2 span ( C++20 )

​ 可基于 C 数组、 array 等构造

​ 可读写

#include <iostream>
#include <vector>
#include <span>

void fun(std::span<int> input)
{
    for (auto p : input)
    {
        std::cout << p << ' ';
    }
    std::cout << std::endl;
}

int main()
{
    std::vector<int> s{1, 2, 3};
    fun(s);
}

(2)接口适配器

​ 1 stack / queue / priority_queue

​ 对底层序列容器进行封装,对外展现栈、队列与优先级队列的接口

​ priority_queue 在使用时其内部包含的元素需要支持比较操作

(3)数值适配器 (c++20)

​ std::ranges::XXX_view, std::ranges::views::XXX, std::views::XXX

​ 1可以将一个输入区间中的值变换后输出

​ 2数值适配器可以组合,引入复杂的数值适配逻辑

#include <iostream>
#include <vector>
#include <ranges>

bool isEven(int i)
{
    return i % 2 == 0;
}
int Square(int i)
{
    return i * i;
}
int main()
{
    std::vector<int> s{1, 2, 3, 4, 5};
    //1
    for (auto p : std::ranges::filter_view(v, isEven))
    {
        std::cout << p << ' ';
    }
    std::cout << std::endl;
    
    //2
    for (auto p : std::ranges::transform_view(v, Square))
    {
        std::cout << p << ' ';
    }
    std::cout << std::endl;
    
    //3
    auto x0 = std::views::filter(isEven);
   	for (auto p : x0(v))	//或 v | x0
    {
        std::cout << p << ' ';
    }
    std::cout << std::endl;
    
    //4
    auto x = std::views::filter(isEven);
    auto y = std::views::transform(Square);
   	for (auto p : v | x | y)	
    {
        std::cout << p << ' ';
    }
    std::cout << std::endl;
    
}

(4)生成器 (c++20)

​ std::ranges::itoa_view, std::ranges::views::itoa, std::views::itoa

​ 可以在运行期生成无限长或有限长的数值序列

#include <ranges>
#include <iostream>
 
int main()
{
    for (int i : std::ranges::iota_view{1, 10})
        std::cout << i << ' ';
    std::cout << '\n';
 
    for (int i : std::views::iota(1, 10))
        std::cout << i << ' ';
    std::cout << '\n';
 
    for (int i : std::views::iota(1) | std::views::take(9))
        std::cout << i << ' ';
    std::cout << '\n';

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值