当前位置:文档之家› 回调函数的使用

回调函数的使用

回调函数的使用
回调函数的使用

回调函数的使用

2011-01-07 14:50 1452人阅读评论(3) 收藏举报

callbackuserclass编程timer手机

0. 引言

使用过SDK的朋友应该知道“回调函数”(callback function)这个概念,但本文并不是介绍如何使用回调函数,而是站在SDK开发者的角度,讲述如何实现回调机制。

1. 何为回调(callback)

所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。例如Win32下的窗口过程函数就是一个典型的回调函数。

一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C 不得不提供。由于S并不知道C提供的B叫甚名谁,所以S会约定B的接口规范(函数原型),然后由C提前通过S的一个函数R告诉S自己将要使用B函数,这个过程称为回调函数的注册,R称为注册函数。

下面举个通俗的例子:

某天,我打电话向你请教问题,当然是个难题,:),你一时想不出解决方法,我又不能拿着电话在那里傻等,于是我们约定:等你想出办法后打手机通知我,这样,我就挂掉电话办其它事情去了。过了XX分钟,我的手机响了,你兴高采烈的说问题已经搞定,应该如此这般处理。故事到此结束。

这个例子说明了“异步+回调”的编程模式。其中,你后来打手机告诉我结果便是一个“回调”过程;我的手机号码必须在以前告诉你,这便是注册回调函数;我的手机号码应该有效并且手机能够接收到你的呼叫,这是回调函数必须符合接口规范。

2. 什么情况下使用回调

如果你是SDK的使用者,一旦别人制定了回调机制,那么你被迫得使用回调函数,因此这个问题只对SDK设计者有意义。

从引入的目的看,回调大致分为三种:

1) SDK有消息需要通知应用程序,比如定时器被触发;

2) SDK的执行需要应用程序的参与,比如SDK需要你提供一种排序算法;

3) SDK的操作比较费时,但又不能让应用程序阻塞在那里,于是采用异步方式,让调用函数及时返回,SDK另起线程在后台执行操作,待操作完成后再将结果通知应用程序。

经上面这样一总结,你也许会恍然大悟:原来“回调机制”无处不在啊!

是的,不光是Win32 API编程中你会用到,也不光是其它SDK编程中会用到,平时我们自己编写程序时也可能用到回调机制,这时,我们既是回调的设计者又是回调的使用者。

3. 传统SDK回调函数设计模式

Win32 SDK是这方面的典型例子,这类SDK的函数接口都是基于C语言的,SDK 或者提供专门的注册函数,用于注册回调函数的地址,或者是在调用某个方法时才传入回调函数的地址,回调函数的原型也由于注册函数中的函数指针定义而受到约束。

以Win32中的多媒体定时器函数为例,其原型为:

MMRESULT timeSetEvent(

UINT uDelay, // 定时器时间间隔,以毫秒为单

UINT uResolution,

LPTIMECALLBACK lpTimeProc, // 回调函数地址

DWORD dwUser, // 用户设定的数据

UINT fuEvent

);

其中第三个参数便是用于注册回调函数的,第四个参数用于设定用户自定义数据,其作用将在后文说明,LPTIMECALLBACK的定义为:

typedef void (CALLBACK TIMECALLBACK)(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);

typedef TIMECALLBACK FAR *LPTIMECALLBACK;

因此,用户定义的回调函数必须具有上面指定的函数原型,下面是回调函数的具体使用方法:

#include "stdio.h"

#include "windows.h"

#include "mmsystem.h" // 多媒体定时器需要包含此文件

#pragma comment(lib, "winmm.lib") // 多媒体定时器需要导入此库

void CALLBACK timer_proc(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) // 定义回调函数

{

printf("time out./n");

}

int main()

{

UINT nId = timeSetEvent( 1000, 0, timer_proc, 0,

TIME_CALLBACK_FUNCTION|TIME_PERIODIC); // 注册回调函数

getchar();

timeKillEvent( nId );

return 0;

}

运行程序,我们会看到,屏幕上每隔一秒将会打印出“time out.”信息。同时我们也应该注意到,这里的timeSetEvent是异步执行的,timeSetEvent很快执行完毕,主线程继续执行,操作系统在后台负责检查timer是否超时。

前面已经说过,本文的是站在SDK设计者的角度来看待问题,这里,我们就把前面那个通俗的例子变成程序,如下:

/// sdk.h (SDK头文件)

#ifndef __SDK_H__

#define __SDK_H__

typedef void (*HELP_CALLBACK)(const char*); // 回调函数指针

void help_me( const char* question, HELP_CALLBACK callback ); // 接口声明

#endif//__SDK_H__

/// sdk.cpp (SDK源文件,为方便,没有使用.c文件)

#include "sdk.h"

#include "stdio.h"

#include "windows.h"

HELP_CALLBACK g_callback;

void do_it() // 处理函数

{

printf("thinking.../n");

Sleep( 3000 );

printf("think out./n");

printf("call him./n");

g_callback( "2." );

}

void help_me( const char* question, HELP_CALLBACK callback ) // 接口实现

{

g_callback = callback; // 保存回调函数指针

printf("help_me: %s/n", question);

do_it(); // 如果采用异步方式的话,这里一般采用创建线程的方式

}

