C++Primer學習(6.7 函數指針——難!)

6.7 函數指針 (這一章節比較難)
函數指針指向的是函數而非對象。和其他指針一樣,函數指針指向某種特定類型。函數的類型由它的返回類型和形參類型共同決定,與函數名無關。例如:

//比較兩個 string 對象的長度
bool lengthCompare(const string &,const string &);

該函數的類型是 bool(conststring&,conststring&)。要想聲明一個可以指向該函數的指針,只需要用指針替換函數名即可:

//pf 指向一個函數,該函數的參數是兩個const string的引用,返回值是bool 類型
bool(*pf)(const string&,const string&);//未初始化

從我們聲明的名字開始觀察,pf前面有個 *,因此pf是指針;右側是形參列表,表示pf指向的是函數;再觀察左側,發現函數的返回類型是布爾值。因此,pf就是一個指向函數的指針,其中該函數的參數是兩個const string的引用,返回值是boo1類型。
*pf 兩端的括號必不可少。如果不寫這對括號,則pf是一個返回值為 bool指針的函數:

//聲明一個名為pf的函數,該函數返回 bool * 
bool * pf(const string &const string &);

使用函數指針
當我們把函數名作為一個值使用時,該函數自動地轉換成指針。例如,按照如下形式我們可以將 lengthCompare的地址賦給pf:

pf = lengthCompare;//pf 指向名為 lengthCompare 的函數
pf = &lengthCompare;//等價的賦值語句:取地址符是可選的

此外,我們還能直接使用指向函數的指針調用該函數,無須提前解引用指針:

bool bl =pf("hello","goodbye");//調用lengthCompare 函數
bool b2=(*pf)("hello""goodbye");//一個等價的調用
bool b3=lengthCompare("hello""goodbye");// 另一個等價的調用

在指向不同函數類型的指針間不存在轉換規則。但是和往常一樣,我們可以為函數指針賦一個nullptr(參見2.3.2節,第48頁)或者值為0的整型常量表達式,表示該指針沒有指向任何一個函數:

string::size_type sumLength(const string &,const string &);
bool cstringCompare(const char*const char*);
pf = 0;//正確:pf 不指向任何函數
pf = sumLength;//錯誤:返回類型不匹配
pf = cstringCompare;//錯誤:形參類型不匹配
pf = lengthCompare;//正確:函數和指針的類型精確匹配

重載函數的指針
當我們使用重載函數時,上下文必須清晰地界定到底應該選用哪個函數。如果定義了指向重載函數的指針

void ff(int*);
void ff(unsigned int);
void(*pfl)(unsigned int)=ff;//pfl指向ff(unsigned)

編譯器通過指針類型決定選用哪個函數,指針類型必須與重載函數中的某一個精確匹配

void(*pf2)(int)=ff;//錯誤:沒有任何一個ff與該形參列表匹配
double(*pf3)(int*)=ff;//錯誤:ff和pf3的返回類型不匹配

函數指針形參
和數組類似(參見6.2.4節,第193頁),雖然不能定義函數類型的形參,但是形參可以是指向函數的指針。此時,形參看起來是函數類型,實際上卻是當成指針使用:

//第三個形參是函數類型,它會自動地轉換成指向函數的指針
void useBigger(const string & s1,const string &s2,
bool pf(const string &const string &));
//等價的聲明:顯式地將形參定義成指向函數的指針
void useBigger(const string &s1,const string &s2,bool(*pf)(const string &,const string &));

我們可以直接把函數作為實參使用,此時它會自動轉換成指針:

//自動將函數lengthCompare轉換成指向該函數的指針
useBigger(s1,s2,lengthCompare);

正如useBigger的聲明語句所示,直接使用函數指針類型顯得冗長而煩瑣。類型別名(參見2.5.1節,第60頁)和decltype(參見2.5.3節,第62頁)能讓我們簡化使用了函數指針的代碼:

//Func和Func2是函數類型
typedef bool Func(const string&,const string&);
typedef decltype(lengthCompare) Func2;//等價的類型
//FuncP 和FuncP2是指向函數的指針
typedef bool(*FuncP)(const string&,const string&);
typedef decltype(lengthCompare) *FuncP2;//等價的類型

我們使用 typedef定義自己的類型。Func和Func2是函數類型,而Funcp和FuncP2是指針類型。需要注意的是,decltype返回函數類型,此時不會將函數類型自動轉換成指針類型。因為decltype的結果是函數類型,所以只有在結果前面加上*才能得到指針。可以使用如下的形式重新聲明useBigger:

