函數(shù)指針
不只變量有地址犁钟,函數(shù)也有地址
void example(int n)
{
printf("%d\n",n);
}
int main()
{
//打印函數(shù)的地址
printf("%08X\n",&example);
//printf("%p\n",&example);
return 0;
}
每個函數(shù)在編譯后都對應一串指令,這些指令在內(nèi)存中的位置就是函數(shù)的地址
我們可以用一個指針類型來表示函數(shù)的地址
void (*p) (int);
//變量名為p,變量類型為函數(shù)指針从隆,記作void (int)* 歉眷,返回值為void,參數(shù)為int
void example(int n)
{
printf("%d\n",n);
}
int main()
{
void (*p) (int);
p = &example;
return 0;
}
void example(int a,int b)
{
printf("%d,%d\n",a,b);
}
int main()
{
void (*p) (int,int);
p = &example;
return 0;
}
第一個也可以寫作
//可讀性較差
void (*p) (int) = &example;
指針變量也是變量材诽,其實所有的指針都是整型底挫,08X打印出來都是8位16進制整數(shù)。
void ex1(int n)
{
printf(...);
}
void ex2(int n)
{
printf(...);
}
int main()
{
void (*p) (int);
//先指向ex1脸侥,再指向ex2
p = &ex1;
p = &ex2;
return 0;
}
與普通指針對比
//普通指針:用于讀寫目標內(nèi)存的值
int *p;
p = &a;
*p = 123;
//函數(shù)指針:用于調(diào)用目標函數(shù)
void (*p) (int);
p = &example;
p(123);
#include<stdio.h>
void example(int n)
{
printf("%d\n",n);
}
int main()
{
void (*p) (int) = &example;
p(1);
return 0;
}
注意
&可以舍去建邓,但是為了和普通變量形式上統(tǒng)一起來,最好還是加上
p = &example;
p = example
函數(shù)指針的使用
使用typedef可以替換掉void (*p) (int)睁枕,后者可讀性很差官边。
使用typedef給函數(shù)指針類型起個別名
#include<stdio.h>
void example(int n)
{
printf("%d\n",n);
}
typedef void (*MY_FUNCTION) (int);
int main()
{
MY_FUNCTION p;
p = &example;
p(1);
return 0;
}
函數(shù)指針可以作為函數(shù)的參數(shù)
#include<stdio.h>
void example(int n)
{
printf("%d\n",n);
}
typedef void (*MY_FUNCTION) (int);
void test(MY_FUNCTION f)
{
f(123);
}
int main()
{
test(&example);
//MY_FUNCTION p;
//p = &example;
//test(p);
return 0;
}
函數(shù)指針作為成員變量
class Object
{
public:
MY_FUNCTION m_func;
};
C語言里的回調(diào)機制
函數(shù)指針的應用場景:回調(diào)(callback)
我們調(diào)用別人提供的 API函數(shù)(Application Programming Interface,應用程序編程接口),稱為Call
如果別人的庫里面調(diào)用我們的函數(shù)外遇,就叫Callback
要拷貝一個文件注簿,將1.pdf拷貝為1_copy.pdf
方法:調(diào)用Windows API里面有一個CopyFile函數(shù),這種就叫調(diào)用Call
注意事先將項目的unicode字符集改為多字節(jié)字符集
#include<stdio.h>
#include<Windows.h>
int main()
{
const char* source = "D:\\Document\\1.pdf";
const char* dst = "D:\\Document\\1_copy.pdf";
BOOL result = CopyFile(source,dst,FALSE);
printf("操作完成:%s\n",result ? "success": "failed");
return 0;
}
何時需要Callback跳仿?
若拷貝一個很大的文件诡渴,這個拷貝過程需要很多時間,如果用CopyFile函數(shù)就需要默默等待菲语,用戶不知道要多久妄辩,而且也不能取消
用戶體驗差,缺少交互性
我們希望顯示拷貝的進度
比如我們提供一個函數(shù)
void CopyProgress(int total,int copied)
{
}
我們希望系統(tǒng)能時不時調(diào)用這個函數(shù)谨究,將total/copied數(shù)據(jù)通知給我們
這就要使用函數(shù)指針恩袱,將我們函數(shù)的地址作為一個參數(shù)傳給系統(tǒng)API即可
使用CopyFileEx(系統(tǒng)API的另一個函數(shù))
-
提供一個函數(shù)
DWORD CALLBACK CopyProgress(...)
-
將函數(shù)指針傳給CopyFileEx
CopyFileEx(source ,dst ,CopyProgress...) //每拷貝到一定的字節(jié)數(shù),就會調(diào)用到我們的函數(shù)
#include <stdio.h>
#include <Windows.h>
// 將LARGE_INTTEGER類型轉(zhuǎn)成unsigned long long
unsigned long long translate(LARGE_INTEGER num)
{
unsigned long long result = num.HighPart;
result <<= 32;
result += num.LowPart;
return result;
}
// 回調(diào)函數(shù)
// 注:要求將此函數(shù)用關鍵字CALLBACK修飾(這是Windows API的要求)
DWORD CALLBACK CopyProgress(
LARGE_INTEGER TotalFileSize,
LARGE_INTEGER TotalBytesTransferred,
LARGE_INTEGER StreamSize,
LARGE_INTEGER StreamBytesTransferred,
DWORD dwStreamNumber,
DWORD dwCallbackReason,
HANDLE hSourceFile,
HANDLE hDestinationFile,
LPVOID lpData)
{
// 文件的總字節(jié)數(shù) TotalFileSize
unsigned long long total = translate(TotalFileSize);
// 已經(jīng)完成的字節(jié)數(shù)
unsigned long long copied = translate(TotalBytesTransferred);
// 打印進度
printf("進度: %I64d / %I64d \n", copied, total); // 64位整數(shù)用 %I64d
//printf("進度: %d / %d \n", (int)copied, (int)total); // 文件大小于2G時胶哲,可以轉(zhuǎn)成int
return PROGRESS_CONTINUE;
}
int main()
{
const char* source = "D:\\Download\\1.Flv";
const char* dst = "D:\\Download\\1_copy.Flv";
printf("start copy ...\n");
// 將函數(shù)指針傳給CopyFileEx
BOOL result = CopyFileEx(source, dst, &CopyProgress, NULL, NULL, 0);
printf("operation done : %s \n", result ? "success" : "failed");
return 0;
}
回調(diào)函數(shù)的上下文
回調(diào)函數(shù)總有一個參數(shù)用于傳遞上下文信息畔塔,上下文:Context
比如
BOOL WINAPI CopyFileEx(
...
LPPROGRESS_ROUTINE lpProgressRoutine,//回調(diào)函數(shù)
LPVOID lpData, //上下文對象void*,只要是一個指針就行,不關心是什么類型的
...);
如果我們希望顯示[當前用戶]源文件->目標文件 :百分比
然而澈吨,上節(jié)代碼CopyProgress的參數(shù)里并沒有源文件名和目標文件名
也就是說只能計算百分比把敢,無法得知當前正在拷貝的是哪個文件
觀察里面有一個參數(shù)LPVOID lpData
上下文對象:攜帶了所有必要的上下文信息
可以定義為任意數(shù)據(jù),由用戶決定
比如
struct Context
{
char username[32],
char source[128],
char dst[128]
};
這樣就能顯示我們想要的了
#include <stdio.h>
#include <Windows.h>
// 文件拷貝所需的上下文信息
struct Context
{
char username[32];
char source[128];
char dst[128];
};
// 將LARGE_INTTEGER類型轉(zhuǎn)成unsigned long long
unsigned long long translate(LARGE_INTEGER num)
{
unsigned long long result = num.HighPart;
result <<= 32;
result += num.LowPart;
return result;
}
// 回調(diào)函數(shù)
// 注:要求將此函數(shù)用關鍵字CALLBACK修飾(這是Windows API的要求)
DWORD CALLBACK CopyProgress(
LARGE_INTEGER TotalFileSize,
LARGE_INTEGER TotalBytesTransferred,
LARGE_INTEGER StreamSize,
LARGE_INTEGER StreamBytesTransferred,
DWORD dwStreamNumber,
DWORD dwCallbackReason,
HANDLE hSourceFile,
HANDLE hDestinationFile,
LPVOID lpData) // <- 這個就是上下文件對象
{
// 計算百分比
unsigned long long total = translate(TotalFileSize);
unsigned long long copied = translate(TotalBytesTransferred);
int percent = (int) ( (copied * 100 / total) );
// 打印進度谅辣,將指針lpData強制轉(zhuǎn)為Context*類型
Context* ctx = (Context*) lpData;
printf("[用戶: %s], %s -> %s : 進度 %d %%\n",
ctx->username, ctx->source, ctx->dst, percent);
return PROGRESS_CONTINUE;
}
int main()
{
Context ctx; // 上下文對象
strcpy(ctx.username, "dada");
strcpy(ctx.source, "D:\\Download\\1.Flv" );
strcpy(ctx.dst, "D:\\Download\\1_copy.Flv");
printf("start copy ...\n");
// 將函數(shù)指針傳給CopyFileEx
BOOL result = CopyFileEx(ctx.source, ctx.dst,
&CopyProgress, // 待回調(diào)的函數(shù)
&ctx, // 上下文對象
NULL, 0);
printf("operation done : %s \n", result ? "success" : "failed");
return 0;
}
上下文對象為void*類型帅霜,他是透傳的(透明的饵逐,不關心類型與內(nèi)容)
C++里的回調(diào)實現(xiàn)
c++里用class語法來實現(xiàn)回調(diào),比如有人提供一個類庫AfCopyFile,能提供文件拷貝功能猪叙,而且能通知用戶當前進度
int DoCopy(const char* source, const char* dst,AfCopyFile* listener);
///別人提供的AfCopyFile.h
#ifndef _AF_COPY_FILE_H
#define _AF_COPY_FILE_H
class AfCopyFileListener
{
public:
virtual int OnCopyProgress(long long total, long long transfered) = 0;
};
class AfCopyFile
{
public:
int DoCopy(const char* source,
const char* dst,
AfCopyFileListener* listener);
};
#endif
用戶只要自己實現(xiàn)一個AfCopyFileListener對象序六,傳給這個函數(shù)就行了
#include <stdio.h>
#include <string.h>
#include "AfCopyFile.h"
class MainJob : public AfCopyFileListener
{
public:
// int DoJob()
// {
// strcpy(user, "shaofa");
// strcpy(source, "c:\\test\\2.rmvb" );
// strcpy(dst, "c:\\test\\2_copy.rmvb");
//
// AfCopyFile af;
// af.DoCopy(source, dst, this); // 將this傳過去
//
// return 0;
// }
int OnCopyProgress(long long total, long long transfered)
{
// 打印進度
int percent = (int) ( (transfered * 100 / total) );
printf("[用戶: %s], %s -> %s : 進度 %d %%\n",
user, source, dst, percent);
return 0;
}
public:
char source[256];
char dst[256];
char user[64];
};
int main()
{
MainJob job;
strcpy(job.user, "shaofa");
strcpy(job.source, "c:\\test\\2.rmvb" );
strcpy(job.dst, "c:\\test\\2_copy.rmvb");
AfCopyFile af;
af.DoCopy(job.source, job.dst, &job); // 將this傳過去
// job.DoJob();
return 0;
}
回調(diào)函數(shù)的缺點:使代碼變得難以閱讀逗旁,我們應該盡量避免使用回調(diào)機制立宜,最好采用單向的函數(shù)調(diào)用。