/// app.cpp (应用程序源文件)

#include "sdk.h"

#include "stdio.h"

void got_answer( const char* msg ) // 定义回调函数

{

printf("got_answer: %s/n", msg);

}

int main()

{

help_me( "1+1=?", got_answer ); // 使用SDK,注册回调函数

return 0;

}

4. C++中回调函数的设计

C++的类中也可以使用类似上面的设计方式。

如果SDK采用C语言接口,应用程序使用C++编程方式,那么类成员函数由于具有隐含的this指针而不能赋值给普通函数指针,解决方法很简单,就是为其加上static关键字。

以上面的程序为例,这里我们只看应用程序的代码:

/// app.cpp ( C++风格 )

#include "sdk.h"

#include "stdio.h"

class App

{

public:

void ask( const char* question )

{

help_me( question, got_answer );

}

static void got_answer( const char* msg )

{

printf("got_answer: %s/n", msg);

}

};

int main()

{

App app;

app.ask( "1+1=?");

return 0;

}

上面这种方式有个明显的缺点:由于got_answer是静态成员函数,所以它不能访问类的非静态成员变量,这可不是一件好事情。为了解决此问题,作为回调函数的设计者,你有必要为其增添一个参数,用于传递用户所需的值,如下:/// sdk.h (SDK头文件)

#ifndef __SDK_H__

#define __SDK_H__

typedef void (*HELP_CALLBACK)(const char*, unsigned long); // 回调函数指针

void help_me( const char* question, HELP_CALLBACK callback, unsigned long user_value ); // 接口声明

#endif//__SDK_H__

/// sdk.cpp (SDK源文件,为方便,没有使用.c文件)

#include "sdk.h"

#include "stdio.h"

#include "windows.h"

HELP_CALLBACK g_callback;

unsigned long g_user_value;

void do_it()

{

printf("thinking.../n");

Sleep( 3000 );

printf("think out./n");

printf("call him./n");

g_callback( "2.", g_user_value ); // 将用户设定的数据传入

}

void help_me( const char* question, HELP_CALLBACK callback, unsigned long user_value )

{

g_callback = callback;

g_user_value = user_value; // 保存用户设定的数据

printf("help_me: %s/n", question);

do_it();

}

/// app.cpp (应用程序源文件)

#include "sdk.h"

#include "stdio.h"

#include "assert.h"

class App

{

public:

App( const char* name ) : m_name(name)

{

}

void ask( const char* question )

{

help_me( question, got_answer, (unsigned long)this ); // 将this

}

static void got_answer( const char* msg, unsigned long user_value ) {

App* pthis = (App*)user_value; // 转换成this指针,以访问非静态数据成员

assert( pthis );

printf("%s got_answer: %s/n", pthis->m_name, msg);

}

protected:

const char* m_name;

};

int main()

{

App app("ABC");

app.ask( "1+1=?");

return 0;

}

这里的user_value被设计成unsigned long,它既可以传递整型数据,也可以传递一个地址值(因为它们在32位机器上宽度都为32),有了地址,那么像结构体变量、类对象等都可以访问了。

5. C++中回调类编程模式

时代在不断进步,SDK不再是古老的API接口,C++面向对象编程被广泛的用到各种库中,因此回调机制也可以采用C++的一些特性来实现。

通过前面的讲解,其实我们不难发现回调的本质便是:SDK定义出一套接口规范,应用程序按照规定实现它。这样一说是不是很简单,

想想我们C++中的继承,想想我们亲爱的抽象基类......于是,我们得到以下的代码:

/// sdk.h

#ifndef __SDK_H__

#define __SDK_H__

class Notifier // 回调类,应用程序需从此派生

{

public:

virtual ~Notifier() { }

virtual void got_answer(const char* answer) = 0; // 纯虚函数,用户必须实现它

};

class Sdk // Sdk提供服务的类

{

Sdk(Notifier* pnotifier); // 用户必须注册指向回调类的指针

void help_me(const char* question);

protected:

void do_it();

protected:

Notifier* m_pnotifier; // 用于保存回调类的指针

};

#define//__SDK_H__

/// sdk.cpp

#include "sdk.h"

#include "windows.h"

#include

using namespace std;

Sdk::Sdk(Notifier* pnotifier) : m_pnotifier(pnotifier)

{

}

voidSdk::help_me(const char* question)

{

cout<< "help_me: " << question <

do_it();

}

voidSdk::do_it()

{

cout<< "thinking..." <

Sleep( 3000 );

cout<< "think out." <

cout<< "call him." <

m_pnotifier->got_answer( "2." );

}

/// app.cpp

#include "sdk.h"

class App : public Notifier // 应用程序实现一个从回调类派生的类

{

public:

App( const char* name ) : m_sdk(this), m_name(name) // 将this指针传入

{

}

void ask( const char* question )

{

m_sdk.help_me( question );

}

void got_answer( const char* answer ) // 实现纯虚接口

{

cout<

}

protected:

Sdkm_sdk;

const char* m_name;

};

int main()

{

App app("ABC");

app.ask( "1+1=?");

return 0;

}