//useBigger的等價聲明,其中使用了類型別名
void useBigger(const string&,const string&,Func);
void useBigger(const string&,const string&,FuncP2);

這兩個聲明語句聲明的是同一個函數,在第一條語句中,編譯器自動地將Func 表示的函數類型轉換成指針。
返回指向函數的指針
和數組類似(參見6.3.3節,第205頁),雖然不能返回一個函數,但是能返回指向函數類型的指針。然而,我們必須把返回類型寫成指針形式,編譯器不會自動地將函數返回類型當成對應的指針類型處理。與往常一樣,要想聲明一個返回函數指針的函數,最簡單的辦法是使用類型別名:

using F=int(int*int);//F是函數類型,不是指針
using PF=int(*)(int*,int);//PF 是指針類型

其中我們使用類型別名(參見2.51節,第60頁)將F定義成函數類型,將PF定義成指向函數類型的指針。必須時刻注意的是,和函數類型的形參不一樣,返回類型不會自動地轉換成指針。我們必須顯式地將返回類型指定為指針:

PF f1(int);//正確:PF是指向函數的指針,f1返回指向函數的指針
F fl(int);//錯誤:F是函數類型,f1不能返回一個函數
F *f1(int);//正確:顯式地指定返回類型是指向函數的指針

當然,我們也能用下面的形式直接聲明f1:

int (*fl(int))(int*int);

按照由內向外的順序閱讀這條聲明語句:我們看到f1有形參列表,所以f1是個函數:f1前面有 *,所以f1返回一個指針;進一步觀察發現,指針的類型本身也包含形參列表,因此指針指向函數,該函數的返回類型是int。
出于完整性的考慮,有必要提醒讀者我們還可以使用尾置返回類型的方式(參見6.3.3節,第206頁)聲明一個返回函數指針的函數:

auto fl(int)->int(*)(int*,int);

將 auto 和 decltype 用于函數指針類型
如果我們明確知道返回的函數是哪一個,就能使用decltype 簡化書寫函數指針返回類型的過程。例如假定有兩個函數,它們的返回類型都是string::size_type,并且各有兩個 const string&類型的形參,此時我們可以編寫第三個函數,它接受一個string類型的參數,返回一個指針,該指針指向前兩個函數中的一個:

string::size_type sumlength(const string&,const string&);
string::size_type largerlength(const string&,const string&);
//根據其形參的取值,getFcn函數返回指向sumLength或者largerLength的指針
decltype(sumLength)*getFcn(const string &);

聲明 getFcn 唯一需要注意的地方是,牢記當我們將 decltype 作用于某個函數時,它返回函數類型而非指針類型。因此,我們顯式地加上* 以表明我們需要返回指針,而非函數本身。

重新學一遍這些內容:

在 C++ 中,函數指針是一種特殊的指針類型,它指向函數而非對象。函數指針在實現回調機制、動態調用函數等方面有著重要的應用。以下從定義、聲明、使用方法、應用場景等方面進行詳細介紹:
定義和聲明
函數指針的聲明需要指定函數的返回類型、參數列表,語法格式如下:

返回類型 (*指針名)(參數列表);

下面是一個簡單的示例:

#include <iostream>
// 定義一個函數
int add(int a, int b) 
{return a + b;
}
int main() 
{// 聲明一個函數指針,指向返回類型為 int,參數為兩個 int 類型的函數int (*funcPtr)(int, int);// 將函數指針指向 add 函數funcPtr = add;return 0;
}

在上述代碼中,int (*funcPtr)(int, int); 聲明了一個函數指針 funcPtr,它可以指向返回類型為 int,參數為兩個 int 類型的函數。然后將其指向了 add 函數。
使用函數指針調用函數
通過函數指針調用函數的方式和直接調用函數類似,使用 (*指針名) 或者直接使用指針名來調用函數,示例如下:

#include <iostream>
// 定義一個函數
int add(int a, int b) 
{return a + b;
}
int main() 
{// 聲明一個函數指針,指向返回類型為 int,參數為兩個 int 類型的函數int (*funcPtr)(int, int);// 將函數指針指向 add 函數funcPtr = add;// 使用函數指針調用函數int result = (*funcPtr)(3, 5);  // 方式一// 或者 int result = funcPtr(3, 5);  // 方式二std::cout << "Result: " << result << std::endl;return 0;
}

