从C++开始的编程生活(19)——set和map

前言

本系列文章承接C++基础的学习,需要++有C语言的基础++ 才能学会哦~

第19篇主要讲的是有关于C++的++set++ 和++map++ 。
C++才起步,都很简单!!

序列式容器和关联式容器

**序列式容器:**逻辑结构为线性,存储的值之间一般没有紧密联系,两个位置的值交换后依旧是序列式容器。如string、vector、list、deque、array和forward_list等。

**关联式容器:**逻辑结构通常为非线性结构,存储的值之间有紧密的关联,两个位置的值交换后存储结构就会被破坏。如map/set系列和unordered_map/unordered_set系列。

map和set的底层是红黑树,红黑树是平衡二叉搜索树。

set和multiset

set不允许冗余,multiset允许冗余。

set类介绍

声明如下:

cpp 复制代码
template<class T,
         class Compare = less<T>,
         class Alloc = allocator<T>
        >class set;

共三个模板参数。

Ⅰ.第一个参数T为底层关键字(码)的类型。

Ⅱ.第二个参数Compare为比较函数,不传递时,默认要求T支持小于比较,如果T类型不支持或者想按自己的需求,就传递自行实现的仿函数。

Ⅲ.第三个参数Alloc为底层存储数据的内存空间,不传递时,默认在空间配置器中申请,如果需要,可以传递自行实现的内存池。

Ⅳ.一般只传第一个参数即可。

Ⅴ.set底层用红黑树实现,增删查的效率是O(logN),迭代器遍历用中序,所以是有序的遍历。

set的构造和迭代器

set支持双向迭代器,迭代器的迭代顺序遵循二叉搜索树的中序。

使用方法和之前学习的序列式容器几乎相同,这里不多赘述。

set的功能和接口

insert()

cpp 复制代码
set<int> s;
//也可以
//set<int, greater<int>> s;

s.insert(5);
s.insert(7);
s.insert(1);
s.insert(2);
//也可以
//s.insert({5,7,1,2});


auto it = s.begin();
while (it != s.end())
{
	cout << *it << "  ";
	++it;
}

cout << endl;


set<string> strset = {"sort", "insert", "add"};
//string按照ascll码大小进行遍历
for(auto& e : strset)
{
    cout << e << " ";
}
cout << endl;

erase()

返回删除成功的个数。

cpp 复制代码
int main()
{
	set<int> s = { 4,2,7,2,8,5,9 };
	for (auto e : s)
	{
		cout << e << "  ";
	}
	cout << endl;

	//删除最小值
	s.erase(s.begin());
	for (auto e : s)
	{
		cout << e << "  ";
	}
	cout << endl;

	//直接删除x
	int x;
	cin >> x;
	int num = s.erase(x);
	if (num == 0)
	{
		cout << x << "不存在!" << endl;
	}

	for (auto e : s)
	{
		cout << e << "  ";
	}
	cout << endl;

	return 0;
}

找到就删,找不到就不删,返回删除对象的个数。

swap()

交换两个容器之内的内容。

cpp 复制代码
#include <iostream>
#include <set>
#include <functional>

int main() {
    // 定义两个类型一致的set(都是int,默认less<int>)
    std::set<int> s1 = {1, 2, 3};
    std::set<int> s2 = {10, 20, 30};

    std::cout << "交换前:" << std::endl;
    std::cout << "s1: ";
    for (int num : s1) std::cout << num << " "; // 输出:1 2 3
    std::cout << "\ns2: ";
    for (int num : s2) std::cout << num << " "; // 输出:10 20 30

    // 调用swap交换s1和s2
    s1.swap(s2);

    std::cout << "\n\n交换后:" << std::endl;
    std::cout << "s1: ";
    for (int num : s1) std::cout << num << " "; // 输出:10 20 30
    std::cout << "\ns2: ";
    for (int num : s2) std::cout << num << " "; // 输出:1 2 3

    return 0;
}

set的swap交换很快,他只交换两个容器之间的指针。

lower_bound()和upper_bound()

用于查找某区间。

cpp 复制代码
int main()
{
	set<int> s = { 4,2,7,2,8,5,9 };
	for (auto e : s)
	{
		cout << e << "  ";
	}
	cout << endl;
    
    //返回≥2的第一个数值的位置
	auto itlow = s.lower_bound(2);
    //返回>5的第一个数值的位置
	auto itup = s.upper_bound(5);
    
    //实际上删除的是[2,7)这个区间
	s.erase(itlow, itup);

	for (auto e : s)
	{
		cout << e << "  ";
	}
	cout << endl;

	return 0;
}

删除掉了区间 [ 2,7 )的数值

upper_bound( n )返回的是>n 的第一个数值的位置;

lower_bound( n )返回的是 ≥ n 的第一个数值的位置,从而实现左闭右开。

find()

set自身实现了一个find()函数,而不是用标准库里的find(),这个set.find()能更好兼容set,时间复杂度为O(logN),标准库的find()适配的是序列式容器,时间复杂度为O(N)。

count()

返回某个key的个数。

set因为不支持冗余,因此只会返回0或者1。multiset会返回实际存储的个数,返回结果 ≥ 0。

可以用来查找对象是否存在于set中。

multiset类与set的不同点

①multiset类可以插入冗余值x,不去重。

②find查找时,x存在多个时,找中序的第一个。

③erase删除时,会删除所有的x值。

④count会返回x的实际个数。

map和mutimap

map类介绍

声明如下:

