C++ STL和泛型编程(一)

STL:Standard Template Library(标准模板库)
GP:Generic Programming(泛型编程)

STL是GP最成功的作品!!

一、STL六大部件

  • Allocators(分配器)
  • Containers(容器)
  • Algorithms(算法)
  • Iterators(迭代器)
  • Adapters(适配器)
  • Functors(仿函式)

六大部件的关系如图:
在这里插入图片描述

1.首先接触到的是Containers,而容器要数据,数据需要占用内存,
2.所以又涉及到分配内存的问题,所以Allocator就是给容器分配对应存放元素的内存的分配器。
3.而需要对存放容器的里的数据元素进行操作时,虽然容器是一个模板类且有一部分操作是通过容器内的函数进行操作的,但大部分操作都是被独立出来变成一个一个模板函数放在容器外部的即Algorithms。
(注意:在面向对象(OO)这种编程技巧时,在定义类时是鼓励将数据放在类里面,处理数据的函数也放在类内,从而形成一个class;但在STL中,则是鼓励数据放在容器中,处理数据的函数放在算法中,分开的,所以设计方式跟面向对象的不同。)
4.当算法要去处理容器如List中的数据时,则需要通过Iterator(即一种泛化的指针)迭代器这个中间的桥梁去找到这些存放容器里的数据而进行相应的操作处理。
5.Functors用来处理一些类与类之间的操作,实现一些类似函数的操作功能,另外Adapters用来帮助Containers\Iterators\Functors作一些转换。

在这里插入图片描述
首先使用vector容器时 < >表示模板,需要指定对应模板参数 ,第一参数表示需要指定的元素参数类型(不能省略),第二个参数则是允许指定分配器来给容器放置其中的元素分配相应的内存(可省略,不指定则调用默认的)。而vector<int, allocator<int>> vi(ia, ia+6)中的赋值形式,是因为容器类的定义中,对赋值的定义不同方式如vi(ia, ia+6),即将数组的头跟尾当成初值赋值给vector。

接下来便是要对数据进行操作,count_if()便是个算法,对vector里的数据进行操作,在满足给出的条件下计数,而算法又需通过Irerators去找到存放于vector的对应数据元素,所以传入参数迭代器【泛化的指针】vi.begin()和vi.end(),即要操作的数据元素范围在哪到哪;而第三个参数则是对应处理的条件,less<int>(),是一个仿函数表示小于某某对象;而条件是要求找出在这些元素中小于40的,所以需要绑定ess<int>()操作的对象,即用上了Adapter适配器bind2nd()绑定其第二传入参数为40;同理notl()也是一个函数适配器,作用是将内部结果取反,即求得大于等于40的结果。所以notl(bind2nd(less<int>(), 40))对应于if条件。以上便是混合使用六大部件的例子。

二、容器的“前闭后开”区间

所有容器都提供begin()和end()两个函数:
这里注意end()函数指向的是容器里最后一个元素的再下一位,即调用end()函数代表的迭代器(泛型指针)其内存地址是指向容器里最后一位元素的下一个位置,因此是在容器范围外的
所以*(c.begin())是可以的,(c.end())是不正确的*,因为其得到的泛型指针的解引用是得到灰色的那块内容,其是在容器外的而不是容器里的一部分,所以*(c.end())这样取不知道会取到容器外的什么东西。
在这里插入图片描述由上便可知,对于一个容器的清空,可以令c.begin() = c.end(),这样就意味着,容器的第一个起始地址指向容器外的地址,相当于容器长度为0,即容器清空了。

list<string> c;
...
list<string>::iterator ite;  // 一般的迭代器即泛型指针的声明(注明容器的类型即相应的容器模板参数)
ite = ::find(c.begin(), c.end, target);

auto ite1 = ::find(c.begin(), c.end, target); // 新特性的迭代器即泛型指针的声明,用auto关键字让编译器自己推导。

三、容器结构与分类

在这里插入图片描述

- Sequence Containers(序列式容器):

  • Array(新增的):固定大小空间的
  • Vector:大小是动态变化的,由allocator自动以前一次内存大小两倍的形式扩充
  • Deque:双向的
  • List:双向的,且在内存里不是连续的
  • Forward-List:单向的List

- Associative Containers(关联式容器):

特征:由key和value组成的,查找快速!

  • Set:key和value是同一个,且不可出现相同的元素
  • Multiset:里面的元素可以重复出现
  • Map:key和value是分开的,且不可出现相同的元素
  • Multimap:里面的元素可以重复出现