函數指針作為參數傳遞
函數指針可以作為參數傳遞給其他函數,這樣可以實現回調機制,使得函數的行為更加靈活。示例如下:

#include <iostream>
// 定義一個函數
int add(int a, int b) 
{return a + b;
}int subtract(int a, int b) 
{return a - b;
}
// 一個接受函數指針作為參數的函數
int calculate(int (*operation)(int, int), int a, int b) 
{return operation(a, b);
}int main() 
{int num1 = 10, num2 = 5;// 調用 calculate 函數,傳遞 add 函數指針int result1 = calculate(add, num1, num2);std::cout << "Addition result: " << result1 << std::endl;// 調用 calculate 函數,傳遞 subtract 函數指針int result2 = calculate(subtract, num1, num2);std::cout << "Subtraction result: " << result2 << std::endl;return 0;
}

在上述代碼中,calculate 函數接受一個函數指針 operation 作為參數,通過這個函數指針可以動態地選擇不同的操作(加法或減法)。
函數指針數組
可以創建函數指針數組,將多個函數指針存儲在數組中,方便根據需要選擇調用不同的函數。示例如下:

#include <iostream>
// 定義幾個函數
int add(int a, int b) 
{return a + b;
}int subtract(int a, int b) 
{return a - b;
}int multiply(int a, int b) 
{return a * b;
}int main() 
{// 聲明一個函數指針數組int (*funcArray[3])(int, int) = {add, subtract, multiply};int num1 = 10, num2 = 5;// 遍歷函數指針數組并調用函數for (int i = 0; i < 3; ++i) {int result = funcArray[i](num1, num2);std::cout << "Result of operation " << i + 1 << ": " << result << std::endl;}return 0;
}

注意事項
函數簽名匹配:函數指針的返回類型和參數列表必須與所指向的函數完全匹配,否則會導致編譯錯誤。
指針空值檢查:在使用函數指針之前,最好檢查其是否為 nullptr,避免調用空指針導致程序崩潰。例如:

if (funcPtr != nullptr) 
{int result = funcPtr(3, 5);
}

返回指向函數的指針
聲明返回指向函數的指針時,語法較為復雜,一般形式如下:

返回類型 (*函數名(參數列表))(返回類型, 參數列表);

下面逐步解釋這個復雜的聲明:
最外層的 函數名(參數列表) 是一個普通的函數聲明,表示這是一個函數,括號內是該函數的參數列表。
(*函數名(參數列表)) 表明這個函數返回的是一個指針。
最后的 (返回類型, 參數列表) 是該指針所指向的函數的參數列表,而整個聲明的開頭的 返回類型 是該指針所指向的函數的返回類型。

#include <iostream>
// 定義幾個簡單的函數
int add(int a, int b) 
{return a + b;
}int subtract(int a, int b) 
{return a - b;
}// 返回指向函數的指針的函數
int (*getOperation(int choice))(int, int) 
{if (choice == 1) {return add;} else {return subtract;}
}int main() 
{int choice;std::cout << "Enter 1 for addition, 2 for subtraction: ";std::cin >> choice;// 獲取函數指針int (*operation)(int, int) = getOperation(choice);int num1 = 10, num2 = 5;int result = operation(num1, num2);std::cout << "Result: " << result << std::endl;return 0;
}

在上述代碼中:
add 和 subtract 是兩個簡單的函數,分別實現加法和減法操作。
getOperation 函數根據用戶輸入的 choice 值,返回指向 add 或 subtract 函數的指針。
在 main 函數中,調用 getOperation 函數獲取函數指針,然后使用該指針調用相應的函數進行計算。
使用 typedef 簡化聲明
由于返回指向函數的指針的聲明語法較為復雜,使用 typedef 可以簡化聲明。示例如下:

#include <iostream>
// 定義幾個簡單的函數
int add(int a, int b) 
{return a + b;
}int subtract(int a, int b) 
{return a - b;
}
// 使用typedef簡化函數指針類型的聲明
typedef int (*Operation)(int, int);
// 返回指向函數的指針的函數,使用簡化后的類型
Operation getOperation(int choice) 
{if (choice == 1) {return add;} else {return subtract;}
}int main() 
{int choice;std::cout << "Enter 1 for addition, 2 for subtraction: ";std::cin >> choice;// 獲取函數指針Operation operation = getOperation(choice);int num1 = 10, num2 = 5;int result = operation(num1, num2);std::cout << "Result: " << result << std::endl;return 0;
}

