1. 程式人生 > >函式物件和stl演算法應用例項

函式物件和stl演算法應用例項

Stl中不僅封裝了常見的資料結構,也用模板實現了常用的演算法,如查詢、排序等。其中的演算法也非常多,不可能都記全也沒必要記,只要知道如何應用如何查詢msdn幫助(可以下載也可以在VS中,選中關鍵詞如sort按住F1進入網頁版本幫助)即可。

1.STL演算法的結構形式和排序樣例

STL提供的演算法庫很龐大,要記下這麼多的演算法是很困難的,所幸它幾乎所有的演算法都遵循如下的結構形式:

fun (beg, end, other parms);  

fun (beg, end, dest, other parms);  

fun (beg, end, beg2, other parms);  

fun (beg, end, beg2, end2, other parms);

這裡的begend都必須是迭代器,parms常常是自定義或是系統定義的函式物件(相當於函式呼叫)。

下面以整數排序為例,sort(vec.begin(),vec.end()),按照正常從大到小排序

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

bool cmp(int a,int b)
{
	return a > b;
}

int main3( ) 
{
	vector<int> q1;
	for(int i=0; i<10; i++)
	{
		q1.push_back(rand()%100+1);//對q1隨機賦值,1-100
	}
	//將q1中的值全部輸出到控制檯
	copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," "));
	cout << endl;

	//正常從小到大排序
	sort(q1.begin(),q1.end());
	cout << "after sort:" << endl;
	copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," "));
	cout << endl;

	//呼叫我們自己提供的排序演算法,從大到小排序
	sort(q1.begin(),q1.end(),cmp);

	cout << "after sort by our algorithm:" << endl;
	copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," "));
	cout << endl;

	return 0;
}


可以看到在第二次排序過程中,呼叫了我們自定義的cmp方法進行排序。這一點在排序物件為自定義時特別有用,比如我們需要排序的是結構體,但是系統不知道如何根據結構體內部進行排序,這就需要我們自己提供排序方法。也就是fun (beg, end, beg2, other parms)模式中的引數。

還有一種方法自定義排序,就是在需要排序的物件或者結構體當中進行運算子過載”<”,運算子過載相當於一個比較器,排序的時候以此作為比較條件。

  2.函式物件

既然我們在呼叫stl演算法的時候,已經可以呼叫自定義函式來實現功能了,為什麼還需要這個所謂的“函式物件”呢?呼叫已有的函式有什麼不好嗎?什麼是函式物件呢?它能給我們帶來什麼好處呢?

現在考慮實現一個拷貝的功能,也就是把一個vector中的所有資料拷貝到另一個vector中間去,但是我們加一些限制條件,需要拷貝的整型資料必須大於15

但是如果下一次我們又改變條件了,條件改成資料必須大於16,那我們必須重新寫一個函式gt16;那假設下次又要改成17.....那這樣要寫的函式豈不是有無窮多個。問題就在於這個比較的值能不能動態的改變,這時就需要我們的function object。 

所謂函式物件就是能以函式的形式呼叫的物件,比如物件class gt_N,需要能夠以 gt_N() 這種形式呼叫。我們可以通過過載運算子()來實現。那我們就來實現下這個gt_N的函式物件吧。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

bool gt_15(int a)
{
	return a > 15;
}

class gt_N
{
	int n;
public:
	gt_N(int data):n(data){}

	bool operator()(int t)
	{
		return n > t;
	}

};

int main4( ) 
{
	vector<int> q1;
	vector<int> q2(10);	
	for(int i=0; i<10; i++)
	{
		q1.push_back(rand()%100+1);//對q1隨機賦值,1-100
	}
	cout << "q1=" ;
	copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," "));
	cout << endl;

	//將q1中的資料拷貝到q2
	copy_if(q1.begin(),q1.end(),q2.begin(),gt_N(50));
	cout << endl;

	//輸出q2的值
	cout << "copy data > 50 from q1" << endl << "q2 = " << endl;
	copy(q2.begin(),q2.end(),ostream_iterator<int>(cout," "));
	cout << endl;

	return 0;
}

 

