成員函數和非成員函數指針
事出的起因是我在leetcode刷着一道題,需要排序,於是我就自定義了一個比較函數,代碼如下:
1 class Solution { 2 public: 3 inline int digit(int x) { 4 if (!x) return 10; 5 int ret = 1; 6 while (x) ret *= 10, x /= 10; 7 return ret; 8 } 9 inline int cmp(int a, int b) { 10 long long ab = 1LL * a * digit(b) + b, ba = 1LL * b * digit(a) + a; 11 return ab > ba; 12 } 13 string largestNumber(vector<int> nums) { 14 sort(nums.begin(), nums.end(), Solution::cmp); 15 string ret; 16 char t[100]; 17 for (int i = 0; i < nums.size(); i++) { 18 if (ret == "0" && !nums[i]) continue; 19 sprintf(t, "%d", nums[i]); 20 ret = ret + t; 21 } 22 return ret; 23 } 24 };
結果這個函數死活通過不了編譯,亂改亂改還出現了invalid use of non-static member function 問題。看來成員函數不讓這么弄,翻閱C++大師Stanley Lippman的Essential C++找一下成員函數的相關用法,還參考了一些技術博客,原來是C++的語法不過關啊!!/哭
下面回顧一下我糾錯的過程,先看下面一段代碼熟悉一下怎么調用普通的函數指針:
1 /*zhen hao*/
2 #include <bits/stdc++.h>
3 using namespace std; 4
5 int F(double x) { return int(x); } 6
7 int G(double x) { return int(x) + 1; } 8
9 int main() { 10 int (*p1)(double) = &F; 11 int (*p2)(double) = &G; 12 cout << (*p1)(1) << endl; 13 cout << (*p2)(1) << endl; 14 return 0; 15 }
如果用指針指向類的成員函數的時候就稍有不同,試着根據自己的想法改一下,主要是加上類的scope運算符限定一下函數的范圍:
/*zhen hao*/ #include <bits/stdc++.h>
using namespace std; class C { public: C() {} int F(double x) { return int(x); } int G(double x) { return int(x) + 1; } }; int main() { int (C::*p1)(double) = &C::F; //編譯通過
int (C::*p2)(double) = &C::G; //編譯通過
cout << (C::*p1)(1) << endl; //error: expected unqualified-id before '*' token
cout << (C::*p2)(1) << endl; //error: expected unqualified-id before '*' token
return 0; }
想當然以為簡單改一下可以,然而編譯不通過問題出在哪里呢?編譯器報的錯誤是什么意思?
(1)先簡單解釋一下這個qualified是什么意思?
單詞的意思是有限制的,也就是一些有范圍的變量,例如下面的代碼:
1 #include <iostream>
2 int main() { 3 std::cout<<"Hello world!"<<std::endl; //cout和endl都是qualified name,因為他們都限定在std這個明明空間上。
4 return 0; 5 }
值得注意的是,如果我們之間在代碼上指定命名空間的話,例如using namespace std,那么cout和endl就是unqualified name了。
(2)另外id的全稱是identifier,即標識符。
所以上述編譯器希望這是一個qualified-id,也就是希望p1,p2是一個不需要用C::限定的變量,去掉之后仍然報錯,報的錯誤是invalid use of 'unary *' on pointer to member,不能夠用指針指着成員直接使用。
書上指出,pointer to member function和pointer to function的不同點是,前者必須通過同一類對象加以調用,而該對象便是此member function內的this指針所指之物。
所以以下用法才是正確的:
1 /*zhen hao*/
2 #include <bits/stdc++.h>
3 using namespace std; 4
5 class C { 6 public: 7 C() {} 8 int F(double x) { return int(x); } 9 int G(double x) { return int(x) + 1; } 10 }; 11
12 int main() { 13 int (C::*p1)(double) = &C::F; 14 int (C::*p2)(double) = &C::G; 15 C *c = new C(); 16 cout << (c->*p1)(1) << endl; 17 cout << (c->*p2)(1) << endl; 18 return 0; 19 }
原來我們需要的是一個實例來調用成員函數指針。
到這里就要問為什么呢?
因為代碼中定義了C類並沒有初始化這個類的成員函數。當你聲明了具體的實例之后,一個實例調用構造函數被初始化了,所以函數指針才能指向具體的類的成員函數。那如果想要傳一個成員函數指針給其他函數調用該怎么做?這就要利用static對象的特性了,因為static的生存周期是從被構造出來到程序結束,也就是程序編譯之后就被初始化好了。
所以我們將上述代碼改一下就能改正錯誤的代碼了:
1 /*zhen hao*/
2 #include <bits/stdc++.h>
3 using namespace std; 4
5 class C { 6 public: 7 C() {} 8 static int F(double x) { return int(x); } 9 static int G(double x) { return int(x) + 1; } 10 }; 11
12 int main() { 13 int (*p1)(double) = &C::F; 14 int (*p2)(double) = &C::G; 15 cout << (*p1)(1) << endl; 16 cout << (*p2)(1) << endl; 17 return 0; 18 }
用static關鍵字修飾之后的成員函數就像普通函數一樣可以傳給別的函數做參數,使用也像普通函數一樣。
總結
這次的經歷讓我認識到什么叫在錯誤中成長。一點一點積累,厚積薄發才能夠寫出健壯的代碼。