哈哈,是不是很爽?Notifier将用户必须实现的回调函数都以纯虚函数的方式定义,这样用户就不得不实现它,当然如果不是非实现不可,那么我们也可以将其定义成一般的虚函数,并像析构函数那样提供一个“空”的实现,这样用户可以在关心它时才去实现它。由于这个类的作用是实现回调,所以我们不妨称之为回调类。

上面这种方式一简化,可以将Sdk与Notifier合并在一起,@#%&@#...,Stop,这样的代码每本C++书上都有,还用的着说吗(不要砸偶,鸡蛋可以,西红柿不要)。

6. C++类模板方式

上面的方式有继承还有虚函数,某些朋友可能不喜欢其中带来的额外代价,那么怎么才能消除这二者呢?本节的标题已给出了答案——类模板,代码为:

/// sdk.h

#ifndef __SDK_H__

#define __SDK_H__

#include

using namespace std;

template

class Sdk

{

public:

Sdk(Notifier* pnotifier);

void help_me(const char* question);

protected:

void do_it();

protected:

Notifier* m_pnotifier;

};

template

Sdk::Sdk(Notifier* pnotifier) : m_pnotifier(pnotifier) {

}

template

void Sdk::help_me(const char* question)

{

cout<< "help_me: " << question <

do_it();

}

template

void Sdk::do_it()

{

cout<< "thinking..." <

Sleep( 3000 );

cout<< "think out." <

cout<< "call him." <

m_pnotifier->got_answer( "2." );

}

#endif//__SDK_H__

/// app.cpp

#include "sdk.h"

class App

{

public:

App( const char* name ) : m_sdk(this), m_name(name)

{

}

void ask( const char* question )

{

m_sdk.help_me( question );

}

void got_answer( const char* answer )

{

cout<

}

protected:

Sdkm_sdk;

const char* m_name;

};

int main()

{

App app("ABC");

app.ask( "1+1=?");

return 0;

}

由于现在很少有编译器支持模板的分离编译,所以迫使我们不得不把SDK的实现也放在头文件中,这对于想要隐藏SDK具体实现的朋友来说可不是一个好的选择。因此这种方式在SDK中还是用得较少。

7. 尾声

知识是灵活运用的,根据具体的需求,可以设计出各种精彩的模式,并不一一雷同。

关于回调的总结就暂告一段落,等以后想到新的方式再作补充。

其实,你如果用MFC编写应用程序的话,根本不许要显式调用回调函数.但是如果用SDK的话就要显式调用回调函数.调用方法是:在WinMain函数之前声明回调函数

如.然后在初始化窗口类中指明对象的成员的回调函数是你之前声明的即可.而后在消息循环之后,在实现达即可.

一般的函数,也可以在“恰当的时机”被调用。并不是说,只要这个函数的调用是有时机的,它就被称为回调函数。

通常模块有上层模块和下层模块之分,上层模块可以依赖下层模块(实际表现是,上层模块直接调用下层模块的函数),而下层模块不会依赖上层模块(如果存在双向依赖,则它们实际上是一个模块)。但是确实有某种情形,下层模块需要使用上层模块的逻辑(调用上层模块的函数),这就是所谓的“回调”。

为了去除下层模块对上层模块的依赖,引入了函数指针,因此回调函数通常就是指通过函数指针调用的函数。

举个实际的例子,各种图形库,比如MFC,在按键消息来的时候,需要调用应用程序的函数来处理消息。但是MFC不可能和应用程序一起发布啊?只能是先发布MFC这个产品,再在上头开发应用程序。也就是说,MFC不能依赖应用程序(只能是应用程序依赖MFC)。

那么MFC怎么调用应用程序的函数?它提供了一个注册函数指针的接口(自动生成的代码隐藏了这部分逻辑),它用函数指针维护这个注册进来的函数,并在适当的时机(消息产生时)通过函数指针来调用这个函数。

这就是回调函数典型的应用场景。

你提到的_beginthreadex()本质上也是这种场景。新建的线程需要执行应用程序的逻辑,但实现这个函数的库(windows的系统库)不可能依赖应用程序,所以提供一个参数,允许用户传入自己的函数(函数名称代表了函数地址),操作系统会将用户的函数维护在函数指针中,新的线程在开始运行的时候,通过函数指针调用用户的函数。

所以,本质就是,为了避免因为直接调用而引起的链接要求,才需要通过函数指针调用。

而事实上,底层模块通常只有在特定的时机才需要调用上层模块的函数,实现定制的逻辑。因此,回调函数的调用总是有时机的,比如消息的通知等。

回调函数

对于很多初学者来说,往往觉得回调函数很神秘,很想知道回调函数的工作原理。本文将要解释什么是回调函数、它们有什么好处、为什么要使用它们等等问题,在开始之前,假设你已经熟知了函数指针。 什么是回调函数? 简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。 为什么要使用回调函数? 因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。 如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。 回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer() API使用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。 另一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上所有的顶层窗口,为每个窗口调用一个程序提供的函数,并传递窗口的处理程序。如果被调用者返回一个值,就继续进行迭代,否则,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。 不管怎么说,回调函数是继续自C语言的,因而,在C++中,应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。 一个简单的回调函数实现 下面创建了一个sort.dll的动态链接库,它导出了一个名为CompareFunction的类型--typedef int (__stdcall *CompareFunction)(const byte*, const byte*),它就是回调函数的类型。另外,它也导出了两个方法:Bubblesort()和Quicksort(),这两个方法原型相同,但实现了不同的排序算法。