2.1函式物件的分類

一般來說,演算法的流程都是將迭代器中的每個元素都執行一遍我們所提供的函式物件或者函式,然後做出相應選擇。標頭檔案<functional>定義了大量的通用函式物件,如plusgreaterless,並且可以通過函式物件介面卡(bind1stbind2ndnot1not2)增強功能。

根據函式物件中的運算子operator()使用引數的個數進行分類:(1)一元判定函式:返回bool型值的一元函式,如判斷邏輯是否為真true/false 2)二元判定函式:返回bool型值的二元函式,如判斷兩個值的大小。

a)下面以greater為例,將q1中大於2的數字拷貝到q2

Copy_if(q1.begin(),q1.end(),q2.begin(), bind2nd(greater<int>(),2) );

    (b)複製序列中不等於20的數字:

Copy_if(q1.begin(),q1.end(),q2.begin(), not1(bind2nd(equal_to<int>(),20) );

下面對上述兩個例子進行解釋,在例(a)中使用了函式物件greatergreater是個二元的,也就是比較兩個數的大小,其中一個數是vector中遍歷的每個數字。那麼另外一個需要比較的數字,我們可以讓它固定下來(可以繫結第一個或者第二個數值)。繫結bind, 第一個1st,第二個2nd。所以bind2nd(greater<int>(),2)就是將vector中的每個資料和2進行比較,只有大於2的才會拷貝到q2中。

not1(bind2nd(equal_to<int>(),20)) :首先bind2nd(equal_to<int>(),20)表示vector遍歷中的每個數字等於2的才進行拷貝到q2,然後not1表示每個數字不等於2的才進行拷貝到q2

Not1是一元的,not2是二元的,比如下面例子中的less就是兩個數字比較。那上面的例子(b)中的equal_to也是兩個數字比較啊,但是由於綁定了第二個元素(bind2nd)為20,所以也就變成一元了,即採用not1.  sort( v1.begin( ), v1.end( ), not2(less<int>( ) ) );

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <map>
using namespace std;

int main5( ) 
{
	vector<int> q1;
	vector<int> q2(10);
	vector<int> q3(10);
	for(int i=0; i<10; i++)
	{
		q1.push_back(rand()%100+1);//對q1隨機賦值,1-100
	}
	cout << "q1=" ;
	copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," "));
	cout << endl;

	//將q1中大於50的資料拷貝到q2,採用了自帶的函式物件greater和函式物件介面卡bind2nd
	copy_if(q1.begin(),q1.end(),q2.begin(),bind2nd(greater<int>(),50));
	cout << endl;



	//輸出q2的值
	cout << "copy data > 50 from q1" << endl << "q2 = " << endl;
	copy(q2.begin(),q2.end(),ostream_iterator<int>(cout," "));
	cout << endl;


	//將q1中不大於50的資料拷貝到q3,採用了自帶的函式物件greater和函式物件介面卡bind2nd以及not1
	copy_if(q1.begin(),q1.end(),q3.begin(),not1(bind2nd(greater<int>(),50)));
	cout << endl;

	//輸出q3的值
	cout << "copy data <= 50 from q1" << endl << "q3 = " << endl;
	copy(q3.begin(),q3.end(),ostream_iterator<int>(cout," "));
	cout << endl;
	return 0;
}


總結:1.約束器bind1stbind2nd可以繫結二元函式物件的某一個值。2.謂詞否定迭代器,由not1not2組成並對原來的條件取反,它們分別有一個或兩個模板引數。

 2.2 函式物件的介面卡

2.2.1普通函式介面卡介面卡ptr_fun()

有時候需要我們自己去寫一些函式物件或者函式來處理一些邏輯,但是我們又無法使用已有的bind1st,bind2nd,not1,not2. 這些函式物件介面卡都無法應用在我們自己寫的函式上,那麼如何才能使用呢?