- Unordered Containers(未定序容器):其内部位置会动态发生变化的

  • Unordered Set:key和value是同一个,且不可出现相同的元素
  • Unordered Multiset:里面的元素可以重复出现
  • Unordered Map:key和value是分开的,且不可出现相同的元素
  • Unordered Multimap:里面的元素可以重复出现

其是通过Hash Table 来实现的,每个bucket里面是一条separate chaining,元素的存放是通过一些计算,看挂到哪条链表上的:如容器有20个空间,元素A经过计算放到第3个位置,B经过计算也放到第3个位置,则此时在第3个位置便会形成一条链表,A->B…而当某条链当链上的元素过多即某位置链表过长时,此时则会进行重新分配(因为链表查找元素时,如果链表长度过长会影响链表的查找速度),所以称之为unordered。

四、容器分类与各种测试

测试程序之辅助函数

using std::cin;
using std::cout;
using std::string;

long get_a_target_long(){
	long target = 0;
	cout << "target (0~" << RAND_MAX << "): ";
	cin >> target;
	return target;
}

string get_a_target_string(){
	long target = 0;
	char buf[10];
	cout << "target (0~" << RAND_MAX <<"):";
	cin >> target;
	snprintf(buf, 10, "%d", target); // 将输入的长整型数据元素转化为字符串型数据元素
	return string(buf); // 创建个临时string类型变量
}

int compareLongs(const void* a, const void* b){ // 传入数据类型为任何类型指针数据均可
	return (*(long*)a - *(long*)b);	// 将传入的任何指针类型强转型为长整型的指针类型,然后解引用,取出其中内存地址数据元素
}

int compareStrings(const void* a, const void*b){
	if(*(string*)a > *(string*)b)
		return 1;
	else if(*(string*)a < *(string*)b)
		return -1;
	else
		return 0;
}

- 容器 array

#include<array>
#include<iostream>
#include<ctime>
#include<cstdlib> // qsort, bsearch, NULL
#define ASIZE 5000000
namespace jj01{
	void test_array(){
		cout << "\n test_array()...... \n";
		array<long, ASIZE> c; // 这个容器的两个模板参数必须指定
		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; // 取出这个容器在内存起点的地址
		
		long target = get_a_target_long(); // 搜索目标为20000
		timeStart = clock();
		qsort(c.data(), ASIZE, sizeof(long), compareLongs); // 传入起始地址、容器放置的元素数据有多少个、每个元素数据的大小(因为元素为long型,所以每个元素的大小均为sizeof(long))、比较大小排序的方式
		long* pItem = (long*)bsearch(&target, (c.data()), ASIZE, sizeof(long), compareLongs); // 先qsort()排序,再bsearch()二分查找
		cout << "qsort() + bsearch(), milli-second :" << (clock() - timeStart) << endl; 
		if(pItem != NULL)
			cout << "found," << *pItem << endl;
		else
			cout << "not found!" << endl;
	}
}

在这里插入图片描述

由上可知,在array容器中,要查找某个目标元素,一般先对元素进行排序qsort(),然后再二分法查找bsearch(),所以有相当一部分时间是耗费在前期的排序上的。

- 容器 vector

#include<vector>
#include<stdexcept>
#include<string>
#include<cstdlib> // absort()
#include<cstdio> // snprintf()
#include<iostream>
#include<ctime>
#include<algorithm> // sort()
namespace jj02{
	void test_vector(long& value){
		cout << "\n test_vector()...... \n";
		vector<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)); // 因为vector是从尾巴放元素进去的,所以用push_back(),而不能push_front()
			}
			catch(exception& p){
				cout << "i = " << i << " " << p.what() << endl; 
				// 曾经最高i = 58389486 then std::bad_alloc
				// 每放入一个新元素,需要拓展空间时都是allocator去分配的,当要不到空间时就会bad_alloc
				abort(); // 当发生bad_alloc时,需要用abort()命令去终止它
			}
		}
		cout << "milli-seconds :" << (clock() - timeStart) << endl; // 生成数据放入容器消耗的时间
		cout << "vector.size() = " << c.size() << endl;
		cout << "vector.front() = " << c.front() << endl; // 取出容器中的第一个元素
		cout << "vector.back() = " << c.back() << endl;
		cout << "vector.data() = " << c.data() << endl; // 取出这个容器在内存起点的地址 
		cout << "vector.capacity() = " << c.capacity() << endl;
		string target = get_a_target_string();
		{
			timeStart = clock();
			auto pItem = ::find(c.begin(), c.end(), target); 
			cout << "::find(), milli-seconds : " << (clock() - timeStart) << endl;
			// find()返回的是个iterator即迭代器(泛型指针),所以判断时是pItem != c.end(),而不是pItem != NULL
			if(pItem != c.end())
				cout << "found, " << *pItem << endl;
			else
				cout << "not found " << endl;
		}

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

