網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
C++成員函數(shù)如何當(dāng)作回調(diào)函數(shù)同時(shí)傳遞this指針_C 語(yǔ)言
作者:易拉罐里的人 ? 更新時(shí)間: 2022-12-23 編程語(yǔ)言就我目前了解所知,有三種函數(shù)可以作為回調(diào)函數(shù):
- 1.普通函數(shù)
- 2.靜態(tài)函數(shù)(我用得少?zèng)]有寫(xiě),直接跳過(guò))
- 3.成員函數(shù)
1.普通函數(shù)作為注冊(cè)函數(shù)
普通函數(shù)作為回調(diào)函數(shù),比較簡(jiǎn)單,只要函數(shù)簽名(返回值類型+參數(shù)類型)一致就可以了。
因?yàn)槠胀ê瘮?shù)不是類成員函數(shù),如果想要訪問(wèn)類成員,在執(zhí)行回調(diào)函數(shù)的時(shí)候,要把對(duì)象指針傳給回調(diào)函數(shù),如下代碼:
namespace yy0
{
//普通全局函數(shù)
void call_back(void* pointer);
class A
{
public:
A() {
//初始化指針
p_call_back = NULL;
//單數(shù)最大的數(shù)
num = 9;
}
~A() {}
public:
//打印這個(gè)數(shù)來(lái)驗(yàn)證是否正常調(diào)用回調(diào)函數(shù)
int num;
private:
//指向回調(diào)函數(shù)的地址的指針
void(*p_call_back)(void*);
public:
//用于注冊(cè)回調(diào)函數(shù)
void register_call_back(void(*p)(void*)) {
if (p)
p_call_back = p;
}
//執(zhí)行回調(diào)函數(shù)
void run_call_back() {
if (p_call_back)
{
//把對(duì)象指針傳遞出去
p_call_back(this);
}
}
//測(cè)試函數(shù)
void test() {
//注冊(cè)
register_call_back(call_back);
//執(zhí)行
run_call_back();
}
};
void call_back(void* pointer)
{
if (pointer)
{
//需要進(jìn)行指針轉(zhuǎn)換
A* p = (A*)pointer;
cout << "打印的值:" << p->num;
}
}
}
int main()
{
yy0::A a;
a.test();
getchar();
return 0;
}
結(jié)果正確打印,說(shuō)明回調(diào)函數(shù)正常調(diào)用:
也可以定義一個(gè)全局的類對(duì)象指針:
namespace yy0
{
//前置聲明
class A;
//普通全局函數(shù)
void call_back();
//全局的類對(duì)象指針
A* pointer = NULL;
class A
{
public:
A() {
//給全局類對(duì)象指針賦值
pointer = this;
//初始化指針
p_call_back = NULL;
//單數(shù)最大的數(shù)
num = 9;
}
~A() {}
public:
//打印這個(gè)數(shù)來(lái)驗(yàn)證是否正常調(diào)用回調(diào)函數(shù)
int num;
private:
//指向回調(diào)函數(shù)的地址的指針
void(*p_call_back)();
public:
//用于注冊(cè)回調(diào)函數(shù)
void register_call_back(void(*p)()) {
if (p)
p_call_back = p;
}
//執(zhí)行回調(diào)函數(shù)
void run_call_back() {
if (p_call_back)
{
//把對(duì)象指針傳遞出去
p_call_back();
}
}
//測(cè)試函數(shù)
void test() {
//注冊(cè)
register_call_back(call_back);
//執(zhí)行
run_call_back();
}
};
void call_back()
{
if (pointer)
{
//需要進(jìn)行指針轉(zhuǎn)換
A* p = (A*)pointer;
cout << "打印的值:" << p->num;
}
}
}
這也可以正確執(zhí)行,但是這種定義全局的對(duì)象指針有風(fēng)險(xiǎn)。如果只創(chuàng)建一個(gè)A的對(duì)象,就可以正常使用,不會(huì)出現(xiàn)什么太大問(wèn)題。但是,一旦創(chuàng)建的對(duì)象個(gè)數(shù)≥2,那么就造成數(shù)據(jù)讀取錯(cuò)誤的問(wèn)題。
可以想象一下,創(chuàng)建對(duì)象a1時(shí),全局對(duì)象指針pointer是指向a1的位置,那么讀取的pointer->num,是a1對(duì)象的num。
然后再創(chuàng)建a2,那么全局對(duì)象指針pointer就變成了指向a2的位置(因?yàn)閜ointer是個(gè)全局變量,從始至終只有一個(gè)這個(gè)變量),那么執(zhí)行a2.text(),pointer->num讀取的是a2的num。
如果執(zhí)行a1.text(),那么此時(shí),pointer->num讀取的也是a2的num,而不是a1的num。更嚴(yán)重的是,一旦刪除了a1或者a2,就會(huì)造成另外一個(gè)對(duì)象訪問(wèn)內(nèi)存失敗的問(wèn)題。
2.靜態(tài)函數(shù)作為注冊(cè)函數(shù)
這個(gè)就自行上網(wǎng)查看吧,我用的少就不寫(xiě)了。
3.成員函數(shù)作為注冊(cè)函數(shù)
假設(shè)場(chǎng)景:A類成員函數(shù)作為B類回調(diào)函數(shù)
《深度探索C++對(duì)象模型》這本書(shū)講到,類成員函數(shù)都有一個(gè)隱藏參數(shù)用于傳遞this指針,這個(gè)this傳遞給函數(shù)由編譯器來(lái)完成,不需要用戶來(lái)做。
直接上代碼:
namespace yy3
{
class B
{
public:
B() {
pointer = NULL;
}
~B() {}
public:
//存放A類的this指針
void* pointer;
//指向回調(diào)函數(shù)
void(__stdcall *pCallBack)(void*);
public:
/*
@函數(shù)作用:注冊(cè)回調(diào)
@輸入?yún)?shù):
void(*p)(void*) -- 輸入A類的回調(diào)函數(shù)的地址
void* p_this -- 輸入A類的this指針
*/
//②
void register_fun(void(__stdcall *p)(void*), void* p_this) {
pCallBack = p;
pointer = p_this;
}
//執(zhí)行回調(diào)
//③
void run_call_back() {
if (pCallBack)
pCallBack(pointer);
}
};
class A
{
public:
A() {
a = 5;
}
A(int num) {
a = num;
};
~A() {}
public:
//在A類中定義一個(gè)B類的變量
B b;
//拿來(lái)測(cè)試的變量
double a;
//定義聯(lián)合,不知道原理,網(wǎng)上查到的技巧
union for_callback {
void(__stdcall *fun_int_c)(void*);
void (A::*fun_in_class)(void*);
}fp;
public:
//要拿來(lái)注冊(cè)的回調(diào)函數(shù)
void call_back(void* p) {
A* pointer = (A*)p;
//能打印出正確的a值就對(duì)了
cout << "a:" << pointer->a << endl;
}
//測(cè)試函數(shù)
//①
void test() {
fp.fun_in_class = &A::call_back;
b.register_fun(fp.fun_int_c, this);
b.run_call_back();
}
};
}
int main()
{
yy3::A a;
a.test();
getchar();
return 0;
}
首先來(lái)解釋一地方
1.__stdcall聲明:這個(gè)看情況,我在公司電腦寫(xiě)的時(shí)候不需要加這個(gè)關(guān)鍵字,自己的電腦就要加這個(gè)。就是一個(gè)傳參約定,可以上網(wǎng)查。
2.
//定義聯(lián)合,不知道原理,網(wǎng)上查到的技巧
union for_callback {
void(__stdcall *fun_int_c)(void*);
void (A::*fun_in_class)(void*);
}fp;
使用union,這個(gè)說(shuō)是為了逃避編譯器檢查,原理我也不太懂,如果有知道原理的大神,麻煩告訴一下下,感謝感謝。我直接就拿來(lái)用了。
3.前面說(shuō)了成員函數(shù)有個(gè)隱含傳遞指針的參數(shù),所以函數(shù)指針:
//指向回調(diào)函數(shù)
void(__stdcall *pCallBack)(void*);
需要定義參數(shù)為void*的函數(shù)指針,用于傳遞A類的this指針
4.因?yàn)楹瘮?shù)指針是B類的成員,而函數(shù)指針接受的參數(shù)是A類的this指針,我們不能直接這樣使用:
void run_call_back() {
if (pCallBack)
pCallBack(this);
}
這個(gè)pCallBack(this)中的this是指向B類對(duì)象的地址而非A類對(duì)象的地址,因此,在B類定義一個(gè)成員:void* pointr,用于保存A類對(duì)象的指針,然后這樣使用
//執(zhí)行回調(diào)
void run_call_back() {
if (pCallBack)
pCallBack(pointer);
}
這樣就運(yùn)行回調(diào)函數(shù),同時(shí)傳遞A類對(duì)象指針。
5.(無(wú)參這一點(diǎn)單獨(dú)在這里說(shuō))當(dāng)然,雖然成員函數(shù)有自帶隱藏參數(shù),我們也可以把它轉(zhuǎn)換成無(wú)參的函數(shù),修改這些地方:
//【1】
//指向回調(diào)函數(shù)
void(__stdcall *pCallBack)(void*);
//修改為
void(__stdcall *pCallBack)();
//【2】
void register_fun(void(__stdcall *p)(void*), void* p_this) {
pCallBack = p;
pointer = p_this;
}
//修改為
void register_fun(void(__stdcall *p)(), void* p_this) {
pCallBack = p;
pointer = p_this;
}
//【3】
//執(zhí)行回調(diào)
void run_call_back() {
if (pCallBack)
pCallBack(pointer);
}
//修改為
void run_call_back() {
if (pCallBack)
pCallBack();
}
//【4】
union for_callback {
void(__stdcall *fun_int_c)(void*);
void (A::*fun_in_class)(void*);
}fp;
//修改為
union for_callback {
void(__stdcall *fun_int_c)();
void (A::*fun_in_class)();
}fp;
//【5】
//要拿來(lái)注冊(cè)的回調(diào)函數(shù)修改為
void call_back() {
cout << "a:" << this->a << endl;
}
這種情況編譯能通過(guò),但是void call_back()使用this指針,是無(wú)法正確讀取內(nèi)存的值,如下
言歸正傳。
成員函數(shù)轉(zhuǎn)為帶一個(gè)void*參數(shù)的函數(shù)運(yùn)行情況如下:
?結(jié)果也是一個(gè)不正確的值,因此進(jìn)行調(diào)試查看,把斷點(diǎn)放在這個(gè)函數(shù)上,發(fā)現(xiàn)了一個(gè)奇怪的問(wèn)題:
//要拿來(lái)注冊(cè)的回調(diào)函數(shù)
void call_back(void* p)
{
A* pointer = (A*)p;
//能打印出正確的a值就對(duì)了
cout << "a:" << pointer->a << endl;
}
pointer是A類對(duì)象的指針,pointer通過(guò)函數(shù)指針pCallBack(pointr)傳遞給了call_back(void* p),從理論上講,p的值要與pointer保持一致才對(duì)。但是p的值與pCaalBack相同,也就是p是函數(shù)指針,特別奇怪。我也不知道什么原因,所以如果有人知道,麻煩跟我講一下,在這里先謝謝了。
我無(wú)法解決這個(gè)問(wèn)題,所以嘗試了將函數(shù)指針轉(zhuǎn)為帶有兩個(gè)void*參數(shù)的函數(shù),竟然可以傳遞正確的this指針,算是瞎貓碰上死耗子,代碼跟上面類似,如下:
namespace yy3
{
class B
{
public:
B() {
pointer = NULL;
}
~B() {}
public:
//存放A類的this指針
void* pointer;
//指向回調(diào)函數(shù)
void(__stdcall *pCallBack)(void*, void*);
public:
/*
@函數(shù)作用:注冊(cè)回調(diào)
@輸入?yún)?shù):
void(*p)(void*,void*) -- 輸入A類的回調(diào)函數(shù)的地址
void* p_this -- 輸入A類的this指針
*/
void register_fun(void(__stdcall *p)(void*, void*), void* p_this) {
pCallBack = p;
pointer = p_this;
}
//執(zhí)行回調(diào)
void run_call_back() {
if (pCallBack)
//需要兩個(gè)指針作為參數(shù),干脆就傳遞兩個(gè)pointer吧
pCallBack(pointer,pointer);
}
};
class A
{
public:
A() {
a = 5;
}
A(int num) {
a = num;
};
~A() {}
public:
//在A類中定義一個(gè)B類的變量
B b;
//拿來(lái)測(cè)試的變量
double a;
//定義聯(lián)合,不知道原理,網(wǎng)上查到的技巧
union for_callback {
void(__stdcall *fun_int_c)(void*, void*);
void (A::*fun_in_class)(void*, void*);
}fp;
public:
//要拿來(lái)注冊(cè)的回調(diào)函數(shù)
void call_back(void* p, void* pp)
{
A* pointer = (A*)p;
//能打印出正確的a值就對(duì)了
cout << "a:" << pointer->a << endl;
}
//測(cè)試函數(shù)
void test() {
fp.fun_in_class = &A::call_back;
b.register_fun(fp.fun_int_c, this);
b.run_call_back();
}
};
}
結(jié)果是正確的:
?從圖上可知,pCallBack(函數(shù)指針)的值,與p和pp都不同,無(wú)論是p還是pp,這兩個(gè)值都是A類對(duì)象的地址,也就是說(shuō),已經(jīng)成功把A的this指針傳遞進(jìn)來(lái)了。因此結(jié)果也是正確的。
原文鏈接:https://blog.csdn.net/weixin_45416828/article/details/124074506
相關(guān)推薦
- 2022-06-04 CZGL.ProcessMetrics監(jiān)控.NET應(yīng)用_實(shí)用技巧
- 2022-05-23 React中setState同步異步場(chǎng)景的使用_React
- 2022-08-19 解決:curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSC
- 2022-11-04 ASP.NET?MVC獲取多級(jí)類別組合下的產(chǎn)品_實(shí)用技巧
- 2022-04-16 C語(yǔ)言線性表之雙鏈表詳解_C 語(yǔ)言
- 2022-11-17 獲取C++變量類型的簡(jiǎn)單方法_C 語(yǔ)言
- 2024-02-17 通過(guò)springMVC統(tǒng)一設(shè)置localhost的時(shí)間格式
- 2022-04-06 C/C++的內(nèi)存管理你了解嘛_C 語(yǔ)言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支