空指針檢查:在使用返回的函數指針之前,最好檢查其是否為 nullptr,避免調用空指針導致程序崩潰。例如:

Operation operation = getOperation(choice);
if (operation != nullptr) 
{int result = operation(num1, num2);
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/897810.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/897810.shtml
英文地址,請注明出處:http://en.pswp.cn/news/897810.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

高級java每日一道面試題-2025年2月26日-框架篇[Mybatis篇]-Mybatis是如何將sql執行結果封裝為目標對象并返回的?都有哪些映射形式 ?

如果有遺漏,評論區告訴我進行補充 面試官: Mybatis是如何將sql執行結果封裝為目標對象并返回的?都有哪些映射形式 ? 我回答: 在Java高級面試中討論MyBatis如何將SQL執行結果封裝為目標對象并返回的過程時&#xff0c;我們可以從過程細節和映射形式兩個方面來綜合解答這個問…

react(一):特點-基本使用-JSX語法

初識React React是一個用于構建用戶界面的 JavaScript 庫&#xff0c;由 Facebook 開發和維護。 官網文檔&#xff1a;React 官方中文文檔 特點 1.聲明式編程 2.組件化開發 3.多平臺適配 開發依賴 開發React必須依賴三個庫&#xff1a; 1.react&#xff1a;包含react所必…

【Python+HTTP接口】POST請求不同請求頭構造

1、{‘Content-Type’: ‘application/json’} import requestsbody {"name1": "value1","name2": "value2"} requests.post(urlurl, databody)2、{“Content-Type”: “application/x-www-form-urlencoded; charsetUTF-8”} impor…

Java常用API:String與ArrayList的設計哲學與實踐應用

在Java編程中&#xff0c;API&#xff08;應用程序編程接口&#xff09;是開發者最強大的工具之一。它們封裝了復雜的底層邏輯&#xff0c;提供了簡潔的調用方式。本文將聚焦Java中兩個最常用的API——String和ArrayList&#xff0c;從底層原理到實際應用&#xff0c;結合深度思…

Python的字符串優雅優化策略:特定編碼 -> Unicode碼點 -> UTF-8(可自定義)

Python利用唯一uni-pot中介打理&#xff0c;任意制式輸出&#xff08;首選uyf-8&#xff09;。 筆記模板由python腳本于2025-03-14 23:37:04創建&#xff0c;本篇筆記適合喜歡探究字符串編碼細節的coder翻閱。 【學習的細節是歡悅的歷程】 博客的核心價值&#xff1a;在于輸出思…

linux 時間同步(阿里云ntp服務器)

1、安裝ntp服務 rootlocalhost ~]# yum -y install ntp 已加載插件&#xff1a;fastestmirror, langpacks Loading mirror speeds from cached hostfile* base: mirrors.nju.edu.cn* centos-sclo-rh: mirrors.nju.edu.cn* centos-sclo-sclo: mirrors.huaweicloud.com* epel: m…

虛擬化數據恢復—重裝系統服務器崩了的數據恢復過程

虛擬化數據恢復環境&故障&#xff1a; VMware虛擬化平臺 vmfs文件系統 工作人員誤操作重裝操作系統&#xff0c;服務器崩潰。 重裝系統會導致文件系統元文件被覆蓋。要恢復數據&#xff0c;必須找到&提取重裝系統前的文件系統殘留信息&#xff0c;通過提取出來的元文件…

微信開發者工具內建終端使用不了npm,但是cmd可以

下載cnpm并配置鏡像源 終端cmd&#xff1a; npm install -g cnpm --registryhttp://registry.npmmirror.com 打開微信開發者工具&#xff0c;找到方框的文件右擊選擇內建終端打開 初始化&#xff1a; npm init -y 發現npm沒有此命令 關閉微信開發工具&#xff0c;用管理…

vue/react/vite前端項目打包的時候加上時間最簡單版本,防止后端扯皮

如果你是vite項目&#xff0c;直接寫一個vite的插件&#xff0c;通過這個插件可以動態注入環境變量&#xff0c;然后當打包的時候&#xff0c;自動注入這個時間到環境變量中&#xff0c;然后在項目中App.vue中或者Main.tsx中打印出來&#xff0c;這就知道是什么時候編譯的項目了…

element-plus中Autocomplete自動補全輸入框組件的使用

目錄 1.基本使用 ①從官網賦值如下代碼 ②查看運行效果 ③代碼解讀 2.調用后端接口&#xff0c;動態獲取建議數據 結語 1.基本使用 ①從官網賦值如下代碼 <template> <div><!-- 自動補全輸入框 --><el-autocompletev-model"state":fetc…