java基础知识

一.set,list 区别 List和Set都是接口。他们各自有自己的实现类, 有无顺序的实现类,也有有顺序的实现类。 最大的不同就是List是可以重复的。而Set是不能重复的。 List适合经常追加数据,插入,删除数据。但随即取数效率比较低。 Set适合经常地随即储存,插入,删除。但是在遍历时效率比较低。 二.ArrayList与LinkedList区别。 List: 有顺序的,元素可以重复 遍历:for 迭代 排序:Comparable Comparator Collections.sort() ArrayList:底层用数组实现的List 特点:查询效率高,增删效率低轻量级线程不安全 遍历: ArrayList al=new ArrayList(); al.add("winsun"); al.add("weixin"); al.add("mybole"); for(int i=0;i

回调函数与回调机制

回调函数与回调机制 1. 什么是回调函数 回调函数(callback Function),顾名思义,用于回调的函数。回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机。回调函数包含下面几个特性: ?属于工作流的一个部分; ?必须按照工作流指定的调用约定来申明(定义); ?他的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能; 2. 回调机制 回调机制是一种常见的设计模型,他把工作流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。 如上图所示,工作流提供了两个对外接口(获取参数、显示结果),以回调函数的形式实现。 ?“获取参数”回调函数,需要工作流使用者设定工作流计算需要的参数。 ?“显示结果”回调函数,提供计算结果给工作流使用者。

再以Windows的枚举顶级窗体为例。函数EnumWindows用于枚举当前系统中的所有顶级窗口,其函数原型为: BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value ); 其中lpEnumFunc是一个回调函数,他用于返回枚举过程中的获得的窗口的句柄。其定义约定为: BOOL CALLBACK EnumWindowsProc( HWND hwnd, // handle to parent window LPARAM lParam // application-defined value ); 在这个例子中,EnumWindows 是一个工作流,这个工作流用于遍历windows的所有窗口并获得其句柄。用户使用EnumWindows工作流的目的是想通过工作流来来获取窗口的句柄以便针对特定的一个或多个窗口进行相关处理。于是EnumWindows就扩展出接口lpEnumFunc,用于返回遍历的窗口句柄。 EnumWindows工作流的结束有两个方式:1,用户在回调函数中返回FALSE;2,再也找不到顶级窗口。我们可以推测EnumWindows的实现机制如下: 注:下列代码中的FindFirstTopWindows(), FindNextTopWindow()为假设的,Windows API 没有此函数,只是为了表明Enumwindows的内部流程。 BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value ) { BOOL bRet = TRUE; HWND hWnd = ::FindFirstTopWindows(); // 此函数是假设的,查找第一个顶级窗口 // 当hWnd为0时表示再也找不到顶级窗口 while( hWnd ) { bRet = (*lpEnumFunc)( hWnd, value ); if( !bRet) break; // 终止EnumWindows工作流; hWnd = ::FindNextWindow(); // 此函数是假设的,查找下一个顶级窗口 } } 在EnumWindows(...)函数中,实现了窗口枚举的工作流,他通过回调机制把用户关心(顶级窗口句柄)的和枚举工作流分开,用户不需要知道EnumWindows的具体实现,用户只要知道,设定了lpEnumFunc函数,然后把函数指针传给EnumWindwos就可以获得想要的窗口句柄。

hook的使用实例

在网上找了好久都没有找到消息hook的实例,下面是我的例子给大家分享一下 下面是dll中的代码: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //我的经验,编译的时候会提示DllMain,已在DllMain.cpp中定义,把DllMain.cpp从源文件里删掉就好了 #include "stdafx.h" #include HHOOK hkey=NULL; HINSTANCE h_dll; #pragma data_seg(".MySec") //定义字段,段名.MySec HWND h_wnd=NULL; #pragma data_seg() #pragma comment(linker,"/section:.MySec,RWS") BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved) { h_dll=hinstDLL; // MessageBox(0,"运行dllman","",MB_OK); return TRUE; } LRESULT CALLBACK my_test(int nCode,WPARAM wParam,LPARAM iParam)// { /* if(nCode==HC_ACTION) { MessageBox(0,"成功!!","标题",MB_OK); } else { MessageBox(0,"失败!!","标题",MB_OK); } */ MessageBox(0,"被截取","",MB_OK); UnhookWindowsHookEx(hkey); return 1; } void SetHook(HWND hwnd) { h_wnd = hwnd; // MessageBox(0,"运行sethook","",MB_OK); hkey=SetWindowsHookEx(WH_KEYBOARD,my_test,h_dll,0); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 下面是EXE的代码:有很多头文件是没用上的,我个人习惯都带着- -,虽然这不是好习惯

Java中集合类用法总结

帮助 | 留言交? | 登录 首页我的图书馆主题阅读精彩目录精品文苑Tags 会员浏览好书推荐 以文找文 如何对文章标记,添加批注? Java 中集合?用法总结(转载) wade0564 收录于2010-07-08 阅读数:查看 收藏数:7 公众公开 原文来源 tags : java 集合类 欢迎浏览 wade0564 个人图书馆中收藏的文章,想收藏这篇好文章吗,赶快 吧,1分钟拥有自己的个人图书馆! 我也要收藏 举报 Java 中集合?用法总结 收藏 Collection ├List │├LinkedList │├ArrayList (异步,线程不安全,空间用完时自动增长原容量一半)│└Vector (同 步,线程安全,空间用完时自动增长原容量一倍)│ └Stack └Set ├HashSet └TreeSet Map ├Hashtable ├HashMap ├WeakHashMap └TreeMap Map 接口: | + -- WeakHashMap: 以弱键 实现的基于哈希表的 Map 。在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条 | 目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为 可终止的,被终 | 止,然后被回收。丢弃某个键时, 其条目从映射中有效地移除,因此,该类的行为与其他的 Map 实现有所不同。此实现 | 不是同步的。 | + -- TreeMap:该映射根据其键的自然顺序进行 排序,或?根据创建映射时提供的 Comparator 进行 排序,具体取决于使用的 | 构造方法。此实现不是同步的。 | + -- HashMap:基于哈希表的 Map 接?的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了 | 非同步和允许 使用 null 之外,HashMap 类与 Hashtable ?致相同。)此类不保证映射的顺序,特别是它不保证该顺 | 序恒久不变。此实现不是同步的。 | +-- SortedMap: 进一步提供关于键的总体排序 的 Map 。该映射是根据其键的自然顺序进 行排序的,或?根据通常在创建有 序映射时提供的 Comparator 进行排序。对有序映射的 collection 视图(由 entrySet 、keySet 和 values 方法返回 )进行迭代时,此顺序就会反映 出来。要采用此排序方式,还需要提供一些其他操作(此接?是 SortedSet 的对应映 射)。 Collection 接口: | 热点推荐 中国经典汤品——广东汤常用多音字汇总 如果你失恋。。。这些话...影响世界的100个管理定律汽车发动机?作过程和原理分析温家宝总理答中外记?问女人味,有多少男人可以读懂?珍稀的白头叶猴(组图)三鹿门事件之——中国,...国家公务员职务与级别当代古筝四美 付娜《渔...生活?秘方 真的很实用...哲理?品:守护梦想聚会时可以玩的?游戏依赖型人格障碍的表现和治疗经典妙语,十分精彩江边施救[贴图]李一男2003年在港湾...电脑速度慢的解决方法 ...重装系统后必须做的10件?事

