• 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
    }
    
  • 相关阅读:
    Java并发编程:线程池
    并发编程之 CAS 的原理
    单例模式
    UWP开发入门(十七)——判断设备类型及响应VirtualKey
    UWP开发入门(十六)——常见的内存泄漏的原因
    UWP开发入门(十五)——在FlipView中通过手势操作图片
    UWP开发入门(十四)—— UserControl中Adaptive UI的小技巧
    UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏
    UWP开发入门(十二)——神器Live Visual Tree
    UWP开发入门(十一)——Attached Property的简单应用
  • 原文地址:https://www.cnblogs.com/laizhenghong2012/p/11783669.html
Copyright © 2020-2023  润新知