C++ STL——C++容器的共性和相关概念


注:原创不易,转载请务必注明原作者和出处,感谢支持!

注:内容来自某培训课程,不一定完全正确!

一 STL容器共性机制

STL容器所提供的值都是值(value)寓意,而非引用(reference)寓意,也就是说当我们给容器中插入元素的时候,容器内部实施了拷贝动作,将我们要插入的元素再另行拷贝一份放入到容器中,而不是将原数据元素的引用放入容器中,也就是说我们提供的元素必须能够被拷贝

(1)除了queue和stack之外,每个容器都提供可返回迭代器的函数,运用返回的迭代器就可以访问元素。
(2)通常STL不会抛出异常,需要使用者传入正确参数
(3)每个容器都提供了一个默认构造函数和默认的拷贝构造函数。
(4)大小相关的方法:size()返回容器中元素的个数,empty()判断容器是否为空

二 STL容器的使用场合

vector deque list set multiset map multimap
典型内存结构 单端数组 双端数组 双向链表 二叉树 二叉树 二叉树 二叉树
可随机存取 对key而言:是
元素搜索速度 非常慢 对key而言:快 对key而言:快
元素插入和删除 尾端 头尾两端 任何位置 - - - -

(1)vector容器的使用场景:比如软件历史操作记录的存储。
(2)deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。

vector和deque的比较:

  • vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置却是不固定的。
  • 如果有大量的释放操作的话,vector花的时间更少,这和二者的内部实现有关。
  • deque支持头部的快速插入和删除,这是deque的优点。

(3)list的使用场景:比如公交乘客的存储,随时可能有乘客下车,支持频繁的不确定位置元素的移除。
(4)set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
(5)map的使用场景:比如按ID号存储十万个用户,想要快速通过ID查找对应的用户,二叉树的查找效率就体现出来了。

三 函数对象

重载函数调用操作符的类,其对象常被称为函数对象(function object),即它们是行为类似函数的对象,也叫仿函数(functor),其实就是重载“()”操作符,使得类对象可以像函数那样调用。

注意:
(1)函数对象(仿函数)是一个类,不是一个函数
(2)函数对象重载了“()”操作符使得它可以像函数一样调用

假定某个类有一个重载的operator(),而且重载的operator()要求获取一个参数,我们就将这个类称为“一元仿函数(unary functor)”;相反,如果重载的operator()要求获取两个参数,我们就将这个类称为“二元仿函数(binary functor)”。

下面是函数对象的应用实例。

// 仿函数
class MyPrint
{
public:
	MyPrint() { cnt = 0; }
	void operator()(int val)
	{
		cout << val << endl;
		++cnt;
	}
	unsigned getCnt() { return cnt; }

private:
	unsigned cnt;
};

void Test1()
{
	// 函数对象可以像普通函数那样调用
	// 函数对象可以像普通函数那样接受参数
	// 函数对象超出了函数的概念,函数对象可以保存函数的调用状态
	MyPrint PRINT;
	PRINT(10);

	// 打印调用次数,使用函数对象可以避免使用全局变量
	cout << "调用次数:" << PRINT.getCnt() << endl;
}

void Test2()
{
	vector<int> v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);

	MyPrint PRINT = for_each(v.begin(), v.end(), MyPrint());
	cout << "调用次数:" << PRINT.getCnt() << endl;
}

你虽然避免了使用全局变量,但是却要求共用PRINT对象?

四 谓词

谓词是指普通函数或者重载的operator()返回值是bool类型的函数对象(仿函数)。如果operator()接受一个参数,那么叫做一元谓词,如果接受两个参数,那么叫做二元谓词,谓词可以作为一个判断式。

举例:
一元函数对象:for_each
一元谓词:find_if
二元函数对象:transform
二元谓词:sort

五 内建函数对象

STL内建了一些函数对象。分为:
(1)算术类函数对象
(2)关系运算符类函数对象
(3)逻辑运算类函数对象

这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数的功能。使用内建函数对象,需要引入头文件#include <functional>

6个算术类函数对象,除了negate是一元运算,其他都是二元运算。

template<class T> T plus<T>			// 加
template<class T> T minute<T>		// 减
template<class T> T multiplies<T>	// 乘
template<class T> T divides<T>		// 除
template<class T> T modulus<T>		// 取模
template<class T> T negate<T>		// 取反

6个关系运算类函数对象,每一种都是二元运算。

template<class T> bool equal_to<T>			// 等于
template<class T> bool not_equal_to<T>		// 不等于
template<class T> bool greater<T>			// 大于
template<class T> bool greater_equal<T>		// 大于等于
template<class T> bool less<T>				// 小于
template<class T> bool less_equal<T>		// 小于等于