关于回调函数的几个例子(c)

以下是一个简单的例子。实现了一个repeat_three_times函数,可以把调用者传来的任何回调函数连续执行三次。 例 1. 回调函数 /* para_callback.h */ #ifndef PARA_CALLBACK_H #define PARA_CALLBACK_H typedef void (*callback_t)(void *); extern void repeat_three_times(callback_t, void *); #endif /* para_callback.c */ #include "para_callback.h" void repeat_three_times(callback_t f, void *para) { f(para); f(para); f(para); } /* main.c */ #include #include "para_callback.h" void say_hello(void *str) { printf("Hello %s\n", (const char *)str); } void count_numbers(void *num) { int i; for(i=1; i<=(int)num; i++) printf("%d ", i); putchar('\n');

} int main(void) { repeat_three_times(say_hello, "Guys"); repeat_three_times(count_numbers, (void *)4); return 0; } 回顾一下前面几节的例子,参数类型都是由实现者规定的。而本例中回调函数的参数按什么类型解释由调用者规定,对于实现者来说就是一个void *指针,实现者只负责将这个指针转交给回调函数,而不关心它到底指向什么数据类型。调用者知道自己传的参数是char *型的,那么在自己提供的回调函数中就应该知道参数要转换成char *型来解释。 回调函数的一个典型应用就是实现类似C++的泛型算法(Generics Algorithm)。下面实现的max函数可以在任意一组对象中找出最大值,可以是一组int、一组char或者一组结构体,但是实现者并不知道怎样去比较两个对象的大小,调用者需要提供一个做比较操作的回调函数。 例 2. 泛型算法 /* generics.h */ #ifndef GENERICS_H #define GENERICS_H typedef int (*cmp_t)(void *, void *); extern void *max(void *data[], int num, cmp_t cmp); #endif /* generics.c */ #include "generics.h" void *max(void *data[], int num, cmp_t cmp) { int i; void *temp = data[0];

OpenGL一个简单的例子

先编译运行一个简单的例子,这样我们可以有一个直观的印象。从这个例子我们可以看到OpenGL可以做什么,当然这个例子只做了很简单的一件事--绘制一个彩色的三角形。除此以外,我们还可以看到典型的OpenGL程序结构及openGL的运行顺序。 例1:本例在黑色的背景下绘制一个彩色的三角形,如图一所示。

图一:一个彩色的三角形首先创建工程,其步骤如下:

