1. 程式人生 > >C++ Pirmer : 第十四章 : 過載運算子與型別轉換之函式呼叫運算子與標準庫的定義的函式物件

C++ Pirmer : 第十四章 : 過載運算子與型別轉換之函式呼叫運算子與標準庫的定義的函式物件

函式呼叫運算子

struct test {
	int operator()(int val) const { return (i > 0 ? i : -i); }
};

所謂的函式呼叫就是一個類過載了函式呼叫符,類在使用過載函式呼叫符時接受相應引數。這一過程就像是使用一個函式一樣,因此叫做函式呼叫。 上面的類test,它過載了函式呼叫符(), 接受一個int型別引數,返回它的絕對值。 我們就可以將一個test類物件當做一個函式來使用:
int main(void) 
{
	test t;
	int val = -3;
	std::cout << "abs(val) = " << t(val) << std::endl;
	return 0;
}

使用函式過載呼叫符時,直接在類名後面跟括號,括號里加所需引數即可,看起來就和一個普通的函式呼叫一樣。 我們稱這樣的類為“行為像函式一樣的類”。 如果類定義了函式呼叫運算子,則該類的物件稱作函式物件函式呼叫運算子必須是成員函式。 函式物件常常作為泛型演算法的實參。我們可以定義一個函式物件,作用為輸出實參內容,我們將其作為標準庫定義的for_each的實參:
class printString {
public:
	printString(std::ostream& o = std::cout) : os(o) {}
	void operator()(const std::string& s) { os << "s = " << s << std::endl; }
private:
	std::ostream& os;
};

int main(void)
{
	std::vector<std::string> vec = { "Hello", "World" };
	printString ps;
	for_each(vec.begin(), vec.end(), ps);
	return 0;
}


lambda是函式物件

當編寫一個lambda表示式時,編譯器將該表示式翻譯成一個未命名類的未命名物件。例如:
stable_sort(words.begin(), words.end(), [](const std::string& a, const std::string& b) { return a.size() < b.size(); });


編譯器會為我們生成一個類似這樣的一個類的物件(未命名的):
class Shorter {
public:
	bool operator()(const std::string& a, const std::string& b) const { return a.size() < b.size(); }
	
};

因為預設的lambda是不可以修改捕獲引數的,因此上面這個生成的類似的類的函式呼叫運算子是一個const成員函式。 如果lambda宣告捕獲引數是可變的(mutable), 則翻譯成的類的函式呼叫運算子就不是const的了。 捕獲的變數被拷貝到lambda中,因此, 這種lambda產生的類必須為每個值的變數建立對應的資料成員,同時建立建構函式:
auto wc = find_if(words.begin(), words.end(), [sz](const std::string& a) { return a.size() >= sz; });

該lambda產生的類形如:
class SizeComp {
public:
	SizeComp(size_t n) : sz(n) {}
	bool operator()(const std::string& a) const { return a.size() >= sz; }
	
private:
	size_t sz
};

lambda產生的類不含預設建構函式、賦值運算子及預設解構函式,它是否有預設的拷貝/移動建構函式則視捕獲的資料成員的型別決定
習題14.38

class CheckString {
public:
	bool operator()(const std::string& s) { return (s.size() >= 1 && s.size() <= 10); }
	
	size_t operator()(std::vector<std::string>& vec, size_t length) {
		size_t num = 0;
		for (const auto& elmt : vec)
			(elmt.size() == length) ? ++num : num;
		return num;
	}
	
};


int main()
{
	CheckString ck;
	std::string s{"HelloWorld"};
	std::vector<std::string> vec = { "hello", "world", "i", "love", "you", "but", "you", "is", "what" }; 

	std::cout << "s.size() >= 1 && s.size() <= 10 ?" << ck(s) << std::endl;

	size_t n = 1;
	std::cout << "size == 1 has " << ck(vec, n) << std::endl;

	return 0;
}


標準庫定義的函式物件

在標頭檔案 <functional>中,定義了一組表示算術運算子、關係運算符和邏輯運算子的類,每個類分別定義了一個執行命名操作的呼叫運算子。 這些類都被定義成模板的形式。
算術:
plus<Type>  // 加法
minus<Type>  // 減法
multiplies<Type>  // 乘法
divides<Type>  // 除法
modulus<Type>  // 取模
negate<Type>  //  取反

關係:
equal_to<Type>  // 等於
not_equal_to<Type>  // 不等於
greater<Type>  // 大於
greater_equal<Type>  // 大於等於
less<Type>  // 小於
less_equal<Type>  // 小於等於

邏輯:
logical_and<Type>  // 邏輯與
logical_or<Type>  // 邏輯或
logical_not<Type>  // 邏輯非


這些函式物件對於指標同樣適用。 比較兩個無關指標會產生未定義的行為,如果希望通過比較指標的地址來sort指標的vector,直接這樣做會產生未定義的行為。不過我們可以使用標準庫定義的函式物件來比較,標準庫規定指標的less是定義良好的。
std::vector<std::string*> vec;
// 未定義行為!
sort(vec.begin(), vec.end(), [](std::string* a, std::string* b){ return a < b; });

sort(vec.begin(), vec.end(), less<std::string*>());

關聯容器使用less<key_type>對元素排序可呼叫物件與function 標準庫function定義在標頭檔案 <functional>中
functional<T> f;	f是一個用來儲存可呼叫物件的空function,這些可呼叫物件的呼叫形式應該與函式型別T相同
functional<T> f(nullptr);	顯示的構造一個空function
functional<T> f(obj);	在f中儲存可呼叫物件obj的副本
f	將f做為條件,當f含有一個可呼叫物件時為真,否則為假
f(args)	呼叫f中的物件,引數是args

定義為function<T>的成員的型別
result_type		該function型別的可呼叫物件返回的型別
argument_type	當T有一個或兩個實參時定義的型別,當T有一個實參時,argument_type是該型別的同義詞
first_argument_type		當有兩個實參時,first_argument_type和second_argument_type分別代表兩個實參型別
second_argument_type

function是一個模板, 建立一個function時必須指定它的型別。
int add(int i, int j) { return i / j; }
auto mod = [](int x, int y){ return x % y; };
struct divide {
	int operator()(int a, int b) { return a / b; }
};

std::function<int(int, int)> f1 = add;
std::function<int(int, int)> f2 = divide();
std::function<int(int, int)> f3 = [](int x, int y){ return x % y; };

std::cout << f1(4, 2) << std::endl;
std::cout << f2(4, 2) << std::endl;
std::cout << f3(4, 2) << std::endl;


對於過載的函式,不能直接放入function型別物件中:
int add(int i, int j) { return i / j; }
Sales_data add(const Sales_data&, const Sales_data&);

int add(int i, int j) { return i / j; }
Sales_data add(const Sales_data&, const Sales_data&);

std:map<std::string, std::function<int(int, int)>> mp;
mp.insert({"+", add});  // 錯誤,哪個add?
可以使用函式指標來避免二義性的問題:
int (*fp)(int, int) = add;
mp.insert({"+", fp});

或者還可以使用lambda來消除二義性。