系統提供了普通函式介面卡介面卡ptr_fun(),它可以將一個我們自己的普通函式變成函式物件並和已有的bind1st,bind2nd,not1,not2.配合使用。下面以判定某個數是n的倍數:

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <map>
using namespace std;

//判定x是否是n的倍數
bool isNtimes(int x,int n)
{
	return (x % n == 0) ? 1 : 0;
}
int main6( ) 
{
	vector<int> q1;
	vector<int> q2(10);
	for(int i=0; i<10; i++)
	{
		q1.push_back(rand()%100+1);//對q1隨機賦值,1-100
	}
	cout << "q1=" ;
	copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," "));
	cout << endl;

	//將q1中為2的倍數的資料拷貝到q2,採用了ptr_fun函式物件介面卡和bind2nd
	copy_if(q1.begin(),q1.end(),q2.begin(),bind2nd(ptr_fun(isNtimes),2));
	cout << endl;

	//輸出q2的值
	cout << "copy data > 50 from q1" << endl << "q2 = " << endl;
	copy(q2.begin(),q2.end(),ostream_iterator<int>(cout," "));
	cout << endl;

	return 0;
}


2.2.2成員函式介面卡

1.mem_fun()接受一個物件指標傳遞過來的成員函式

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <map>
using namespace std;

class Shape{
public:
	virtual void draw()	{ 	}
};

class Circle: public Shape{
public:
	void draw()
	{
		cout << "Circle ...." << endl;
	}
};

class Rect: public Shape{
public:
	void draw()
	{
		cout << "Rect ...." << endl;
	}
};

int main7( ) 
{
	vector<Shape*> q1;
	q1.push_back(new Circle);
	q1.push_back(new Rect);
	//對q1中的每個資料,也就是Shape*,執行其成員函式,由於其是虛擬函式所以執行動態連編
	//mem_fun()接受一個物件指標傳遞過來的成員函式
	for_each(q1.begin(),q1.end(),mem_fun(&Shape::draw));
	return 0;
}


(2).mem_fun_ref()接受一個物件引用傳遞過來的成員函式,它們都返回一個函式物件。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <map>
using namespace std;

class Angle
{
	int degree;
public:
	Angle(int d):degree(d){}
	int multiply(int ntimes)
	{
		return degree * ntimes;
	}
};

int main( ) 
{
	vector<Angle> q1;
	for (int i=0; i<50; i+=10)
	{
		q1.push_back(Angle(i));
	}
	int x[] = {1,2,3,4,5};
	//transform(src1.begin,src1.end,src2.bgin,dest1.begin,FunObj)
	//將q1和x中的資料相乘,然後輸出到控制檯
	//其中q1和x中對應資料操作時,都呼叫了方法Angle::multiply實現了相乘的功能
	transform(q1.begin(),q1.end(),x,ostream_iterator<int>(cout," "), mem_fun_ref(&Angle::multiply));

	return 0;
}


3.常見stl演算法

一般常用的比如查詢find、排序sort,堆運算make_heap, push_heap, pop_heap, sort_heap; 計數count_if; 拷貝copy_if ; 資料處理transform; 刪除元素remove_if

4.總結

Stl演算法中的要點:(1)確定操作源資料的迭代器beginend和目標存放資料的迭代器begin (2)確定函式物件,也就是對遍歷中每個資料需要進行的操作。(3)函式物件約束器,bind1st,bind2nd和謂詞否定迭代器not1,not2,可以固定某個運算元的大小(4)函式物件介面卡,ptr_fun(),mem_fun(),mem_fun_ref() 將普通函式、成員函式轉換為函式物件並且可以和函式對象約束器組合使用。分清楚mem_fun(針對物件指標的成員函式)和mem_fun_ref(物件本身傳遞過來的成員函式)的區別。