1)创建一个Win32 Console Application。 2)链接OpenGL libraries。在Visual C++中先单击Project,再单击Settings,再找到Link单击,最后在Object/library modules 的最前面加上OpenGL32.lib GLu32.lib GLaux.lib 3)单击Project Settings中的C/C++标签,将Preprocessor definitions 中的_CONSOLE改为__WINDOWS。最后单击OK。 现在你可以把下面的例子拷贝到工程中去,编译运行。你可以看到一个彩色的三角形。 我们先看看main函数。函数中以glut开头的函数都包含在glut.h中。GLUT库的函数主要执行如处理多窗口绘制、处理回调驱动事件、生成层叠式弹出菜单、绘制位图字体和笔画字体,以及各种窗口管理等任务。 ·glutInit用来初始化GLUT库并同窗口系统对话协商。 ·glutInitDisplayMode用来确定所创建窗口的显示模式。本例中的参数GLUT_SINGLE 指定单缓存窗口,这也是缺省模式,对应的模式为GLUT_DOUBLE 双缓存窗口。参数GLUT_RGB指定颜色RGBA模式,这也是缺省模式,对应的模式为GLUT_INDEX 颜色索引模式窗口。 ·glutInitWindowSize初始化窗口的大小,第一个参数为窗口的宽度,第二个参数为窗口的高度,以像素为单位。 ·glutInitWindowPosition设置初始窗口的位置,第一个参数为窗口左上角x的坐标,第二个参数为窗口左上角y的坐标,以像素为单位。屏幕的左上角的坐标为(0,0),横坐标向右逐渐增加,纵坐标向下逐渐增加。 ·glutCreateWindow创建顶层窗口,窗口的名字为扩号中的参数。 ·background() 这是自己写的函数,设置背景。其实这个函数中的语句可以写在display 函数中,但为了使功能块更加清晰,所以把背景这一部分单独提出来。 ·glutReshapeFunc注册当前窗口的形状变化回调函数。当改变窗口大小时,该窗口的形状改变回调函数将被调用。在此例中就是myReshape指定形状变化函数。 ·glutDisplayFunc注册当前窗口的显示回调函数。当一个窗口的图像层需要重新绘制时,GLUT将调用该窗口的的显示回调函数。在此例中的mydisplay就是显示回调函数,显示回调函数不带任何参数,它负责整个图像层的绘制。我们的大部分工作将集中在这个函数中。 ·glutMainLoop进入GLUT事件处理循环。glutMainLoop函数在GLUT程序中最多只能调用一次,它一旦被调用就不再返回,并且调用注册过的回调函数。所以这个函数必须放在注册回调函数的后面,此例中为glutReshapeFunc,glutDisplayFunc。

java之public class和class声明区别详解

