• 数据结构利器之私房STL(下)


    索引


    这篇文章 http://www.cnblogs.com/daoluanxiaozi/archive/2012/12/02/confidential-stl.html 由于严重违反了『博客园首页』的相关规则,因此笔者改将《私房STL》系列的每一篇都整合到博客园中,取消外链的做法。另,考虑篇幅的问题,此系列文章将分为上、中、下。此篇为《数据结构利器之私房STL(下)》,最后一篇。喜欢就顶下:-)

    此系列的文章适合初学有意剖析STL和欲复习STL的同学们。

    1. 私房STL之左值和右值
    2. 私房STL之函数对象
    3. 私房STL之函数配接器
    4. 私房STL之迭代器

    私房STL之左值和右值

    左值和右值并不专属于STL里的内容,是在接触STL的过程发现了笔者C/C++的知识规则漏洞。

    左值右值

    左值(LValue)即等号左边的值,右值(RValue)即等号右边的值,右值必须放在等号右边,但左值既可以在左边也可以放在右边。那么数值(等式),字符串,常量,只能作为右值,右值决不能放置等号左边。

    100 = 60;		/*数值是右值,不合法*/
    'A' = 'a';		/*字符是右值,不合法*/
    const int a = 1;
    a = 100;		/*a为常量,属右值,不合法*/

    变量,引用(reference)作为左值,既可以在等式左边,又可以在等式右边。

    int a,b;
    a = 2;		/*a左值,2右值。*/
    b = a;		/*b左值,a左值。*/
    int &c = a;	/*a左值,c左值。*/

    特别的,自增有两种形式:i++和++i,但两者是有区别的,允许我用c将其操作展开:

    i++的操作:

    {
    	int temp = i;
    	++i;
    	return temp;
    }

    ++i的操作:

    {
    	i = i + 1;
    	return i;
    }

    因此i++ = 1是不合法的,因为i++返回的是临时值,不是i自己,为了消除歧义,i++坚决返回右值,也就是说它只能放置在等式右边。而++i = 1是合法的,从上面操作的展开来看,++i确实返回了它自己,因此它是一个左值,既能是在等式的左边,也能是右边。

    C++左值右值延伸

    延伸至类的运算符重载的问题上。我们先假定一个类Node,

    class Node  
    {  
    public:  
    	Node(int nAge = 0)  
    	{  
    		m_nAge = nAge;  
    	}  
    
    	int GetAge()const
    	{  
    		return m_nAge;  
    	}  
    
    	Node operator ++ (int n)	/*i++*/
    	{
    		Node temp = *this;
    		m_nAge ++;
    		return temp;
    	}
    
    	Node& operator ++ ()		/*++i*//*你知道为什么要返回reference吗?*/
    	{
    		m_nAge ++;
    		return *this;
    	}
    
    	Node& operator = (int n)
    	{
    		m_nAge = n;
    		return *this;
    	}
    private:  
    	int m_nAge;  
    };

    C++规定,Node& operator ++ ()是重载前缀自增运算符(++i),而Node operator ++ (int n)是重载后缀自增运算符(i++)。细心发现,重载前缀自增运算符返回的是reference引用,而重载后缀自增运算符返回的是临时变量。换句话说,如果有Node对象node,我希望++node(前缀)返回的是左值,node++(后缀)返回的右值。意即希望,注意是希望:

    ++node = 1;	/*合法,++node返回值作为左值*/
    node++ = 1;	/*不合法,node++返回值作为左值*/

    但是,重载运算操作符本来就是为改变运算符的行为而来的,所以上述行为是编译器所允许的。但语法上没有问题,但逻辑上却有严重的漏洞。++node = 1;确实改变了node的内容,但node++ = 1;未能得逞,因为“=1”的操作被执行在temp上,故node++ = 1;执行过后,node内容改变为以前的值+1,而不是等于1。

    ......
    Node node(23);	/*node.m_nAge初值为23。*/
    
    node ++ = 1;
    cout << "node ++ = 1;执行过后,node.m_nAge = " << node.GetAge() << endl;
    ++ node = 1;
    cout << "++ node = 1;执行过后,node.m_nAge = " << node.GetAge() << endl;
    ......

    node ++ = 1;执行过后,node.m_nAge = 24
    ++ node = 1;执行过后,node.m_nAge = 1
    请按任意键继续. . .

    node++ = 1;执行过后,node没有被“=1”影响。那可不可以反其道而行呢?“我偏要让它返回左值”!当然行,没有问题,只要修改它的行为就好了。

    ......
    Node& operator ++ (int n)	/*i++*//*修改i++的行为,让它也返回左值*/
    {
    	//Node temp = *this;
    	//m_nAge ++;
    	//return temp;
    	m_nAge ++;
    	return *this;
    }
    ......

    node ++ = 1;执行过后,node.m_nAge = 1
    ++ node = 1;执行过后,node.m_nAge = 1
    请按任意键继续. . .

    但还是建议不要这样做,因为会引起混淆。

    总结下,左值可以出现在等式左边,又可以是右边;右值只能出现在右边。++i返回左值,i++返回右值,这样也就是为什么在重载前缀自增运算符时候,要返回reference(左值)了。

    本文完 2012-10-23

    捣乱小子 http://www.daoluan.net/


    私房STL之函数对象

    一句话之函数对象:函数对象(又称仿函数)的秘密不足以让你吃惊,它是重载“()”操作符的类(结构体)的对象;实现简单:声明一个类(结构体),重载“()”操作符,即可。

    PS:你也知道了函数对象的定义,下面的内容函数对象有时候指的就是函数对象,有时候指的是函数对象对应的类(结构体)。

    STL存在内建的函数对象,比如算术类函数对象(+,-等),关系运算类函数对象(==,!、等),逻辑运算类函数对象(&&,||等),详见;http://cplusplus.com/reference/std/functional/,但编程无止境,可能这些还不够用,我们可以轻而易举的自定义函数对象。

    函数对象的使用

    函数对象直接应用的地方较少,但了为了配合接下来配接器(下一篇博文)的内容,简单测试下:

    cout << "10 + 10 = " << plus<int>()(10,10) << endl;
    cout << "10 - 10 = " << minus<int>()(10,10) << endl;
    cout << "10 == 10 ? " << equal_to<int>()(10,10) << endl;

    10 + 10 = 20
    10 - 10 = 0
    10 == 10 ? 1
    请按任意键继续. . .

    plus<int>()(10,10);中,第一个括号是为了产生struct plus的临时对象,第二个括号调用struct plus内部重载的“()”函数。上面是优雅的写法,也可以通俗点:

    plus<float> opplus;
    cout << "10.0 + 10.1 = " << opplus(10.0,10.1) << endl;

    一下是struct plus的全貌,它很单纯:

    template<class _Ty>
    	struct plus
    		: public binary_function<_Ty, _Ty, _Ty>
    	{	// functor for operator+
    	_Ty operator()(const _Ty& _Left, const _Ty& _Right) const
    		{	// apply operator+ to operands
    		return (_Left + _Right);
    		}
    	};

    自定义函数对象

    有些情况内建的函数对象不能满足我们的要求,需要我们自定义一个函数对象来对付当下的问题。STL规定:所有可配接的一元函数都应该继承unary_function(unary:一元),所有可配接的二元函数都应该继承binary_function(binary:二元)。譬如,我们定义一个平方算术类函数对象,很easy,照猫画虎plus的做法,修改其内部执行即可:

    template<class _Ty>
    struct square
    	: public unary_function<_Ty, _Ty>
    {	
    	_Ty operator()(const _Ty& Arg) const
    	{	
    		return (Arg * Arg);
    	}
    };
    
    ......
    cout << "10^2 = " << square<int>()(10) << endl;
    ......

    10^2 = 100
    请按任意键继续. . .

    本文完 2012-10-25

    捣乱小子 http://www.daoluan.net/


    私房STL之函数配接器

    本文只通过简单的例子介绍函数配接器是如何工作的。

    函数对象直接应用的地方较少,它配合实现一些算法(作为算法的参数),于是便有函数配接器。因为通过函数对象,几乎可以实现我们所要的表达式,那么某些算法也确实可以通过函数对象,来得到我们所预期(把预期放在表达式中)的结果。

    展示一段代码:

    /*摘自C++ standard STL。*/
    
    ......
    int ia[] = {11,12,13,1,2,3,4,5,6,7};
    vector<int> iv(ia,ia+10);
    cout << count_if(iv.begin(),iv.end(),bind2nd(less<int>(),10)) << endl;
    ......

    7
    请按任意键继续. . .

    我们可以试着一步一步展开count_if()函数,

    /*摘自C++ standard STL。*/
    template<class _InIt,
    	class _Pr> inline
    	typename iterator_traits<_InIt>::difference_type
    		count_if(_InIt _First, _InIt _Last, _Pr _Pred)/*这里。*/
    	{	// count elements satisfying _Pred
    	return _Count_if(_CHECKED_BASE(_First), _CHECKED_BASE(_Last), _Pred);
    	}
    /*count_if()的底层函数。*/
    template<class _InIt,
    	class _Pr> inline
    	typename iterator_traits<_InIt>::difference_type
    		_Count_if(_InIt _First, _InIt _Last, _Pr _Pred)
    	{	// count elements satisfying _Pred
    	_DEBUG_RANGE(_First, _Last);
    	_DEBUG_POINTER(_Pred);
    	typename iterator_traits<_InIt>::difference_type _Count = 0;
    
    	for (; _First != _Last; ++_First)
    		if (_Pred(*_First))
    			++_Count;
    	return (_Count);
    	}

    其中,_Pred就是函数对象binder2nd,它在return (std::binder2nd<_Fn2>(_Func, _Val));语句中,被构造出来,同时它重载了“()”操作符,再来看看bind2nd和binder2nd:

    /*摘自C++ standard STL。*/
    		// TEMPLATE CLASS binder2nd
    template<class _Fn2>
    	class binder2nd
    		: public unary_function<typename _Fn2::first_argument_type,
    			typename _Fn2::result_type>
    	{	// functor adapter _Func(left, stored)
    public:
    	typedef unary_function<typename _Fn2::first_argument_type,
    		typename _Fn2::result_type> _Base;
    	typedef typename _Base::argument_type argument_type;
    	typedef typename _Base::result_type result_type;
    
    	binder2nd(const _Fn2& _Func,
    		const typename _Fn2::second_argument_type& _Right)
    		: op(_Func), value(_Right)
    		{	// construct from functor and right operand
    		}
    
    	result_type operator()(const argument_type& _Left) const
    		{	// apply functor to operands
    		return (op(_Left, value));
    		}
    
    	result_type operator()(argument_type& _Left) const
    		{	// apply functor to operands
    		return (op(_Left, value));
    		}
    
    protected:
    	_Fn2 op;	// the functor to apply
    	typename _Fn2::second_argument_type value;	// the right operand
    	};
    
    		// TEMPLATE FUNCTION bind2nd
    template<class _Fn2,
    	class _Ty> inline
    	binder2nd<_Fn2> bind2nd(const _Fn2& _Func, const _Ty& _Right)
    	{	// return a binder2nd functor adapter
    	typename _Fn2::second_argument_type _Val(_Right);
    	return (std::binder2nd<_Fn2>(_Func, _Val));
    	}

    bind2nd()是辅助函数,为的是使用binder2nd(真正的主角)更为方便。

    count_if(iv.begin(),iv.end(),bind2nd(less<int>(),10));中less<int>()在binder12函数对象的构造函数中被作为其内部操作成员(它是一个函数对象)_Fn2 op;

    count_if()函数在处理每一个元素的时候,实际调用binder2nd的“()”运算符重载函数,而这个函数当中有调用了其内部操作成员op(其又是一个函数对象less<int>)的“()”运算符重载函数。如此一来,配接成功了。其它的函数配接器做法类似。

    总结就是count_if()调用bind2nd()函数,bind2nd()函数实际产生binder2nd函数对象,返回给count_if()做为参数,count_if()再调用更为底层的函数_Count_if()函数。

    我们从count_if()的源代码可以得知,count_if(iv.begin(),iv.end(),bind2nd(less<int>(),10));还可以被改写成,

    bool less10(int i)
    {
    	if(i<10)
    		return true;
    	return false;
    }
    ......
    int ia[] = {11,12,13,1,2,3,4,5,6,7};
    vector<int> iv(ia,ia+10);
    cout << count_if(iv.begin(),iv.end(),less10) << endl;
    ......

    此时,count_if()中的_Pred就是一函数指针了。所以,函数对象还是可以用一般函数指针替换的。

    STL实现的函数配接器:http://www.cplusplus.com/reference/std/functional/

    本文完 2012-10-26

    捣乱小子 http://www.daoluan.net/


    私房STL之迭代器

    STL表现的如此出色的,迭代器(iterator)做出了很大的贡献,功不可没。

    一个数据元有多重表示方式,包括:实体(它自己),指针,引用;另外,还有两个与数据元属性相关的类型:间距(即两个数据元之间的关系)和其移动特性与施行方式的类型。迭代器中包含了这上述五种与数据元先关的描述,所以迭代器能够很充分的表示和操控一个数据元。迭代器本身只能表示操作数据元,但是它没有含括任何的数据元数据,就似电视机和遥控器之间的关系,遥控器(迭代器)只是能够切换不同的频道,但是它不含括电视机(数据元)。

    这里不单独描述迭代器,它个单不是一独的个体,放在其所属的大框架中,更能体现它的作用和执行机制。

    STL中会定义迭代器:

    template <class _Ty>		/*_Ty:节点元素类型。*/
    struct container_iterator
    {
    	/*五种与数据元先关的描述。*/
    	......
    	typedef container_iterator<_Ty> iterator;
    
    	typedef bidirectional_iterator_tag itetator_category;
    	typedef _Ty value_type;
    	typedef Node<_Ty>* link_type;
    	typedef size_t size_type;
    	typedef ptrdiff_t difference_type;
    
    	link_type node;
    
    	/*迭代器构造函数。*/
    	......
    	container_iterator(link_type x):node(x){}
    	...
    	......
    
    	/*迭代器行为*/
    	/*+,-,++,--,+=,-=,->,&,[]等运算符重载各取所需 */
    };
    iterator_op

    从中看出,迭代器不包含数据实体,它只是能表现和操作数据实体。因为上述container_iterator操控的的实现,因此当手头有一container_iterator的时候,你可以“*ite”来获得数据元的引用,可以“ite->”获得数据元的指针,“++”可以另ite自动前进一步,“--”可以另ite后退一步。。。

    通过模板,迭代器可以为任何数据元服务。一个有趣的地方便是迭代器的构造函数:

    container_iterator(link_type x):node(x){}

    在container(以下展示)的元素操作当中,很多时候会直截返回指向数据元的指针,这时可能此操作的函数可能需要返回的是container_iterator类型,而不是返回一个指向数据元的指针(这种做法不上道,太龌龊),于是会临时构造(调用迭代器的构造函数)一个迭代器作为返回值。

    意即:

    class Node  
    {  
    public:  
    	Node(int nAge = 0)  
    	{  
    		m_nAge = nAge;  
    	}  
    	......
    private:  
    	int m_nAge;  
    };  
    
    Node foo(int i)
    {
    	return i;	/*直截返回一个int,但Node有Node(int)构造函数,因此会临时构造一个Node对象返回。*/
    }
    
    int main()
    {	
    	Node i = foo(2);
    	return 0;
    }

    下面是container:

    template <class _Ty,class alloc>		/*T:节点元素类型。*/
    class container
    {
    	/*container数据结构*/
    	typedef container_iterator<_Ty> iterator;
    	typedef const container_iterator<_Ty> const_iterator;
    
    	typedef Node<_Ty>* link_type;
    
    	typedef _Ty value_type;
    	typedef Node<_Ty>* link_type;
    	typedef size_t size_type;
    	typedef ptrdiff_t difference_type;
    
    private:
    	link_type head;		/*头指针。*/
    	link_type tail;		/*为指针。*/
    
    	iterator begin();
    	iterator end();
    
    	bool empty();
    	size_type size()const ;
    	......
    
    	/*元素的操作。push_front,push_back,pop_front,pop_back,insert,earse等根据容器的不同各取所需。*/
    	iterator insert(const _Ty& x);
    	iterator earse(iterator position);
    	void push_back(const _Ty& x);
    	void push_front(const _Ty& x);
    	void pop_back();
    	void pop_front();
    	......
    };

    container内部实现的大多数是元素的操作函数,它们有充分利用container_iterator,包括container_iterator内部实现的各种元素的操控(++,--,*,->等等)。

    container和container_iterator就是这样结合起来的。还剩下一STL中的镇库之宝:算法。通用的的算法中,少不了迭代器。如何能做到通用?不同的容器对应不同的迭代器,那是否对于一个算法,要实现多个迭代器的版本?不,不需要,这就是泛化编程的好处,根据传入的迭代器(一般的STL算法会以迭代器作为参数)来推导出相应的迭代器类型。以最为简单的find()算法为例:会通过_InIt _First来推导出迭代器的类型,推导出迭代器的类型,也就推导出了相应的容器。

    /*摘自c++ standard library。*/
    template<class _InIt, class _Ty>
    inline
    _InIt find(_InIt _First, _InIt _Last, const _Ty& _Val)
    {	// find first matching _Val
    	_ASSIGN_FROM_BASE(_First,
    		_Find(_CHECKED_BASE(_First), _CHECKED_BASE(_Last), _Val));
    	return (_First);
    }
    
    template<class _InIt, class _Ty>
    inline
    _InIt _Find(_InIt _First, _InIt _Last, const _Ty& _Val)
    {	// find first matching _Val
    	_DEBUG_RANGE(_First, _Last);
    	for (; _First != _Last; ++_First)
    		if (*_First == _Val)
    			break;
    	return (_First);
    }

    我们看到,迭代器在算法中的表现,++,--,==。。。。

    故迭代器和算法模块结合了。STL中迭代器,容器,算法三足鼎立,整体上通力合作,细微之处不乏各司其职。妙哉!妙哉!

    本文完 2012-10-28

    捣乱小子 http://www.daoluan.net/

    更多请访问:http://daoluan.net
  • 相关阅读:
    tensorflow学习3---mnist
    tensorflow学习2-线性拟合和神经网路拟合
    关于泛型数据结构中OrderBy的使用
    敏捷开发之观察者模式
    敏捷开发之设计文档
    C#算法实现获取树的高度
    武林高手?敏捷开发,唯velocity取胜
    C#接口多继承方法重名问题
    .Net平台技术栈?不止于此
    浅谈C#中Tuple和Func的使用
  • 原文地址:https://www.cnblogs.com/daoluanxiaozi/p/2809360.html
Copyright © 2020-2023  润新知