逻辑运算类函数对象,not为一元运算,其余为二元运算

template<class T> bool logical_and<T>		// 逻辑与
template<class T> bool logical_or<T>		// 逻辑或
template<class T> bool logical_not<T>		// 逻辑非

六 函数对象适配器

函数对象适配器是完成一些配接工作,这些配接工作包括绑定(bind),否定(negate)以及对一般函数或成员函数的修饰,使其成为函数对象。

bind2st:将参数绑定为函数对象的第一个参数
bind2nd:将参数绑定为函数对象的第二个参数
not1:对一元函数对象取反
not2:对二元函数对象取反

ptr_fun:将普通函数修饰成函数对象
mem_fun:修饰成员函数
mem_fun_ref:修饰成员函数

函数适配器的应用案例。

struct MyPrint : public binary_function<int, int, void>
{
	void operator()(int val, int add) const
	{
		cout << "val = " << val << " add = " << add << " val + add = " << val + add << endl;
	}
};

// 仿函数适配器 bind1st bind2nd 绑定适配器
void Test1()
{
	vector<int> v;
	for (int i = 0; i < 10; ++i)
	{
		v.push_back(i);
	}

	// for_each()在位置3只能填入一个参数,如果需要传入多个参数
	// 那么你需要用到绑定适配器,绑定适配器的作用是将二元函数对象
	// 转变成一元函数对象。
	int add = 200;
	for_each(v.begin(), v.end(), bind2nd(MyPrint(), add));

	// bind1st,将add的值200绑定为第一个参数val
	// bind2nd,将add的值200绑定为第二个参数add

}

// 仿函数适配器 not1 not2 取反适配器
struct MyCompare : public binary_function<int, int, bool>
{
	// 从大到小排序
	bool operator()(int v1, int v2) const
	{
		return v1 > v2;
	}
};

struct MyPrint2
{
	void operator()(int v) const
	{
		cout << v << " ";
	}
};

// 大于5
struct MyGreater5 : public unary_function<int, bool>
{
	bool operator()(int v) const
	{
		return v > 5;
	}
};

void Test2()
{
	vector<int> v;
	for (int i = 0; i < 10; ++i)
	{
		v.push_back(rand() % 100 + 10);
	}

	for_each(v.begin(), v.end(), MyPrint2());
	cout << endl;
	sort(v.begin(), v.end(), not2(MyCompare()));
	for_each(v.begin(), v.end(), MyPrint2());
	cout << endl;

	// 如果对二元谓词取反,用not2
	// 如果对一元谓词取反,用not1

	// 使用not1()将条件由大于5改为小于等于5
	vector<int>::iterator ret = find_if(v.begin(), v.end(), not1(MyGreater5()));
	if (ret != v.end())
	{
		cout << *ret << endl;
	}
	else
	{
		cout << "没有找到!" << endl;
	}
}

// 仿函数适配器 ptr_fun
void MyPrint3(int val, int add)
{
	cout << "val = " << val << " add = " << add << endl;
}

void Test3()
{
	vector<int> v;
	for (int i = 0; i < 10; ++i)
	{
		v.push_back(i);
	}

	// 无法直接对函数MyPrint3()进行参数绑定
	// for_each(v.begin(), v.end(), MyPrint3);

	// 把普通函数适配成函数对象,再进行参数绑定
	for_each(v.begin(), v.end(), bind2nd(ptr_fun(MyPrint3), 10));
}

// 成员函数适配器 mem_fun mem_fun_ref
class Person
{
public:
	Person(int age, int id) : age(age), id(id) {}
	void show()
	{
		cout << "age = " << age << " id = " << id << endl;
	}

public:
	int age;
	int id;
};

void Test4()
{
	// 如果容器中存放的对象或者对象指针,我们for_each算法
	// 打印的时候调用类自己提供的打印函数
	
	vector<Person> v;
	Person p1(10, 20), p2(30, 40), p3(50, 60), p4(70, 80);
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	// 没有提供额外的打印函数,调用Person自己的成员函数show()
	// 格式: &类名::函数名
	for_each(v.begin(), v.end(), mem_fun_ref(&Person::show));
	cout << endl << endl;

	// 存储的是对象指针,用mem_fun
	vector<Person *> v1;
	v1.push_back(&p1);
	v1.push_back(&p2);
	v1.push_back(&p3);
	v1.push_back(&p4);
	for_each(v1.begin(), v1.end(), mem_fun(&Person::show));

	// 如果存放的是对象,使用mem_fun_ref
	// 如果存放的是对象指针,使用mem_fun
}
posted @ 2019-11-02 19:17  wallace-rice  阅读(358)  评论(0编辑  收藏  举报