namespace xx{}其作用是开辟一个完全新的内存空间去执行操作,这样可保证在不同namespace里的同名变量是毫不相干的,如namespace 01{int a = 0;}和namespace 02{int a = 1;}中的变量a是地址不同的。

vector容器的增长是以当前空间的两倍增长的!!但注意不是在原来的空间中成长两倍,而是在另外一个地方找到这个两倍大小的空间,然后在原来的空间上一个个搬进去新的内存空间那!!因为如果在原来的位置上进行扩充的话,可能跟其他已经使用的内存造成冲突,所以其是找到一个新的更大的内存空间再把原来的数据搬过去。

vector::size()是当前存放元素的总数,vector::capacity()则是当前容器的总大小。

在这里插入图片描述

- 容器 list

在这里插入图片描述

list容器内部有自己的sort()函数,相比于用全局的::sort(),用自己的list::sort()会更好。而这里全局的::find()明显快于list::sort()。

- 容器 forward_list

在这里插入图片描述
其也有自己的sort()函数。

- 非标准库容器 slist(只是gun c下的非标准库的,与forward_list功能相似)

在这里插入图片描述

- 容器 deque

在这里插入图片描述
其结构是底层存放的是指针,然后每个指针指向一段buffer,每个buffer放有固定的元素,即其是分段连续的。但往右push_back()扩展到一个buffer的末端时,则会跳到下一个指针指向的buffer的开头;往左push_front()扩展到头时,则会扩充新的一段buffer,从而往里面添加新元素。

在这里插入图片描述没有自己的sort(),所以只能用全局的::sort()。

- stack(deque adapter):底层实际是由deque来实现的

在这里插入图片描述放置元素为:push()和pop()。

- queue(deque adapter):底层实际是由deque来实现的

在这里插入图片描述放置元素为:push()和pop()。

stack和queue不提供iterator的操作,否则则可以用指针随意往stack和queue里面任意位置添加元素,这样就不能保证其先进先出或后进后出的特点了。

- 容器 multiset

底部是通过红黑树来实现的,里面的数据放进去时就已经做好排序了!且放进去的元素可以重复出现。
在这里插入图片描述插入元素时因为key和value是一样的,所以insert()如insert( int a)只需插入一个类型数据即可。
但注意与map的insert()的区别!!

- 容器 multimap

底部是通过红黑树来实现的,里面的数据放进去时就已经做好排序了!且放进去的value的元素可以重复出现,但key是一定不一样的!
在这里插入图片描述插入元素时key和value是不一样的,所以insert()需要以一对的形式插入元素即insert(pair<long,string>(i, buf))。另外其查找时是通过key来查找的,而其不能用 [ ] 来做insertion。

- 容器 unordered_multiset(底部由hash table实现)

在这里插入图片描述hash table的bucket比data还多,因为有些bucket上面挂链表元素,有些bucket则不挂,所以出现bucket比data还多的情况是合理的。
经验法则:如果元素的个数等于bucket的个数,则bucket会扩充两倍,且元素倍打散重新挂于bucket上。

- 容器 unordered_multimap (底部由hash table实现)

在这里插入图片描述

- 容器 set

在这里插入图片描述因为set放进去的元素是不可重复的,所以虽然放进去1000000个数,但是在0~32767中选择的,肯定存在大量重复,所以实际上只是放进去了32768个数。

- 容器 map

在这里插入图片描述注意:这里可以通过c[i] = string(buf) 的形式将value放进对应的key里,因为这里key是规定不可以重复的,所以相比于multimap,其可以用[ ]来做insertion。另外由于这里的key是不同的,所以可以放进去1000000个key,即使其后面对应的value会出现重复。

五、分配器与之测试

在这里插入图片描述

注意:自己使用额外的分配器时,需要#include<ext…>,且是在__gnu_cxx::下调用的各个allocator。

在这里插入图片描述另外注意:
若要绕开容器,自己使用allocator也是可以的,告诉分配空间时,只需要告诉allocator元素个数就行如p = alloc2.allocate(100),但是再释放空间时,则需要传入对应指针和元素个数才可,如alloc2.deallocate(p, 100),即要记住当初拿的那根指针p以及背后有100个数据。
但用new和delete时却不用指明要多少个多少个具体的元素数量。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值