java之public class和class声明区别详解(转) 在编写类的时候可以使用两种方式定义类: public class定义类: class定义类: 如果一个类声明的时候使用了public class进行了声明,则类名称必须与文件名称完全一致。 范例:定义一个类(文件名称为:Hello.java) public class HelloDemo{ //声明一个类,类名称的命名规范:所有单词的首字母大写public static void main(String args[]){ //主方法 System.out.println("Hello World!!!"); //系统输出,在屏幕上打印 } }; 此类使用public class声明,类名称是Hello Demo,但是文件名称Hello.java,所以,此时编译时会出现如下问题: Hello.java:1 类HelloDemo 是公共的,应在名为HelloDemo.java文件中声明 public class HelloDemo{ //声明一个类,类名称的命名规范:所有单词首字母大写 1、错误 以上的错误提示表示:因为使用的是public class声明,所以类名称应该与文件名称完全一致,即应该使用"HelloDemo.java"表示类的名称。 如果类的声明使用了class的话,则类名称可以与文件名称不一致,但是执行的时候肯定执行的是生成后的名称。 范例:有如下代码(文件名称为:Hello.java) class HelloDemo{ public static void main(String args[]){ System.out.println("Hello World!!!"); } }; 文件名称为Hello.java,文件名称与类名称不一致,但是因为使用了class声明所以,此时编译不会产生任何错误,但是生成之后的*.class文件的名称是和class声明的类名称完全一

OracleAQ消息队列的使用详解

Oracle AQ 消息队列(-) 随着不同应用模块间的消息交互和通信成为一个关键的功能,并且变得越来越重要。Oracle 引入了一种强大的队列机制,通过它程序间可以实现信息的交互,oracle把它称作为AQ - Advanced Queuing. 使用Oracle AQ,我们不需要安装额外的中间件,它是Oracle数据库的一个功能组件,只要你安装了Oracle 数据库就可以使用AQ了。接下来分两部分来介绍AQ的使用,使用之前我们要创建QUEUE. 我们创建一个自己的AQ的管理角色"my_aq_adm_role" 和管理用户"aqadm",再把Oracle AQ 管理角色"aq_adminstrator_role" 授权给"my_aq_adm_role". CREATE ROLE my_aq_adm_role; GRANT aq_adminsistator_role TO my_aq_adm_role 创建一个用户的角色"my_aq_user_role" 和普通用户"aquser" ,再把Oracle AQ的用户角色"aq_user_role"和一些基本操作需要的系统权限授权给"my_aq_adm_role" CREATE ROLE my_aq_user_role; GRANT CREATE session, aq_user_role TO my_aq_user_role; EXEC DBMS_AQADM.GRANT_SYSTEM_PRIVILEGE( privilege => 'ENQUEUE_ANY', grantee => 'my_aq_user_role', admin_option => FALSE); EXEC DBMS_AQADM.GRANT_SYSTEM_PRIVILEGE( privilege => 'DEQUEUE_ANY', grantee => 'my_aq_user_role', admin_option = 'FALSE'); 现在我们创建AQ管理用户

Matlab的GUI回调函数

Kinds of Callbacks The following table lists the callback properties that are available, their triggering events, and the components to which they apply. Note:User interface controls include push buttons, sliders, radio buttons, check boxes, editable text boxes, static text boxes, list boxes, and toggle buttons. They are sometimes referred to as uicontrols.

GUIDE Callback Arguments All callbacks in a GUIDE-generated GUI code file have the following standard input arguments: hObject —Handle of the object, e.g., the GUI component, for which the callback was triggered. For a button group SelectionChangeFcn callback, hObject is the handle of the selected radio button or toggle button. eventdata — Sequences of events triggered by user actions such as table selections emitted by a component in the form of a MATLAB struct (or an empty matrix for components that do not generate eventdata) handles — A MATLAB struct that contains the handles of all the objects in the GUI, and may also contain application-defined data. See handles Structure for information about this structure. Object Handle The first argument is the handle of the component issuing the callback. Use it to obtain relevant properties that the callback code uses and change them as necessary. For example, theText = get(hObject,'String'); places the String property (which might be the contents of static text or name of a button) into the local variable theText. You can change the property by setting it, for example set(hObject,'String',date) This particular code changes the text of the object to display the current date. Event Data Event data is a stream of data describing user gestures, such as key presses, scroll wheel movements, and mouse drags. The auto-generated callbacks of GUIDE GUIs can access event data for Handle Graphics? and uicontrol and uitable object callbacks. The following ones receive event data when triggered: CellEditCallback in a uitable CellSelectionCallback in a uitable KeyPressFcn in uicontrols and figures KeyReleaseFcn in a figure SelectionChangeFcn in a uibuttongroup WindowKeyPressFcn in a figure or any of its child objects WindowKeyReleaseFcn in a figure or any of its child objects WindowScrollWheelFcn in a figure Event data is passed to GUIDE-generated callbacks as the second of three standard arguments. For components that issue no event data the argument is empty. For those that provide event data, the argument contains a structure, which varies in composition according to the component that generates it and the type of event. For example, the event data for a key-press provides information on the key(s) currently being pressed. Here is a GUIDE-generated KeyPressFcn callback template: % --- Executes on key press with focus on checkbox1 and none of its controls. function checkbox1_KeyPressFcn(hObject, eventdata, handles)

java类与对象

5.2.1 编程实验1:基本类定义 本实验要求在封闭实验课中在教师指导下完成。 1. 实验目的 本实验旨在巩固学生对《Java语言程序设计》中第5章内容的掌握。在这个实验中学生将练习: ? 基本类的定义。 ? 使用自定义的基本类创建对象,并进行调用。 在强化练习中,学生将练习: ? 给基本类中增加属性 ? 在测试类中将增加的属性向控制台打印输出 2. 问题描述 编写一个类,描述学生的学号、姓名、成绩。学号用整型,成绩用浮点型,姓名用String类型。 编写一个测试类,输入学生的学号和成绩,并显示该学号的学生姓名,以及成绩。 3. 示例输出 4. 程序模板 class Student{ /* 此处创建三个属性。 */ } public class T1_Student{ public static void main(String[] args){ /* 先构造Student对象,然后分别为对象的属性赋值 最后打印输出对象的各个属性。 */ } } 5. 解答提示 1)在基本类的定义中描述学生类,学生的学号、成绩和姓名作为学生类的属性来描述。2)在测试类中写main方法,作为程序的入口进行运行,在main方法中创建学生对象,并

给对象的各个属性赋予具体值。 3)在测试类中用System.out.println()方法将属性的具体值输出到控制台,完成程序的输出结果要求。 6. 强化练习 1)给学生类添加性别属性,取值为boolean类型,用true表示男,用false表示女。 2)在测试类中输出学生的姓名、学号、成绩的同时,输出性别为:男或女。 5.2.2 编程实验2:构造方法 本实验要求在封闭实验课中在教师指导下完成。 1. 实验目的 本实验旨在巩固学生对《Java语言程序设计》中第5章内容的掌握。在这个实验中学生将练习: ? 定义多个基本类 ? 构造方法的定义 ? 构造方法的重载 在强化练习中,学生将练习: ? 构造方法的多个重载 ? 只通过指定长和宽来定制桌子。 2. 问题描述 编写一个类,描述桌子,包括以下几种属性:长、宽、高、颜色。并且使该类具有这样的功能:在定制桌子(即创建桌子对象时),就可以同时指定桌子的长宽高来订制。也可以同时指定长、宽、高、颜色来订制,也可单独指定桌子颜色来订制。 并编写一个测试类测试这几种定制方法。 5.2.3 编程实验3:访问器方法 本实验要求在封闭实验课中在教师指导下完成。 1. 实验目的 本实验旨在巩固学生对《Java语言程序设计》中第5章内容的掌握。在这个实验中学生将练习: ? 编写基本类,包括私有属性 ? 给基本类添加访问器方法,从而限制属性的读、写。 ? 测试类的编写,测试访问器方法对属性的读、写限制。 在强化练习中,学生将练习: ? 在基本类中添加私有属性。 ? 给该私有属性分别添加get和set访问器方法进行读、写。 2. 问题描述 编写一个类,描述银行账户,包括收入、支出和账户余额三种属性,同时包括对这三种

