c语言字符集改为多字节,(三十六)函数指针与回调机制

函数指针

不只变量有地址,函数也有地址

void example(int n)

{

printf("%d\n",n);

}

int main()

{

//打印函数的地址

printf("%08X\n",&example);

//printf("%p\n",&example);

return 0;

}

每个函数在编译后都对应一串指令,这些指令在内存中的位置就是函数的地址

我们可以用一个指针类型来表示函数的地址

void (*p) (int);

//变量名为p,变量类型为函数指针,记作void (int)* ,返回值为void,参数为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进制整数。

void ex1(int n)

{

printf(...);

}

void ex2(int n)

{

printf(...);

}

int main()

{

void (*p) (int);

//先指向ex1,再指向ex2

p = &ex1;

p = &ex2;

return 0;

}

与普通指针对比

//普通指针:用于读写目标内存的值

int *p;

p = &a;

*p = 123;

//函数指针:用于调用目标函数

void (*p) (int);

p = &example;

p(123);

#include

void example(int n)

{

printf("%d\n",n);

}

int main()

{

void (*p) (int) = &example;

p(1);

return 0;

}

注意

&可以舍去,但是为了和普通变量形式上统一起来,最好还是加上

p = &example;

p = example

函数指针的使用

使用typedef可以替换掉void (*p) (int),后者可读性很差。

使用typedef给函数指针类型起个别名

#include

void example(int n)

{

printf("%d\n",n);

}

typedef void (*MY_FUNCTION) (int);

int main()

{

MY_FUNCTION p;

p = &example;

p(1);

return 0;

}

函数指针可以作为函数的参数

#include

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;

}

函数指针作为成员变量

class Object

{

public:

MY_FUNCTION m_func;

};

C语言里的回调机制

函数指针的应用场景:回调(callback)

我们调用别人提供的 API函数(Application Programming Interface,应用程序编程接口),称为Call

如果别人的库里面调用我们的函数,就叫Callback

要拷贝一个文件,将1.pdf拷贝为1_copy.pdf

方法:调用Windows API里面有一个CopyFile函数,这种就叫调用Call

注意事先将项目的unicode字符集改为多字节字符集

#include

#include

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函数就需要默默等待,用户不知道要多久,而且也不能取消

用户体验差,缺少交互性

我们希望显示拷贝的进度

比如我们提供一个函数

void CopyProgress(int total,int copied)

{

}

我们希望系统能时不时调用这个函数,将total/copied数据通知给我们

这就要使用函数指针,将我们函数的地址作为一个参数传给系统API即可

使用CopyFileEx(系统API的另一个函数)

提供一个函数

DWORD CALLBACK CopyProgress(...)

将函数指针传给CopyFileEx

CopyFileEx(source ,dst ,CopyProgress...)

//每拷贝到一定的字节数,就会调用到我们的函数

#include

#include

// 将LARGE_INTTEGER类型转成unsigned long long

unsigned long long translate(LARGE_INTEGER num)

{

unsigned long long result = num.HighPart;

result <<= 32;

result += num.LowPart;

return result;

}

// 回调函数

// 注:要求将此函数用关键字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)

{

// 文件的总字节数 TotalFileSize

unsigned long long total = translate(TotalFileSize);

// 已经完成的字节数

unsigned long long copied = translate(TotalBytesTransferred);

// 打印进度

printf("进度: %I64d / %I64d \n", copied, total); // 64位整数用 %I64d

//printf("进度: %d / %d \n", (int)copied, (int)total); // 文件大小于2G时,可以转成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");

// 将函数指针传给CopyFileEx

BOOL result = CopyFileEx(source, dst, &CopyProgress, NULL, NULL, 0);

printf("operation done : %s \n", result ? "success" : "failed");

return 0;

}

回调函数的上下文

回调函数总有一个参数用于传递上下文信息,上下文:Context

比如

BOOL WINAPI CopyFileEx(

...

LPPROGRESS_ROUTINE lpProgressRoutine,//回调函数

LPVOID lpData, //上下文对象void*,只要是一个指针就行,不关心是什么类型的

...);

如果我们希望显示[当前用户]源文件->目标文件 :百分比

然而,上节代码CopyProgress的参数里并没有源文件名和目标文件名

也就是说只能计算百分比,无法得知当前正在拷贝的是哪个文件

观察里面有一个参数LPVOID lpData

上下文对象:携带了所有必要的上下文信息

可以定义为任意数据,由用户决定

比如

struct Context

{

char username[32],

char source[128],

char dst[128]

};

这样就能显示我们想要的了

#include

#include

// 文件拷贝所需的上下文信息

struct Context

{

char username[32];

char source[128];

char dst[128];

};

// 将LARGE_INTTEGER类型转成unsigned long long

unsigned long long translate(LARGE_INTEGER num)

{

unsigned long long result = num.HighPart;

result <<= 32;

result += num.LowPart;

return result;

}

// 回调函数

// 注:要求将此函数用关键字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强制转为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");

// 将函数指针传给CopyFileEx

BOOL result = CopyFileEx(ctx.source, ctx.dst,

&CopyProgress, // 待回调的函数

&ctx, // 上下文对象

NULL, 0);

printf("operation done : %s \n", result ? "success" : "failed");

return 0;

}

上下文对象为void*类型,他是透传的(透明的,不关心类型与内容)

C++里的回调实现

c++里用class语法来实现回调,比如有人提供一个类库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

用户只要自己实现一个AfCopyFileListener对象,传给这个函数就行了

#include

#include

#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;

}

回调函数的缺点:使代码变得难以阅读,我们应该尽量避免使用回调机制,最好采用单向的函数调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值