函数指针的使用方法
对指针的应用是C语言编程的精髓所在,而回调函数就是C语言里面对函数指针的高级应用。简而言之,回调函数是一个通过函数指针调用的函数。如果你把函数指针(函数的入口地址)传递给另一个函数,当这个函数指针被用来调用它所指向的函数时,我们就说这个函数是回调函数。
为什么要使用回调函数呢?我们先看一个小例子:
Node * Search_List (Node * node, const int value)
{
while (node != NULL)
{
if (node -> value == value)
{
break;
}
node = node -> next;
}
return node;
}
这个函数用于在一个单向链表中查找一个指定的值,返回保存这个值的节点。它的参数是指向这个链表第一个节点的指针以及要查找的值。这个函数看上去很简单,但是我们考虑一个问题:它只能适用于值为整数的链表,如果查找一个字符串链表,我们不得不再写一个函数,其实大部分代码和现在这个函数相同,只是第二个参数的类型和比较的方法不同。
其实我们更希望令查找函数与类型无关,这样它就能用于查找存放任何类型值的链表了,因此必须改变比较的方式,而借助回调函数就可以达到这个目的。我们编写一个函数(回调函数),用于比较两个同类型的值,然后把一个指向这个函数的指针作为参数传递给查找函数,查找函数调用这个比较函数来执行比较,采用这个方法,任何类型的值得都可以进行比较。
我们还必须给查找函数传递一个指向待比较的值的指针而不是值本身,也就是一个void *类型的形参,这个指针会传递给回调函数,进行最终的比较。这样的修改可以让我们传递指向任何类型的指针到查找函数,从而完成对任何类型的比较,这就是指针的好处,我们无法将字符串、数组或者结构体作为参数传递给函数,但是指向它们的指针却可以。
现在,我们的查找函数就可以这样实现:
NODE *Search_List(NODE *node, int (*compare)(void const *, void const *) ,
void const *desired_value);
{
while (node != NULL)
{
if (compare((node->value_address), desired_value) == 0)
{
break;
}
node = node->next;
}
return node;
}
可以看到,用户将一个函数指针传递给查找函数,后者将回调这个函数。
注意这里我们的链表节点是这样定义的:
typedef struct list
{
void *value_address;
struct list *next;
}NODE;
这样定义可以让NODE *类型的指针指向存储任何类型数据的链表节点。而value_address就是指向具体数据的指针,我们把它定义为void *,表示一个指向未知类型的指针,这样链表就可以存储任何类型的数据了,而我们传递给查找函数Search_List的第一个参数就可以统一表示为:NODE *,否则,还是要分别写查找函数以适应存储不同数据类型的链表。
现在,查找函数与类型无关,因为它不进行实际的比较,因此,我们必须编写针对不同类型的比较函数,这是很容易实现的,因为调用者知道链表中所包含的值的类型,如果创建几个分别包含不同类型值的链表,为每种类型编写一个比较函数就允许单个查找函数作用于所有类型的链表。
下面是一个比较函数,用于在一个整型链表中查找:
注意强制类型转换,比较函数的参数必须被声明为void *以匹配查找函数的原型,然后强制转换为(int *)类型用于比较整型。
int int_compare(void const *a, void const *b)
{
if (*(int *)a == *(int *)b)
{
return 0;
}
else
{
return -1;
}
}
这个函数可以这样被使用:
desired_node = Search_List(root, int_compare, &desired_int_value);
如果你希望在一个字符串链表中进行查找,下面的代码就可以完成任务:
desired_node = Search_List(root, strcmp, “abcdefg”);
正好库函数strcmp所执行的比较和我们需要的一样,不过gcc会发出警告信息:因为strcmp的参数被声明为const char *而不是void const *。
上面的例子展示了回调函数的基本原理和用法,回调函数的应用是非常广泛的。通常,当我们想通过一个统一接口实现不同内容的时候,用回调函数来实现就非常合适。任何时候,如果你所编写的函数必须能够在不同的时刻执行不同的类型的工作或者执行只能由函数调用者定义的工作,你都可以用回调函数来实现。许多窗口系统就是使用回调函数连接多个动作,如拖拽鼠标和点击按钮来指定调用用户程序中的某个特定函数
函数指针
方法 指针函数和函数指针的区别 关于函数指针数组的定义 为函数指针数组赋值 函数指针的声明方法为: 数据类型标志符 (指针变量名) (形参列表); 注1:“函数类型”说明函数的返回类型,由于“()”的优先级高于“*”,所以指针变量名外的括号必不可少,后面的“形参列表”表示指针变量指向的函数所带的参数列表。例如: int func(int x); /* 声明一个函数 */ int (*f) (int x); /* 声明一个函数指针 */ f=func; /* 将func函数的首地址赋给指针f */ 赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。 注2:函数括号中的形参可有可无,视情况而定。 下面的程序说明了函数指针调用函数的方法: 例一、 #include
C语言中数组指针在汇编语言寻址方式中的应用
231 1、引言 《汇编语言程序设计》是高等院校计算机及相近专业学生必修的专业基础课程之一,它不仅是《嵌入式开发》、《操作系统》、《单片机》、《接口技术》等基础课程的先修课程,而且也十分有助于学生系 统掌握计算机基础知识和提高编程能力[1] 。作为一门直接控制计算机硬件和cpu结合最为紧密的一门语言,执行起来时最为有效和速度最快的。但是区别于高级语言他又自身的弱点,比如可读性差,需要更深入地熟悉硬件结构,编程和调试过程繁琐,而且没有便捷的开发调试环境。在讲授《汇编语言程序设计》过程中,如果能够结合或者转化为高级语言如C语言的内容那学生接受和学习起来就能增加不少的兴趣,提高学生的学习效率。 2、C 语言数组和指针的使用 2.1 数组 数组是在程序设计中为了处理方便,把具有相同类型的若干变量按有序的形式组织起来的一种形式。这些按序排列的同类数据元 素的集合称为数组[2] 。在C语言中,数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。因在汇编语言中主要把指令系统中的寻址方式转换为一维数组或指针,所以下面就简要介绍一下一维数组和指针的特点 定义一维数组的格式为: 类型说明符 数组名[整型常量表达式],…;例如:int a[10],b[5];说明: (1)它表示定义了两个一维数组,一个数组名为a,另一个数组名为b。数组名是按照“标识符”的规则构成的。(2)a数组含有10个数组元素,即a[0]、a[1]、a[2]、…、a[9];b数组含有5个数组元素,即b[0]、b[1]、b[2]、b[3]和b[4]。注意,不能使用a[10]和b[5],否则即出现数组超界现象,并且需要注意的是数组的小标是从0开始的。(3)类型说明符int 说明a数组和b数组中的每个元素均占2个字节,只能存放整型数据。(4)整型常量表达式可以是整型常量或符号常量。最常见的是整型常量。不允许为变量。(5)C编译程序(如Turbo C)为a数组在内存中分配了10个连续的数组单元(共占20个字节),为b数组在内存中分配了5个连续的数组单元(共占10个字节)。(6)C编译程序还指定数组名a为数组的首地址,即a与&a[0]等价;指定数组名b为b数组的首地址,即b与&b[0]等价。 2.2 指针 指针是一个特殊的变量,它里面存储的数值被解释成为内存里 的一个地址。计算机内存中的每个内存单元,都有相应的内存地址。在程序中对变量进行存取操作有两种方式,一种叫“直接存取”,就是指在程序中对变量进行存取操作时是按变量的地址来存取的方法,另一种叫“间接存取”,就是通过另外定义一个指针变量来保存 需要访问的数据的地址[3] 。 (1)指向简单变量的指针。(2)指向数组的指针。指针所指的数组既可以是一维数组,也可是多维数组。(3)指针数组。数组的元素值为指针,指针数组是一组有序的指针集合。(4)指向指针的指针。如 果一个指针变量存放的是另一个指针变量的地址,则称这个指针变 量为指向指针的指针。(5)指向函数的指针。在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址赋予一个指针变量,通过指针变量就可以找到并调用这个函数。 3、数组和指针在汇编语言指令系统寻址方式中的应用和转换 3.1 汇编语言指令系统的寻址方式[4] (1)立即寻址。(2)寄存器寻址。(3)直接寻址。(4)寄存器间接寻址:指令中指出一个基址寄存器BX、BP或变址寄存器SI、DI,并以其内容做为操作数的有效地址,ADD AX,[BP]物理地址=10H×(SS)+(BP)。(5)寄存器相对寻址:指令中指出一个基址或变址寄存器,同时给出一个位移量, 寄存器内容与位移之和做为操作数的有效地址。MOV AX,[DI+100H],有效地址EA=(DI)+100H,为物理地址=10H×(DS)+(DI)+100H。(6)基址变址寻址:指令同时指出一个基址寄存器和一个变址寄存器,两寄存器内容的和为操作数的有效地址。ADD AX,[BX][SI],有效地址EA=(BX)+(SI)。物理地址=10H×(DS)+(BX)+(SI)。(7)相对基址变址寻址:指令中给出一个基址寄存器一个变址寄存器和一个位移量。两个寄存器的内容及位移量三者之和做为操作数的有效地址。例:MOV DX,100H [BX] [SI,物理地址=10H×(DS)+(BX)+(SI)+100H。 3.2 间接寻址方式转换为数组或指针 3.2.1 寄存器间接寻址转成一维数组来理解 形式:ADD AX,[BP]物理地址=10H×(SS)+(BP)。我们就可以认为,在此定义了一个数组SS,即SS中的值为这个数组的首地址,当然我们知道这个数组的最大元素个数为64K个。刚才谈到偏移量和数组下标都是从0开始的,所以偏移量BP就可以认为是这个数组的一个下标,在这寻址操作数的时候是要把这个下标作为一个内存地址,其所存储的内容就是我们所要找的操作数。 在数组中形如I=A[10]就是把A数组的第10个元素赋值给I,在ADD AX,[BP]语句中BP也有一个中括号,只是在这个地方省略了数组名;并且也是把SS数组的第BP个元素赋值给AX。 所以无论从形式还是从本质上就把寄存器间接寻址转换成了一个一维数组。 3.2.2 寄存器间接寻址转成指针来理解因为指针和数组有时间是可以相互转换的,所以在这也可以转换成指针来理解。 形式:ADD AX,[BP]物理地址=10H×(SS)+(BP)。BP在汇编语言中本身就定义为一个基址“指针”用来和堆栈段配对使用,其中存放的数据是堆栈段的某一个存储单元地址。这就和指针吻合了,前面说到指针变量名与地址间具有一一对应关系,在存取操作时是按变量的地址来进行的一种“间接存取”的方法。那么这个地方我们可以认为BP是一个指向堆栈段中某一个存储单元的C语言意义上的指针。 这样就把寄存器间接寻址方式可以理解成C语言意义上的指针。对于寄存器相对寻址、基址变址寻址、基址变址寻址我们也都 C语言中数组指针在汇编语言寻址方式中的应用 马耀锋 李红丽 (中州大学信息工程学院 河南郑州 450044) 摘要:因高级语言不需要熟悉低层软件和硬件知识,所以学生有很大的学习兴趣,数组指针是C 语言中的重点内容,学生们都能熟练掌握。而汇编语言因与硬件紧密相连,所以学生学习兴趣不大。为了更好的培养学生的学习兴趣,提高教学效率,本文通过分析数组指针与寻址方式的异同,提出了如何把寻址方式转化成数组指针来学习的方法。 关键词:数组 指针 寻址方式中图分类号:TP312.1-4文献标识码:A 文章编号:1007-9416(2012)04-0231-02 ??????下转第232页
C指针函数习题
C++指针函数习题 一、选择题 1.以下程序的运行结果是()。 sub(int x, int y, int *z) { *z=y-x; } void main() { int a,b; sub(10,5,&a); sub(7,a,&b); cout< #include<>
C语言指针用法详解
让你不再害怕指针 前言:复杂类型说明 要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则: 从变量名处起,根据运算符优先级结合,一步一步分析. 下面让我们先从简单的类型开始慢慢分析吧: int p; //这是一个普通的整型变量 int *p; //首先从P处开始,先与*结合,所以说明P是一个指针,然后再与int结合, //说明指针所指向的内容的类型为int型.所以P是一个返回整型数据的指针int p[3]; // 首先从P处开始,先与[]结合,说明P是一个数组,然后与int结合, // 说明数组里的元素是整型的,所以P是一个由整型数据组成的数组int *p[3]; //首先从P处开始,先与[]结合,因为其优先级比*高,所以P是一个数组, //然后再与*结合,说明数组里的元素是指针类型, 然后再与int结合, //说明指针所指向的内容的类型是整型的,所以P是一个由返回整型数据 //的指针所组成的数组 int (*p)[3]; //首先从P处开始,先与*结合,说明P是一个指针,然后再与[]结合 //(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的 //内容是一个数组,然后再与int结合, 说明数组里的元素是整型的. //所以P是一个指向由整型数据组成的数组的指针 int **p; //首先从P开始,先与*结合,说是P是一个指针,然后再与*结合, 说明指 //针所指向的元素是指针,然后再与int结合, 说明该指针所指向的元素 //是整型数据.由于二级指针以及更高级的指针极少用在复杂类型中, 所 //以后面更复杂的类型我们就不考虑多级指针了, 最多只考虑一级指针. int p(int); //从P处起,先与()结合,说明P是一个函数,然后进入()里分析,说明该 //函数有一个整型变量的参数,然后再与外面的int结合, 说明函数的 //返回值是一个整型数据 Int (*p)(int); //从P处开始,先与指针结合,说明P是一个指针,然后与()结合,
指向函数的指针详解
指向函数的指针 函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关: bool (*pf)(const string &,const string &); 这个语句将pf声明为指向函数的指针,它所指向的函数带有两个const string &类型的形参和bool 类型的返回值。 注意:*pf两侧的括号是必需的。 1.typedef简化函数指针的定义: 函数指针类型相当地冗长。使用typedef为指针类型定义同义词,可将函数指针的使用大大简化: Typedef bool (*cmpfn)(const string &,const string &); 该定义表示cmpfn是一种指向函数的指针类型的名字。该指针类型为“指向返回bool类型并带有两个const string 引用形参的函数的指针”。在要使用这种函数指针类型时,只需直接使用cmpfcn即可,不必每次都把整个类型声明全部写出来。 2.指向函数的指针的初始化和赋值 在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针。假设有函数: Bool lengthcompare(const string &,const string &); 除了用作函数调用的左操作数以外,对lengthcompare的任何使用都被解释为如下类型的指针:
bool (*)(const string &,const string &); 可使用函数名对函数指针初始化或赋值: cmpfn pf1=0; cmpfn pf2=lengthcompare; pf1=legnthcompare; pf2=pf1; 此时,直接引用函数名等效于在函数名上应用取地址操作符: cmpfcn pf1=lengthcompare; cmpfcn pf2=lengthcompare; 注意:函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。 将函数指针初始化为0,表示该指针不指向任何函数。 指向不两只函数类型的指针之间不存在转换: string::size_type sumLength(const string &,const string &); bool cstringCompare(char *,char *); //pointer to function returning bool taking two const string& cmpFcn pf;//error:return type differs pf=cstringCompare;//error:parameter types differ pf=lengthCompare;//ok:function and pointer types match exactly 3.通过指针调用函数 指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用
指针函数与函数指针的区别
指针函数与函数指针的区别 一、 在学习arm过程中发现这“指针函数”与“函数指针”容易搞错,所以今天,我自己想一次把它搞清楚,找了一些资料,首先它们之间的定义: 1、指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针 类型标识符 *函数名(参数表) int *f(x,y); 首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。 表示: float *fun(); float *p; p = fun(a); 注意指针函数与函数指针表示方法的不同,千万不要混淆。最简单的辨别方式就是看函数名前面的指针*号有没有被括号()包含,如果被包含就是函数指针,反之则是指针函数。来讲详细一些吧!请看下面 指针函数: 当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。 格式: 类型说明符* 函数名(参数) 当然了,由于返回的是一个地址,所以类型说明符一般都是int。 例如:int *GetDate(); int * aaa(int,int); 函数返回的是一个地址值,经常使用在返回数组的某一元素地址上。 int * GetDate(int wk,int dy); main() { int wk,dy; do { printf(Enter week(1-5)day(1-7)\n); scanf(%d%d,&wk,&dy); } while(wk<1||wk>5||dy<1||dy>7); printf(%d\n,*GetDate(wk,dy));
利用函数指针数组进行的键散转处理(4x4)
单片机 键盘接口电路 简介 Copyleft2009 by高飞电子经营部 P.S. 文档由高飞整理。能力有限,疏漏在所难免!这里说声抱歉了。 若对文档所描述的观点存在疑问,欢迎交流。 QQ:1275701567
键盘是单片机应用系统中不可缺少的输入设备,是实心人机对话的纽带,是操作人员控制干预单片机应用系统的主要手段。如用手机键盘发送短信息、遥控器键盘控制家用电器等。各种仪器仪表的小键盘系统,则可以显示各种信息。例如,数字式频率计、数字式扫频仪、数字式测量仪等。通过键盘向单片机应用系统输入数据和控制命令,实现对应用系统的认为控制,可以提高应用系统的灵活性。每种单片机应用系统的小键盘系统会实现不同的功能。比如,需要按键进行清零、预置值、改变测量范围等。这些功能是由一系列键盘电路通过编程实现的。因此,键盘在控制系统中得到了广泛的应用。本文档介绍了单片机系统中键盘接口电路及其相应的实现方式。 包含以下内容: ●键盘的组成和分类; ●键盘实现的硬件接口电路; ●4x4键盘与单片机的接口实例; ●二进制编码器键盘与单片机的接口实例。 1.1键盘设计的组成和分类 说说键盘的发展史。键盘发展至今,已经有100多年的时间。伴随着材料科学和电子技术的发展,键盘的物理构成和物理构造也不断变化发展。这些不同结构的键盘,各有特点,适用于不同的场合,但是控制方法都是类似的。 1.1.1键盘的物理结构 1.机械式结构键盘 机械式结构键盘,一般使用类似金属接触式开关的原理,实现触点导通或断开。在实际应用中,机械开关的结构形式很多。最常用的是交叉接触式。它的优点是结实耐用,缺点是不防水,敲击比较费力。交叉接触式机械开关,在单片机应用系统中最为常用。轻触开关也属于这一类。 2.电容式结构键盘 电容式结构键盘是一种类似电容式开关原理键盘。它通过按键改变电极的间距而产生电容量的变化,暂时形成震荡脉冲允许通过的条件。电容的容量是由介质、两极间的距离及两极的面积来决定的。当键帽按下时,两极的距离就发生变化,就引起电容容量发生变化。当参数设计合适时,按键时就有输出,而不按键就无输出。这个输出再经过整形放大,去驱动编码器。由于电容器无接触,所以这种按键在工作过程中不存在磨损、接触不良等问题,耐久性、灵敏度和稳定性都比较好。另外,为了避免电极间进入灰尘,电容式按键开关采用了密闭封装,比较便于保养。优良的特性带来的缺点就是代价高昂,标准PC键盘淘宝卖价不低于1500RMB!
C语言第十六讲(指针与函数)
指针与函数 1.掌握指针变量为参数在调用函数和被调用函数之间的数据传递。 2.掌握函数返回地址值的方法。 3.掌握指向函数的指针及其运算。 4.能熟练运用指针变量完成C程序的编写。 1. 指针变量作为参数时实现数据传递 2.指向函数的指针及运算 3.函数返回地址值的方法
(一)导课 在C语言函数调用中,参数传递可以是一般变量的传递,也可以是地址的传递(指针)。 (二)课程要点 一、指针变量作为函数的参数 使用指针类型做函数的参数,实际向函数传递的是变量的地址。【例1】定义一个函数,用指针变量作参数实现两个数据的交换。 #include
void swap(int *pa,int *pb) { int t; t=*pa; *pa=*pb; *pb=t; } 其数据交换的变化过程如下图所示: 思考:将上面swap 函数作以下修改,a,b 的值是否发生了交换? void swap(int *pa,int *pb) { int *t; t=pa; pa=pb; pb=t; } 程序运行结果: before swap a=15,b=20 after swap a=20,b=15 程序运行结果: before swap a=15,b=20 after swap a=15,b=20