数据结构(Java版)(第3版)叶核亚 答案之用队列编迷宫(部分1)

//用队列: public class Maze { public int[][] maze = null; public int[] xx = { 1, 0, -1, 0 }; public int[] yy = { 0, 1, 0, -1 }; public Queue queue = null; public Maze(int[][] maze) { this.maze = maze; queue = new Queue(maze.length * maze.length); } public void go() { Point outPt = new Point(4, 5); Point curPt = new Point(0, 0); Node curNode = new Node(curPt, null); maze[curPt.x][curPt.y] = 2; queue.entryQ(curNode); while (!queue.isEmpty()) { curNode = queue.outQ(); for (int i = 0; i < xx.length; ++i) { Point nextPt = new Point(); nextPt.x = (curNode.point).x + xx[i]; nextPt.y = (curNode.point).y + yy[i]; if (check(nextPt)) { Node nextNode = new Node(nextPt, curNode); queue.entryQ(nextNode); maze[nextPt.x][nextPt.y] = 2; if (nextPt.equals(outPt)) { //判断是否到达终点 Queue storeQueue=new Queue(maze.length * maze.length); storeQueue.entryQ(nextNode); while ((curNode = nextNode.previous) != null) { nextNode = curNode; storeQueue.entryQ(curNode); } System.out.println("A Path is:"); while (!storeQueue.isEmpty()) { curNode = storeQueue.outQ(); //出栈 System.out.println(curNode.point); } return; } } } } System.out.println("Non solution!");

WH_CBT回调函数

CBTProc Callback Function An application-defined or library-defined callback function used with the SetWindowsHookEx function. The system calls this function before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the keyboard focus; or before synchronizing with the system message queue. A computer-based training (CBT) application uses this hook procedure to receive useful notifications from the system. The HOOKPROC type defines a pointer to this callback function. CBTProc is a placeholder for the application-defined or library-defined function name. Syntax 复制 LRESULT CALLBACK CBTProc( __in int nCode, __in WPARAM wParam, __in LPARAM lParam ); Parameters nCode [in] Type: int The code that the hook procedure uses to determine how to process the message. If nCode is less than zero, the hook procedure must pass the message to the CallNextHookEx function without further processing and should return the value returned by CallNextHookEx. This parameter can be one of the following values.

JAVA类与对象的创建

试验四 类和对象的创建 一.类的定义 类是组成Java程序的基本要素。类封装了一类对象的状态和方法。类用来定义对象的模板。 类的实现包括两部分:类声明和类体。基本格式为: 修饰符class类名[extends父类名]{ /*类体的内容*/ } 1.类声明 以下是一个类声明的例子。 class People成为类声明,People是类名。习惯上,类名的第一个字母大写,但这不是必须的。类的名字不能是Java中的关键字,要符合标识符规定,即类的名字可以由字母、下划线、数字或美元符号组成,并且第一个字母不能是数字。但给类命名时,最好遵守下列规则: (1)如果类名使用拉丁字母,那么名字的首写字母使用大写字母,如Hello、Time、People等。 (2)类名最好容易识别,当类名由几个“单词”复合而成时,每个单词的首写字母使用大写,如BeijingTi me、AmericanGame、HelloChina等。 2.类体 编写类的目的是为了描述一类事物共有的属性和功能,描述过程由类体来实现。类声明之后的一对大括号“{”、“}”以及它们之间的内容称为类体,大括号之间的内容称为类体的内容。 类体的内容由两部分构成:一部分是变量的定义,用来刻画属性;另一部分是方法的定义,用来刻画功能。 下面是一个类名为“Trapezia”的类,类体内容的变量定义部分定义了4个float类型变量:top、bottom、h igh和laderArea,方法定义部分定义了两个方法:“getArea”和“setHigh”。

二.对象 1.对象的创建 创建一个对象包括对象的声明和为对象分配内存两个步骤。 (1)对象的声明。 一般格式为: 类的名字对象名字; 如: 这里People是类的名字,zhubajie是我们声明的对象的名字。 (2)为声明的对象分配内存。 使用new运算符和类的构造方法为声明的对象分配内存,如果类中没有构造方法,系统会调用默认的构造方法(你一定还记得构造方法的名字必须和类名相同这一规定),如: 例1: 「注」如果类里定义了一个或多个构造方法,那么Java不提供默认的构造方法。 2.对象的使用 对象不仅可以改变自己变量的状态,而且还拥有了使用创建它的那个类中的方法的能力,对象通过使用这些方法可以产生一定的行为。 通过使用运算符“.”,对象可以实现对自己的变量访问和方法的调用。 例2:

相关主题
文本预览
相关文档 最新文档