void (*funptr)(int param);
這就是一個簡單的函數指針的聲明。顧名思義,函數指針是一個特殊的指針,它用於指向函數被加載到的內存首地址,可用於實現函數調用。數組
函數名也是指向函數的內存首地址的,他和函數指針有什麼不一樣?——既然他是指針,並且不是const的,那麼他就是靈活可變的,經過賦值不一樣的函數來實現不一樣的函數調用。函數
然而他也有本身的限制(函數簽名——返回值類型和參數類型),那不是和覆蓋、多態實現的功能同樣了麼?額。。。要這麼理解也行,但不全對。編碼
上面說到函數指針的功能相似覆蓋或多態,覆蓋和多態更多體現的是對象自身的特徵和對象之間的繼承關聯,而函數指針則沒這麼多講究,他就是靈活。spa
函數指針不須要依附於對象存在,他能夠用來解決基於條件的多個函數篩選,也能夠處理徹底無關的幾個函數。指針
因此他的做用,什麼封裝性好、用於回調函數、實現多態等等,隨便了,只有一條,符合他的函數簽名而且可達。調試
和變量指針相似,聲明->賦值->使用 或者 定義->使用code
int add(int a,int b) { return a + b; } //1.聲明->賦值->使用 int (*fun)(int a,int b); fun = add; //也可以使用 fun = &add;從某篇文章看到是歷史緣由,後面稍做分析 fun(5,10); //也可以使用 (*fun)(5,10);緣由同上
//2.經過宏定義函數指針類型 #define int (*FUN)(int,int); FUN fun = NULL; fun = add; fun(5,10); //3.定義->使用 int (*fun)(int a,int b) = add; fun(5,10); //4.宏定義 #define int (*FUN)(int,int); FUN fun = add; fun(5,10);
上面的代碼中,又是取地址符&,又是取引用符*,結果還能相互賦值,交叉調用,這又怎麼理解?對象
首先來看下函數指針、函數名的類型。對於函數指針fun,類型爲 int (*)(int,int),這個很好理解,函數名add的類型,經過VS在靜態狀況下用鼠標查看,類型爲int (*)(int,int)。blog
what?爲啥不是int ()(int,int)呢?這個咱們能夠類比數組。數組名爲指向內存中數組首地址的指針,同時數組名可當作指針對數組進行操做。而對於函數名,經過在VS下查看彙編代碼能夠知道,編譯器將函數名賦值爲函數加載入內存的首地址,經過call 函數名來跳轉到相應內存地址進行函數的執行。因此對這裏的函數名爲指針類型也能夠理解。繼承
這樣 fun = add 咱們能夠理解了,那 fun = &add 又是什麼鬼? (*fun)(5,10)也能夠理解,fun(5,10)呢?下面來看看彙編代碼
//原始代碼 int main() { FUN f = NULL; f = &add; FUN f1 = NULL; f1 = add; add(1,2); std::cout<< add <<" "<< &add <<" " << *add <<std::endl; std::cout<< f <<" " << &f <<" " << *f <<" "<<std::endl; std::cout<< (*f)(5, 10) << " " << f(5,10) <<std::endl; std::cout << (*add)(5, 10) << " " <<(&add)(5, 10) << std::endl; } //對應彙編代碼 //對add函數做了一層跳轉(從標識add->add函數),記錄了add函數的入口地址 add: 000412F8 jmp add (042180h) //add函數彙編代碼 int add(int a, int b) { 00042180 push ebp 00042181 mov ebp,esp 00042183 sub esp,0C0h 00042189 push ebx 0004218A push esi 0004218B push edi 0004218C lea edi,[ebp-0C0h] 00042192 mov ecx,30h 00042197 mov eax,0CCCCCCCCh 0004219C rep stos dword ptr es:[edi] return a + b; 0004219E mov eax,dword ptr [a] 000421A1 add eax,dword ptr [b] } int main() { 00042450 push ebp 00042451 mov ebp,esp 00042453 sub esp,0DCh 00042459 push ebx 0004245A push esi 0004245B push edi 0004245C lea edi,[ebp-0DCh] 00042462 mov ecx,37h 00042467 mov eax,0CCCCCCCCh 0004246C rep stos dword ptr es:[edi] //能夠看到,對編譯器來講&add和add實際上是同樣的,都對應內存中的標識add,即上面的jmp代碼的地址 //這裏f、f1都被看成指針,指向標識add,與 int(*)(int,int) 中的那個*對應 //由於在同一代碼段,經過offset獲取段內偏移便可實現跳轉 FUN f = NULL; 0004246E mov dword ptr [f],0 f = &add; 00042475 mov dword ptr [f],offset add (0412F8h) FUN f1 = NULL; 0004247C mov dword ptr [f1],0 f1 = add; 00042483 mov dword ptr [f1],offset add (0412F8h) //經過call調用函數,函數對應標識add add(1,2); 0004248A push 2 0004248C push 1 0004248E call add (0412F8h) 00042493 add esp,8 //這裏能夠看到,對於add、&add、*add 的值,編譯器都當作標識add,由此能夠猜想,對於函數名編譯器有特殊處理 //其實這個也能夠理解,首先函數名即函數的內存首地址,對這個地址值取地址沒有什麼意義,函數調度由系統完成,而指針的指針做用就是改變第一層指針的值,改變函數名的指向沒有意義 //而對其取值,那就是代碼的機器碼,由於代碼存放於只讀段,咱們不會也不能對其進行重寫、拷貝等操做,機器碼咱們也沒法操做,因此這個也沒有意義 std::cout<< add <<" "<< &add <<" " << *add <<std::endl; 00042496 mov esi,esp //cout爲從右往左,所有壓棧後再先進後出地輸出,第一個爲std::endl 00042498 push offset std::endl<char,std::char_traits<char> > (041410h) 0004249D mov edi,esp 0004249F push offset add (0412F8h) 000424A4 push offset string " " (049B30h) 000424A9 mov ebx,esp 000424AB push offset add (0412F8h) 000424B0 push offset string " " (049B34h) 000424B5 mov eax,esp 000424B7 push offset add (0412F8h) 000424BC mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (04D098h)] 000424C2 mov dword ptr [ebp-0DCh],eax 000424C8 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 000424CE mov ecx,dword ptr [ebp-0DCh] //函數調用結束後的平衡堆棧檢測 000424D4 cmp ecx,esp 000424D6 call __RTC_CheckEsp (04115Eh) 000424DB push eax 000424DC call std::operator<<<std::char_traits<char> > (041438h) 000424E1 add esp,8 000424E4 mov ecx,eax 000424E6 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 000424EC cmp ebx,esp 000424EE call __RTC_CheckEsp (04115Eh) 000424F3 push eax 000424F4 call std::operator<<<std::char_traits<char> > (041438h) 000424F9 add esp,8 000424FC mov ecx,eax 000424FE call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 00042504 cmp edi,esp 00042506 call __RTC_CheckEsp (04115Eh) 0004250B mov ecx,eax 0004250D call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0ACh)] 00042513 cmp esi,esp 00042515 call __RTC_CheckEsp (04115Eh) //函數指針f、指針取值*f 都是 dword ptr [f],即標識指針f指向的內存的值(標識add的偏移),若是是取值,*f與*add同樣沒有意義 //而&f即標識指針f的值,這裏取地址的意義即對應他的指針類型,能夠指向不一樣內存地址來調用不一樣函數 //由此能夠猜想,對於函數指針編譯器也有相似的特殊處理 std::cout<< f <<" " << &f <<" " << *f <<" "<<std::endl; 0004251A mov esi,esp 0004251C push offset std::endl<char,std::char_traits<char> > (041410h) 00042521 push offset string " " (049B30h) 00042526 mov edi,esp 00042528 mov eax,dword ptr [f] 0004252B push eax std::cout<< f <<" " << &f <<" " << *f <<" "<<std::endl; 0004252C push offset string " " (049B30h) 00042531 mov ebx,esp 00042533 lea ecx,[f] 00042536 push ecx 00042537 push offset string " " (049B30h) 0004253C mov eax,esp 0004253E mov edx,dword ptr [f] 00042541 push edx 00042542 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (04D098h)] 00042548 mov dword ptr [ebp-0DCh],eax 0004254E call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 00042554 mov ecx,dword ptr [ebp-0DCh] 0004255A cmp ecx,esp 0004255C call __RTC_CheckEsp (04115Eh) 00042561 push eax 00042562 call std::operator<<<std::char_traits<char> > (041438h) 00042567 add esp,8 0004256A mov ecx,eax 0004256C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 00042572 cmp ebx,esp 00042574 call __RTC_CheckEsp (04115Eh) 00042579 push eax 0004257A call std::operator<<<std::char_traits<char> > (041438h) 0004257F add esp,8 00042582 mov ecx,eax 00042584 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 0004258A cmp edi,esp 0004258C call __RTC_CheckEsp (04115Eh) 00042591 push eax 00042592 call std::operator<<<std::char_traits<char> > (041438h) 00042597 add esp,8 std::cout<< f <<" " << &f <<" " << *f <<" "<<std::endl; 0004259A mov ecx,eax 0004259C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0ACh)] 000425A2 cmp esi,esp 000425A4 call __RTC_CheckEsp (04115Eh) //函數調用時 f、*f 都被當成 call dword ptr [f],同上,而&f對應的是指針,就不能進行函數調用了 std::cout<< (*f)(5, 10) << " " << f(5,10) <<std::endl; 000425A9 mov esi,esp 000425AB push offset std::endl<char,std::char_traits<char> > (041410h) 000425B0 mov edi,esp 000425B2 push 0Ah 000425B4 push 5 000425B6 call dword ptr [f] 000425B9 add esp,8 000425BC cmp edi,esp 000425BE call __RTC_CheckEsp (04115Eh) 000425C3 mov edi,esp 000425C5 push eax 000425C6 push offset string " " (049B30h) 000425CB mov ebx,esp 000425CD push 0Ah 000425CF push 5 000425D1 call dword ptr [f] 000425D4 add esp,8 000425D7 cmp ebx,esp 000425D9 call __RTC_CheckEsp (04115Eh) 000425DE mov ebx,esp 000425E0 push eax 000425E1 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (04D098h)] 000425E7 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A8h)] 000425ED cmp ebx,esp 000425EF call __RTC_CheckEsp (04115Eh) 000425F4 push eax 000425F5 call std::operator<<<std::char_traits<char> > (041438h) 000425FA add esp,8 000425FD mov ecx,eax 000425FF call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A8h)] 00042605 cmp edi,esp 00042607 call __RTC_CheckEsp (04115Eh) 0004260C mov ecx,eax 0004260E call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0ACh)] 00042614 cmp esi,esp 00042616 call __RTC_CheckEsp (04115Eh) //如上所說,*add、&add、*add三者相同,因此下面兩個調用也成立,可是爲了良好的編碼風格,儘可能避免 std::cout << (*add)(5, 10) << " " <<(&add)(5, 10) << std::endl; 0004261B mov esi,esp 0004261D push offset std::endl<char,std::char_traits<char> > (041410h) 00042622 push 0Ah 00042624 push 5 00042626 call add (0412F8h) 0004262B add esp,8 0004262E mov edi,esp 00042630 push eax 00042631 push offset string " " (049B30h) 00042636 push 0Ah 00042638 push 5 0004263A call add (0412F8h) 0004263F add esp,8 00042642 mov ebx,esp 00042644 push eax 00042645 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (04D098h)] 0004264B call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A8h)] 00042651 cmp ebx,esp 00042653 call __RTC_CheckEsp (04115Eh) 00042658 push eax 00042659 call std::operator<<<std::char_traits<char> > (041438h) 0004265E add esp,8 00042661 mov ecx,eax 00042663 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A8h)] 00042669 cmp edi,esp 0004266B call __RTC_CheckEsp (04115Eh) 00042670 mov ecx,eax 00042672 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0ACh)] 00042678 cmp esi,esp 0004267A call __RTC_CheckEsp (04115Eh) }
綜上所述,對於函數名add,add、*add、&add三者等價,都指向函數首地址;對於函數指針f,f 和 *f 等價,一樣是指向函數首地址;而&f 爲函數指針的地址,由於函數指針 f 的值可變,可指向不一樣的函數。
當咱們調試代碼時,將指針移到函數名或函數指針上,會顯示其類型爲 functionprt,編譯器對其的特殊處理可能就源自於此。
網上有些文章對這種機制的解釋爲,出於歷史的緣由(從面向過程到面向對象的過渡,函數指針與對象指針的關係等等)