C++标准库学习(一) STL体系结构基础

此系列文章都是根据侯捷老师的系列教学视频进行编写,记录自己在学习STL过程中的知识点。

C++标准库

STL(Standard Template Library)标准模板库,以头文件的形式呈现:

  • C++标准库的头文件不带有后缀,例如#include <vector>
  • 新式的C头文件不带有后缀,例如#include <cstdio>
  • 旧式的C头文件带有后缀,例如#include <stdio.h>

C++标准库的重要网页(查找函数说明,还有例子说明):

cplusplus
cppreference

STL六大部件

image-20230210205614103

STL一共有六个部件,相辅相成各自有各自的功能:

  • 容器:最重要的部件之一,用来存放东西,存放东西就涉及到使用什么数据结构存取,因为也分为序列容器关联容器不定序容器(后面讲解)
  • 分配器:容器既然是存取东西的,那么就一定涉及到内存,C++标准库中把内存都封装好,让我们看不到,而这一部分的工作就是分配器做的(主要做容器与内存之间的连通
  • 算法:最重要的部件之一,按照拥护要求对容器中的数据进行不同的计算方法,算法中也涉及到全局算法和容器自带算法(后面讲解)
  • 迭代器:既然算法是需要计算容器中的元素,那么就需要一个东西能够取出容器中的元素,这就是迭代器(泛型指针,指向容器中的元素)
  • 适配器:在STL中适配器起转换的作用,比如容器适配器stack和queue,它们实际上都是以deque容器为底层的,只是为了实现堆栈的先入先出和先入后出的特性而转换的
  • 仿函数:当你用一个算法去求加法的时候,如果是数字可以直接相加,但是如果是一棵树一个人一列表格等抽象化的东西,怎么相加呢,这就是仿函数实现的,可以自定义算法的运行规则

容器分类与测试

image-20230210211852919

上图中就是所有的容器分类,每一列分别代表不同的容器类别(数据结构):

  • Sequence Containers(序列容器):也就是我们熟知的数组、链表等数据结构。序列容器的优点在于存储方便(不同容器有不同的存储特性),但是想要查找数据就会很慢
  • Associative Containers(关联容器):使用**红黑树(就是完全平衡的二叉树)**存储,优点在于可以快速查找(因为每次存取都会移动树使其完全平衡,令其一直有快速搜索的能力)
  • Unordered Containers(不定序容器):使用哈希表存储,其实跟关联容器是一样的优点,其实有些也把这个容器归并与关联容器

注意:所有的容器都是符合一个原则:左闭右开(即所有容器的begin都是指向容器的第一个元素,end指向容器的最后一个元素再+1的位置

array

image-20230210214548834

就是我们熟知的数组,既然是数组,就需要在使用前指定数组的大小(一般不怎么用,都是使用vector

#include <array>
#define ASIZE 500000
void test_array()
{
	cout << "\ntest_array().......... \n";
	array<long,ASIZE> c;                            //数组array不仅需要指定数据类型,还需要指定数据量有多少,这是数组的特性              

	clock_t timeStart = clock();
    for(long i=0; i< ASIZE; ++i) {
        c[i] = rand();                              //数组中随机放置数值
    }
	cout << "milli-seconds : " << (clock()-timeStart) << endl;
	cout << "array.size()= " << c.size() << endl;
	cout << "array.front()= " << c.front() << endl;
	cout << "array.back()= " << c.back() << endl;
	cout << "array.data()= " << c.data() << endl;     //.data()得到的是指针,指向数组的第一个元素

	long target = get_a_target_long();                //输入我们要查找的数值

	timeStart = clock();
    //这里使用了::是为了告诉编译器这是从全局函数中找qsort函数,平常我们写的时候可以不用写::
    //这里的compareLongs就是使用了自定义的排序规则
    ::qsort(c.data(), ASIZE, sizeof(long), compareLongs);    //这是使用的qsort排序算法
    //从c中使用bsearch函数二分查找,找到target,将指针返回pIem
	long* pItem = (long*)::bsearch(&target, (c.data()), ASIZE, sizeof(long), compareLongs);
	cout << "qsort()+bsearch(), milli-seconds : " << (clock()-timeStart) << endl;    //看看排序和查找一共花了多久
  	if (pItem != NULL)
    	cout << "found, " << *pItem << endl;
  	else
    	cout << "not found! " << endl;
}

输出如下:

test_array()..........
milli-seconds : 12
array.size()= 500000
array.front()= 22596
array.back()= 17507
array.data()= 0x5c40817120
target (0~32767):23456
 qsort()+bsearch(), milli-seconds : 78
found, 23456
vector

image-20230210214708582

常用的容器之一,跟数组最主要的不同就是一端可以自动增长。注意:vector空间不够进行扩充时,是在另一个地方找到一个两倍大的地方,再把数据都迁移过去

#include <vector>
void test_vector(long& value)
{
	cout << "\ntest_vector().......... \n";

	vector<string> c;
	char buf[10];
	//下面存数据使用了try catch语句,主要是为了防止存放数据太多导致程序崩溃
	clock_t timeStart = clock();
    for(long i=0; i< value; ++i)
    {
    	try {
    		snprintf(buf, 10, "%d", rand());
        	c.push_back(string(buf)); 
		}
		catch(exception& p) {
			cout << "i=" << i << " " << p.what() << endl;
			abort();                 //如果数据过多了,就会运行到这了,abort函数就会退出程序
		}
	}
	cout << "milli-seconds : " << (clock()-timeStart) << endl;
	cout << "vector.max_size()= " << c.max_size() << endl;	
	cout << "vector.size()= " << c.size() << endl;                   //vector中有多少元素
	cout << "vector.capacity()= " << c.capacity() << endl << endl;   //vector的容量是多少

	string target = get_a_target_string();
	//下面使用了两个方法进行查找
	timeStart = clock();
	auto pItem = find(c.begin(), c.end(), target);
	cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
  	if (pItem != c.end())
    	cout << "found, " << *pItem << endl << endl;
  	else
    	cout << "not found! " << endl << endl;

	timeStart = clock();
    sort(c.begin(), c.end());
	cout << "sort(), milli-seconds : " << (clock()-timeStart) << endl;
	timeStart = clock();
	string* pItem = (string*)::bsearch(&target, (c.data()),c.size(), sizeof(string), compareStrings);
	cout << "bsearch(), milli-seconds : " << (clock()-timeStart) << endl;
  	if (pItem != NULL)
    	cout << "found, " << *pItem << endl << endl;
  	else
    	cout << "not found! " << endl << endl;
}

输出如下:

test_vector()..........
milli-seconds : 366
vector.max_size()= 288230376151711743
vector.size()= 500000
vector.capacity()= 524288

target (0~32767): ->23456
std::find(), milli-seconds : 1
found, 23456
sort(), milli-seconds : 520
bsearch(), milli-seconds : 2
found, 23456
list

image-20230210221056854

list是一个双向链表,每个元素都存放着两个指针,分别指向前一个和后一个元素

#include <list>
void test_list(long& value)
{
	cout << "\ntest_list().......... \n";
	list<string> c;
	char buf[10];

	clock_t timeStart = clock();
    for(long i=0; i< value; ++i)
    {
    	try {
    		snprintf(buf, 10, "%d", rand());
        	c.push_back(string(buf));                //使用push_back进行数据的存放
		}
		catch(exception& p) {
			cout << "i=" << i << " " << p.what() << endl;
			abort();
		}
	}
	cout << "milli-seconds : " << (clock()-timeStart) << endl;
	cout << "list.size()= " << c.size() << endl;
	cout << "list.max_size()= " << c.max_size() << endl;    //357913941
	cout << "list.front()= " << c.front() << endl;
	cout << "list.back()= " << c.back() << endl;

	string target = get_a_target_string();
    timeStart = clock();
	auto pItem = find(c.begin(), c.end(), target);
	cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
  	if (pItem != c.end())
    	cout << "found, " << *pItem << endl;
  	else
    	cout << "not found! " << endl;

    timeStart = clock();
	c.sort();                   //使用了容器自身的sort函数
	cout << "c.sort(), milli-seconds : " << (clock()-timeStart) << endl;
}

需要注意的是:最后用了c.sort() 函数,这是使用了容器自带的算法函数而没有使用全局的sort算法函数:

  • 表明,有的容器自身是带有一些算法函数的,当使用算法的时候,使用自身带有的算法函数一般情况下都会比全局算法函数要快(而下面的例子可能赶巧了)
test_list()..........
milli-seconds : 199
list.size()= 500000
list.max_size()= 192153584101141162
list.front()= 30596
list.back()= 25823
target (0~32767): ->23456
std::find(), milli-seconds : 2
found, 23456
c.sort(), milli-seconds : 935
forward_list

image-20230210222510120

这是一个单向列表,整体的使用方式跟list一样

#include <forward_list>
void test_forward_list(long& value)
{
	cout << "\ntest_forward_list().......... \n";
	forward_list<string> c;
	char buf[10];

	clock_t timeStart = clock();
    for(long i=0; i< value; ++i)
    {
    	try {
    		snprintf(buf, 10, "%d", rand());
        	c.push_front(string(buf));                      //注意这里使用的是push_front,而不是push_back
		}
		catch(exception& p) {
			cout << "i=" << i << " " << p.what() << endl;
			abort();
		}
	}
	cout << "milli-seconds : " << (clock()-timeStart) << endl;
	cout << "forward_list.max_size()= " << c.max_size() << endl;  //536870911
	cout << "forward_list.front()= " << c.front() << endl;

	string target = get_a_target_string();
    timeStart = clock();
	auto pItem = find(c.begin(), c.end(), target);
	cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
	if (pItem != c.end())
    	cout << "found, " << *pItem << endl;
  	else
    	cout << "not found! " << endl;

    timeStart = clock();
	c.sort();                 //使用算法自带函数
	cout << "c.sort(), milli-seconds : " << (clock()-timeStart) << endl;
}

forward_list使用的是push_front进行数据的存放,还有个容器是slist容器,这是以前旧版本的名字,用法跟forward_list一样:

test_forward_list()..........
milli-seconds : 307
forward_list.max_size()= 230584300921369395
forward_list.front()= 15262
target (0~32767): ->23456
std::find(), milli-seconds : 1
found, 23456
c.sort(), milli-seconds : 1069
deque

image-20230210230034478

deque是个双向队列,从一个内存的角度出发,开辟的数据怎么可能前后都能够一直扩充,从下图中可以看出:

  • deque容器实际上存放的是指针,每个指针都指向一个buffer,每个buffer中都存放8个元素(实际上每个buffer有很多元素),因此deque前后都可以扩充指针大小
  • 每次扩充数据的时候,如果当前buffer不够了,就重新扩充一个buffer,因此每次只会扩充出一个buffer的大小(减少资源浪费)
  • 容器的迭代器指向的是buffer中的元素,那么迭代器指向前一个buffer的最后一个元素时,再++操作后,会自动转向下一个buffer的头(第一个元素),实现的方式是重载了++运算符

image-20230210230103795

对比各个容器扩充数据的方式:

  • vector每次扩充都是两倍扩充,那么就很容易造成资源浪费
  • list每次扩充都是一个元素大小
  • deque每次扩充一个buffer,能够减少一定程度的资源浪费
#include <deque>
void test_deque(long& value)
{
	cout << "\ntest_deque().......... \n";
	deque<string> c;
	char buf[10];

	clock_t timeStart = clock();
    for(long i=0; i< value; ++i)
    {
    	try {
    		snprintf(buf, 10, "%d", rand());
        	c.push_back(string(buf));          //也可以push_front
		}
		catch(exception& p) {
			cout << "i=" << i << " " << p.what() << endl;
			abort();
		}
	}
	cout << "milli-seconds : " << (clock()-timeStart) << endl;
	cout << "deque.size()= " << c.size() << endl;
	cout << "deque.front()= " << c.front() << endl;
	cout << "deque.back()= " << c.back() << endl;
	cout << "deque.max_size()= " << c.max_size() << endl;	

	string target = get_a_target_string();
    timeStart = clock();
	auto pItem = find(c.begin(), c.end(), target);
	cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
	if (pItem != c.end())
    	cout << "found, " << *pItem << endl;
  	else
    	cout << "not found! " << endl;

    timeStart = clock();
	sort(c.begin(), c.end());           //使用的是全局算法函数
	cout << "sort(), milli-seconds : " << (clock()-timeStart) << endl;
}

输出如下:

test_deque()..........
milli-seconds : 111
deque.size()= 500000
deque.front()= 5809
deque.back()= 15313
deque.max_size()= 288230376151711743
target (0~32767): ->23456
std::find(), milli-seconds : 0
found, 23456
sort(), milli-seconds : 597
stack/queue
stack(先进后出)queue(先进先出)
image-20230211093419021image-20230211093457559
  • stack和queue的底层容器都是deque容器,因此这两个叫容器适配器
  • 为了满足先进后出(先进先出)的特性,这两个没有迭代器指向其中的元素,因为如果有迭代器指向元素,就可以修改元素,就不能满足特性了
  • 由于没有迭代器,也就没有算法,只要pushpop
set/multiset

image-20230211095249813

使用二叉树进行数据存储,是一个完全平衡二叉树,保证搜索数据的快速:

  • set存储的元素不能相同,multiset存储的元素可以相同。因此输出中set.size()= 32768multiset.size()= 500000
#include <set>
void test_multiset(long& value)
{
	cout << "\ntest_multiset().......... \n";
	multiset<string> c;
	char buf[10];
	
	clock_t timeStart = clock();
    for(long i=0; i< value; ++i)
    {
    	try {
    		snprintf(buf, 10, "%d", rand());
        	c.insert(string(buf));                  //使用insert进行数据插入
		}
		catch(exception& p) {
			cout << "i=" << i << " " << p.what() << endl;
			abort();
		}
	}
	cout << "milli-seconds : " << (clock()-timeStart) << endl;
	cout << "multiset.size()= " << c.size() << endl;
	cout << "multiset.max_size()= " << c.max_size() << endl;

	string target = get_a_target_string();
	{
    timeStart = clock();
	auto pItem = find(c.begin(), c.end(), target);	//比 c.find(...) 慢很多
	cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
	if (pItem != c.end())
    	cout << "found, " << *pItem << endl;
  	else
    	cout << "not found! " << endl;
 	}

 	{
    timeStart = clock();
	auto pItem = c.find(target);		//比 std::find(...) 快很多
	cout << "c.find(), milli-seconds : " << (clock()-timeStart) << endl;
	if (pItem != c.end())
    	cout << "found, " << *pItem << endl;
  	else
    	cout << "not found! " << endl;
 	}
}

使用自带的算法函数find会更快,这是关联容器使用二叉树的特点:

test_multiset()..........
milli-seconds : 1199
multiset.size()= 500000
multiset.max_size()= 144115188075855871
target (0~32767): ->23456
std::find(), milli-seconds : 52
found, 23456
c.find(), milli-seconds : 0
found, 23456
map/multimap

image-20230211101944481

使用二叉树结构,每个节点都是由keyvalue组成,可以使用key进行搜索:

  • map存储的key不能相同,multimap存储的key可以相同。
#include <map>
void test_multimap(long& value)
{
	cout << "\ntest_multimap().......... \n";
	multimap<long, string> c;
	char buf[10];

	clock_t timeStart = clock();
    for(long i=0; i< value; ++i)
    {
    	try {
    		snprintf(buf, 10, "%d", rand());
    		//multimap 不可使用 [] 做 insertion
        	c.insert(pair<long,string>(i,buf));
		}
		catch(exception& p) {
			cout << "i=" << i << " " << p.what() << endl;
			abort();
		}
	}
	cout << "milli-seconds : " << (clock()-timeStart) << endl;
	cout << "multimap.size()= " << c.size() << endl;
	cout << "multimap.max_size()= " << c.max_size() << endl;	//178956970

	long target = get_a_target_long();
    timeStart = clock();
	auto pItem = c.find(target);
	cout << "c.find(), milli-seconds : " << (clock()-timeStart) << endl;
	if (pItem != c.end())
    	cout << "found, value=" << (*pItem).second << endl;
  	else
    	cout << "not found! " << endl;
}

输出如下:

test_multimap()..........
milli-seconds : 668
multimap.size()= 500000
multimap.max_size()= 128102389400760775
target (0~32767):23456
 c.find(), milli-seconds : 2
found, value=1669
unordered_set/unordered_multiset

image-20230211103535848

除了存储结构不同,剩下都跟前面差不多。哈希表的一列就是篮子,每个元素都在各个篮子下挂着,使用单向链表的方式存储:

  • 由于使用的是单向链表,那么在查找数据的时候,某个链表就不能很长(否则查找很慢),因此哈希表中不会有某几个篮子非常长
#include <unordered_set>
void test_unordered_multiset(long& value)
{
	cout << "\ntest_unordered_multiset().......... \n";
	unordered_multiset<string> c;
	char buf[10];

	clock_t timeStart = clock();
    for(long i=0; i< value; ++i)
    {
    	try {
    		snprintf(buf, 10, "%d", rand());
        	c.insert(string(buf));
		}
		catch(exception& p) {
			cout << "i=" << i << " " << p.what() << endl;
			abort();
		}
	}
	cout << "milli-seconds : " << (clock()-timeStart) << endl;
	cout << "unordered_multiset.size()= " << c.size() << endl;
	cout << "unordered_multiset.max_size()= " << c.max_size() << endl;	//357913941
	cout << "unordered_multiset.bucket_count()= " << c.bucket_count() << endl;
	cout << "unordered_multiset.load_factor()= " << c.load_factor() << endl;
	cout << "unordered_multiset.max_load_factor()= " << c.max_load_factor() << endl;
	cout << "unordered_multiset.max_bucket_count()= " << c.max_bucket_count() << endl;
  	for (unsigned i=0; i< 20; ++i) {
    	cout << "bucket #" << i << " has " << c.bucket_size(i) << " elements.\n";
  	}

	string target = get_a_target_string();
    timeStart = clock();
	auto pItem = find(c.begin(), c.end(), target);	//比 c.find(...) 慢很多
	cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
	if (pItem != c.end())
    	cout << "found, " << *pItem << endl;
  	else
    	cout << "not found! " << endl;

    timeStart = clock();
	auto pItem = c.find(target);		//比 std::find(...) 快很多
	cout << "c.find(), milli-seconds : " << (clock()-timeStart) << endl;
	if (pItem != c.end())
    	cout << "found, " << *pItem << endl;
  	else
    	cout << "not found! " << endl;
}

输出如下:

  • .bucket_count()就是篮子数量,篮子数量一定会比数据数量多
  • 哈希表的每次扩充,都会将数据打散,重新放置
  • unordered_set存储的元素不能相同,unordered_multiset存储的元素可以相同。因此输出中unordered_set.size()= 32768unordered_multiset.size()= 500000
test_unordered_multiset()..........
milli-seconds : 606
unordered_multiset.size()= 500000
unordered_multiset.max_size()= 192153584101141162
unordered_multiset.bucket_count()= 712697
unordered_multiset.load_factor()= 0.70156
unordered_multiset.max_load_factor()= 1
unordered_multiset.max_bucket_count()= 192153584101141162
bucket #0 has 0 elements.
bucket #1 has 9 elements.
bucket #2 has 0 elements.
bucket #3 has 0 elements....
target (0~32767): ->23456
std::find(), milli-seconds : 18
found, 23456
c.find(), milli-seconds : 0
found, 23456
unordered_map/unordered_multimap

image-20230211103535848

整体跟前面一样:

#include <unordered_map>
void test_unordered_multimap(long& value)
{
	cout << "\ntest_unordered_multimap().......... \n";
	unordered_multimap<long, string> c;
	char buf[10];

	clock_t timeStart = clock();
    for(long i=0; i< value; ++i)
    {
    	try {
    		snprintf(buf, 10, "%d", rand());
			//multimap 不可使用 [] 進行 insertion
			c.insert(pair<long,string>(i,buf));
		}
		catch(exception& p) {
			cout << "i=" << i << " " << p.what() << endl;
			abort();
		}
	}
	cout << "milli-seconds : " << (clock()-timeStart) << endl;
	cout << "unordered_multimap.size()= " << c.size() << endl;
	cout << "unordered_multimap.max_size()= " << c.max_size() << endl;	//357913941

	long target = get_a_target_long();
    timeStart = clock();
	auto pItem = c.find(target);
	cout << "c.find(), milli-seconds : " << (clock()-timeStart) << endl;
	if (pItem != c.end())
    	cout << "found, value=" << (*pItem).second << endl;
  	else
    	cout << "not found! " << endl;
}

输出如下:

test_unordered_multimap()..........
milli-seconds : 325
unordered_multimap.size()= 500000
unordered_multimap.max_size()= 192153584101141162
target (0~32767):23456
c.find(), milli-seconds : 1
found, value=381

分配器

分配器…总的来说,可以使用自定义,但是没必要…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值