DeFi開發的深度解析與展望

去中心化金融&#xff08;DeFi&#xff09;作為區塊鏈技術的一個重要應用&#xff0c;近年來在金融領域掀起了一股創新浪潮。它不僅為用戶提供了更加便捷、高效的金融服務&#xff0c;還重新定義了傳統金融的運作方式。本文將圍繞DeFi開發的核心要素、應用場景、面臨的問題以及…

思維鏈醫療編程方法論框架(Discuss V1版)

思維鏈醫療編程方法論框架 1. 方法論核心定義 思維鏈醫療編程方法論是一種結合結構化思維鏈(Chain of Thought)與醫療領域需求的系統化編程實踐框架,旨在通過分步邏輯推理、知識整合與動態反饋,提升醫療軟件/算法的開發效率、準確性與可解釋性。該方法論的關鍵在于通過清晰…

HarmonyOS第21天:解鎖分布式技術,開啟跨設備協同新體驗

一、HarmonyOS 分布式技術&#xff1a;開啟萬物互聯新時代 在物聯網蓬勃發展的今天&#xff0c;設備之間的互聯互通不再是遙不可及的夢想&#xff0c;而是真切融入日常生活的現實。從智能家居設備的聯動控制&#xff0c;到智能辦公場景中的高效協作&#xff0c;再到智能出行中的…

2025移動端軟件供應鏈安全開源治理方案最佳實踐

2025年3月13日&#xff0c;由中國軟件評測中心、CAPPVD漏洞庫聯合主辦的“第六期移動互聯網APP產品安全漏洞技術沙龍”在海口成功召開。懸鏡安全基于移動端數字供應鏈安全開源治理方案榮獲中國軟件評測中心“2024移動互聯網APP產品安全漏洞治理”優秀案例&#xff0c;并獲頒證書…

【Go學習】04-1-Gin框架-路由請求響應參數

【Go學習】04-1-Gin框架 初識框架go流行的web框架GinirisBeegofiber Gin介紹Gin快速入門 路由RESTful API規范請求方法URI靜態url路徑參數模糊匹配 處理函數分組路由 請求參數GET請求參數普通參數數組參數map參數 POST請求參數表單參數JSON參數 路徑參數文件參數 響應字符串方式…

哈爾濱算力服務器托管推薦-青蛙云

哈爾濱年平均氣溫3.5攝氏度&#xff0c;有發展云計算和算力數據中心的天然優勢 &#xff0c;今天為哈爾濱算力服務器托管服務商&#xff1a;青蛙云&#xff0c;黑龍江經營17年的老牌IDC服務商。 先來了解下算力服務器&#xff1a; 算力服務器&#xff0c;尤其是那些用于運行人…

【C++】每日一練(有效的括號)

本篇博客給大家帶來的是用C語言來解答有效的括號&#xff01; &#x1f41f;&#x1f41f;文章專欄&#xff1a;每日一練 &#x1f680;&#x1f680;若有問題評論區下討論&#xff0c;我會及時回答 ??歡迎大家點贊、收藏、分享&#xff01; 今日思想&#xff1a;不服輸的少年…

Embedding模型到底是什么?

嵌入模型&#xff08;Embedding Model&#xff09;是一種將高維數據映射到低維空間的工具&#xff0c;廣泛應用于自然語言處理&#xff08;NLP&#xff09;、推薦系統和圖像識別等領域。它的核心目標是將復雜的數據&#xff08;如文本、圖像或用戶行為&#xff09;轉換為稠密的…

Centos離線安裝perl

文章目錄 Centos離線安裝perl1. perl是什么&#xff1f;2. Centos下載地址&#xff1f;3. perl的安裝4. 安裝結果驗證 Centos離線安裝perl 1. perl是什么&#xff1f; Perl 是一種 高級腳本語言&#xff0c;誕生于 1987 年&#xff0c;以強大的 文本處理能力 和靈活性著稱&…

快速學習Bootstrap前端框架

什么是 Bootstrap? Bootstrap 是一個開源的前端框架,用于快速開發響應式(Responsive)和美觀的網頁。它包含: ? HTML 組件(導航欄、按鈕、表單等) ? CSS 樣式(網格系統、排版、顏色等) ? JavaScript 交互(模態框、輪播圖、工具提示等) 官網:Bootstrap The mo…