cpp 复制代码
template<class Key,
         class T,
         class Compare = less<key>,
         class Alloc = allocator<pair<const Key, T>>
         >class map

pair类型

pair是map底层的红黑树结点的数据,它是Key,T的键值对数据,是一个类模板

cpp 复制代码
//initializer_list构造和迭代遍历
map<string, string> dict = { {"left","左边"} ,{"right","右边"},{"insert","插入"},{"string","字符串"} };

//其中,调用了pair的构造,即等效于:
pair<string, string> kv1 = { "left", "左边" };
pair<string, string> kv2 = { "right","右边" };
pair<string, string> kv3 = { "insert","插入" };
pair<string, string> kv4 = { "string","字符串" };
map<string, string> dict = { kv1,kv2,kv3,kv4 };

pair.first为pair的键,pair.second为pair的值。

cpp 复制代码
map<string, string>::iterator it = dict.begin();

while (it != dict.end())
{
	cout << (*it).first << ":" << (*it).second << " ";
	++it;
}
cout << endl;

map的->重载过,map迭代器的->运算符直接返回对应的pair对象指针,使得(*it).first 等效于 it->first。

cpp 复制代码
map<string, string>::iterator it = dict.begin();

while (it != dict.end())
{
	cout << it->first << ":" << it->second << " ";
	++it;
}
cout << endl;

++pair不支持流插入。++

map的构造和迭代器

map同样支持双向迭代器~

map的功能与接口

insert()

有四种方法进行插入,推荐第四种,最方便~

cpp 复制代码
pair<string, string> kv1 = { "first", "第一个" };
//initializer_list构造和迭代遍历
map<string, string> dict = { {"left","左边"} ,{"right","右边"},{"insert","插入"},{"string","字符串"} };

//直接插入
dict.insert(kv1);

//插入匿名对象,匿名对象要自行指明类型
dict.insert(pair<string, string>("second", "第二个"));

//使用函数模板make_pair,则不需要自行指明类型
dict.insert(make_pair("sort", "排序"));

//多参数隐式类型转换
dict.insert({"auto","自动的"});

其中make_pair为函数模板:

cpp 复制代码
template <class T1, class T2>
inline pair<T1, T2> make_pair (T1 x, T2 y)
{
    return ( pair<T1, T2>(x, y) );
}

find()

同set,map也推荐使用自带的find(),而不是标准库的find()。自带的find()时间复杂度低。

erase()

通过key,来删除map中的对象。

operator[ ]

通过key,返回对应value的引用。

insert的返回值

insert()会返回一个pair<iterator, bool>的对象。

first 为传入key所在结点的迭代器,second为代表插入成功与否的bool值。

故无论插入成功与否,first都会返回对应的key结点迭代器,所以插入失败的时候,可以充当一个查找的功能。基于这一点,我们可以用来实现operator[ ]。

operator[ ] 的内部实现
cpp 复制代码
mapped_type& operator[] (const key_type& k)
{
//若k不在map中,insert会插入k和mapped_typed的类型默认值
//,同时返回传入默认值的引用,通过引用修改返回的映射值,从而达到"插入"+"修改"的功能

//若k在map中,insert插入失败,返回k对应结点的迭代器,
//同时,该函数返回了对应迭代器的映射值的引用,同理通过修改返回的映射值,
//达到"查询"+"修改"的功能。
    pair<iterator, bool> ret = insert({k, mapped_type()});
    iterator it = ret.first;
    return it->second;
}

so,我们可以通过operator[ ]来++实现①查询+修改②插入+修改++ 。但是如果只想查询的话,使用find即可,因为operator底层会调用insert,++不是单纯的查询,所以会导致非期望的默认插入,出现性能损耗,逻辑错误等问题。++

multimap与map的区别

①multimap没有operator[ ] 接口。因为multimap可能会存放多个相同key的对象,索引会有歧义。

补充~

结构化绑定(C++17支持)

cpp 复制代码
for(const auto& [k,v] : dict)
{
    cout << k << ":" << v << endl;
}
cout << endl;

结构化绑定是 C++17 引入的语法糖,核心作用是:将一个聚合类型(如数组、结构体、std::pair/std::tuple、map 的键值对)的多个成员,一次性绑定到多个变量上 ,无需手动通过 .-> 访问成员,让代码更简洁。

简单说:它帮你 "拆解" 复杂类型,把内部的多个值直接赋值给多个变量,就像 "一次性解构赋值"。

❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤

相关推荐
bkspiderx2 小时前
MQTT C/C++开源库全解析:从嵌入式到高并发场景的选型指南
c语言·c++·mqtt·开源·开源库
样例过了就是过了2 小时前
LeetCode热题100 岛屿数量
数据结构·c++·算法·leetcode·dfs
qq_172805592 小时前
基于Go的动态定时器管理功能架构方案设计与实现
开发语言·架构·golang
小乔的编程内容分享站2 小时前
C语言笔记之结构体第二篇
c语言·开发语言·笔记
codeJinger2 小时前
【Python】集合
开发语言·python
俩娃妈教编程2 小时前
C++基础知识点:位运算
java·开发语言·jvm·c++·位运算
zhoupenghui1682 小时前
golang 锁实现原理与解析&锁机制(sync)种类与举例说明以及其使用场景
开发语言·后端·golang·mutex·wait·lock·sync
路弥行至2 小时前
linux运行脚本出现错误信息 /bin/bash^M: bad interpreter解决方法
linux·运维·开发语言·经验分享·笔记·其他·bash
一直不明飞行2 小时前
C++ pari使用的两个注意事项
开发语言·c++