函数指针:
如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址,称为这个函数的指针。
可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。例如:
int (*p)(int,int);
定义p是指向函数的指针变量,它可以指向类型为整型且有两个整型参数的函数。p的类型用int (*)(int,int)表示。
示例:
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
/*
声明 无参,无返回值函数
*/
void msg();
int add(int n1, int n2);
/*
声明 有参,有返回值函数
*/
int main(void)
{
printf("---------- 输出函数地址: ----------\n");
//函数名是一个地址,是函数的入口点
printf("mag函数地址:%p\nadd函数地址:%p\n ", msg, add);
//定义函数指针,指向函数进行调用。
int (*hAdd)(int, int) = add; //定义 有参,有返回值函数
int res = hAdd(2, 3);//调用
printf("结果:%d\n", res);
void (*hMsg)() = msg; //定义 无参,无返回值函数
hMsg();//调用
return 0;
}
void msg()
{
MessageBoxA(0, "你好,世界!", "提示", 0);
}
int add(int n1, int n2)
{
int num = 0;
num = n1 + n2;
return num;
}
函数指针的定义:
定义函数指针需要3步:
假设有函数为:
void msgBox(char* content, char* title)
{
MessageBoxA(0, content, title, 0);
}
- 挖去函数声明 与与参数类型
void msgBox(char* content, char* title)
- 替换函数名 为 ()
void ()(char* content, char* title)
- 在函数名括号内填入指针名称
void (*hmsgBox)(char* content, char* title)
- 初始化函数指针
void (*hmsgBox)(char* content, char* title)=msgBox
示例:
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
void msgBox(char* content, char* title)
{
MessageBoxA(0, content, title, 0);
}
int main(void)
{
void(*hmsgBox)(char* content, char* title) = msgBox;
hmsgBox("Hello world", "Greetings");
return 0;
}
函数指针,不仅仅是地址,必须明确函数指针类型,明确知道参数个数和参数类型以及返回指针类型,如果类型不匹配,仅仅只知道地址,也无法调用。
函数返回值是指针:
一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址。其概念与以前类似,只是返回的值的类型是指针类型而已。
定义返回指针值的函数的一般形式为:
类型名 *函数名(参数表列);
示例:
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<time.h>
int num = 100; //定义全局共用变量
int* getNum()
{
/*
注意 ,返回指针类型,变量num不能是在函数中声明的变量,这是C语言中作用域决定的
在 getNum 函数中声明的变量,当函数执行完成后就释放了,它的作用域仅限于当前函数
*/
return #
}
/*
堆中开辟空间,存储数据,并返回指针, 手动开辟空间,要记得释放
*/
int* getNumofHeapspace()
{
int* p = NULL;
p = (int*)(malloc(sizeof(int)));
*p = 200;
return p;
}
/*
练习题:
随机生成数组,并查找最小数,返回最小数地址, len 数组长度
*/
int* mindata();
int main(void)
{
int* p1 = getNum();
printf("getNum=%d\n", *p1);//取出指针的值
int *p2 = getNumofHeapspace();
printf("getNumofHeapspace=%d\n", *p2);//取出指针的值
free(p2);//释放指针空间
int* pmin = mindata();
printf("pmin=%d\n", *pmin);
return 0;
}
int* mindata()
{
int arr[10];
time_t ts;
srand((unsigned int)time(&ts));//按照时间设定随机数种子,并存放在变量ts中
//初始化数组
for (size_t i = 0; i < 10; i++)
{
arr[i] = rand() % 100;//限定随机数在100以内
printf("%5d", arr[i]);
}
printf("\n");
//查找最小数
int* p = NULL;
int min = arr[0];
p = &arr[0];
for (size_t i = 1; i < 10; i++)
{
if (min > arr[i])
{
min = arr[i];
p = &arr[i]; //保存最小数地址
}
}
printf("最小数地址:%p\n", p);
return p;
}
指针左值指针与整数指针空指针以及指向为空的指针
左值指针: 左值的概念, “可放在赋值号左边的都可称为左值”
指针变量以及指针变量的间接引用都可作左值,如:
int num1=0,num2=0;
int* p=&num1;
p=&num2; /*指针作左值*/
*p=1; /*间接引用作左值*/
- 指针变量可以作左值,并不是因为它们是指针,而是因为它们是变量
整数指针:
无论指针指向什么样的数据类型,对于32位系统来说都占4个内存字节,指针的值是某个内存地址。这应该是个‘整数’。
如果要对某个内存地址进行访问,可以通过强制类型转换来完成,如:
int *p=(int *)0x00123FB1C;
空指针以及指向为空的指针:
void *指针是一种特殊的指针,不指向任何类型的数据,如果需要用此地址指向某类型的数据,应先对地址进行类型转换。可以在程序中进行显式的类型转换,也可以由编译系统自动进行隐式转换。
int n1=100;
double d1=12.2;
int *p1=#
double *p2=&d1;
void p1=p2; // void 类型的指针可以传递地址。
printf(“%d”,*p1); //void 类型的指针,由于指向不明确,大小不确定,无法取出内容
printf(“%d”,((int *)p1)) ; //能够取出内容
空指针用于参数还有返回值,不明确指针类型的情况传递地址需要用到空类型的指针,要把它用于某种类型的指针,需要强制转换。
空类型指针可以指向任何类型的数据,包含他们的地址:
char c1='A';
int n=10;
double d1=12.2;
void *p;
如果: p=&n; 编译没有问题,但是输出就会报错:printf(“%d”,*p); //*p不明确从地址开始,前进几个字节,所以出错
正确写法 : *((double *)p)=20.8 // 明确了从地址开始,前进了几个字节
任何指针都可以赋值给空类型的指针,用于保存地址。
函数指针的内存原理:
函数被载入内存,函数必然有一个地址是函数的入口,我们用这个地址来调用,函数名也是指向函数入口点的指针,我们可以通过函数名找到函数的执行入口。同时C语言的编译器(无论VC或者GCC)都有这样的规则。
针对函数void run(),函数名run 解析为函数的地址,run,&run,*run都解析为run的入口地址,即为&run函数的首地址。而且函数名不可以用sizeof操作符。
示例:
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<time.h>
/*
声明 无参,无返回值函数
*/
void msg();
void main()
{
printf("输出函数地址:%p", msg);
/*
msg 是函数的入口点地址,是一个常量
*/
msg();//显式调用函数 跳到函数入口点,开始执行函数
//定义一个与 msg 函数 类型一致,参数一致的函数指针
void (*hmsg)();
hmsg = msg;//hmsg 是一个变量,可以存储函数入口点的地址
hmsg();//调用
system("pause");
}
void msg()
{
MessageBoxA(0, "你好,世界!", "提示", 0);
}
在反汇编窗口输入函数地址,找到函数地址入口:001610CD
_msg:
001610CD E9 0E 08 00 00 jmp msg (01618E0h)
__initialize_denormal_control:
001610D2 E9 F9 23 00 00 jmp _initialize_denormal_control (01634D0h)
jmp: 跳转, -> msg(01618E0h) //函数开始地址 (后缀h 代表16进制)
E9 0E 08 00 00 代码字节【 5个字节数10个16进制数据(001610D2 - 001610CD)】保存了函数的入口点。
再次 转到 msg(01618E0h)地址,下面是函数msg的汇编代码
所以,函数名就是函数入口点的指针,保存了函数的入口点。
内存模型:
那么显式调用的顺序也可以查看
msg();//显式调用函数
可以看出 函数名 +() 就是跳到函数入口点,开始执行函数 【call :就是调用函数】
函数指针数组:
当数组元素都是同种类型的指针时,该数组称为指针数组,如“int* A[3];”即声明了一个指针数组A,大小为3,其中每个元素都是int型指针。如果数组元素都是指向同型函数(返回值类型相同,参数类型相同)的指针,该数组称为函数指针数组。
示例:
#pragma once
/*
operation.h 定义函数
*/
int add(int n1, int n2);
int sub(int n1, int n2);
int mul(int n1, int n2);
int division(int n1, int n2);
int modulo(int n1, int n2);
/*
函数实现
*/
#include"operation.h"
//加法
int add(int n1, int n2)
{
return n1 + n2;
}
//减法
int sub(int n1, int n2)
{
return n1 - n2;
}
//乘法
int mul(int n1, int n2)
{
return n1 * n2;
}
//除法
int division(int n1, int n2)
{
return n1 / n2;
}
//求模
int modulo(int n1, int n2)
{
return n1 % n2;
}
#include<stdio.h>
#include<stdlib.h>
#include"operation.h"
void main()
{
//int (*hfun)(int, int);//定义函数指针
int (*hfun[5])(int, int);//定义函数指针数组
//初始化函数指针数组
hfun[0] = add;
hfun[1] = sub;
hfun[2] = mul;
hfun[3] = division;
hfun[4] = modulo;
int num1 = 100;
int num2 = 10;
for (size_t i = 0; i < 5; i++)
{
printf("函数地址: %p ;计算结果:%d\n", hfun[i], hfun[i](num1, num2));
}
system("pause");
}
指向函数指针的指针:
double (*f[5])( );
已经知道,数组名可作为指向数组首元素起始地址的常指针,那函数指针数组的数组名是什么呢?类推得出,函数指针数组名,对应上面语句中的f,是指向函数指针的常指针,下述代码声明了一个指向函数指针的指针变量p,并用f为其初始化:
double (**p)( )=f;
示例:
#include<stdio.h>
#include<stdlib.h>
#include"operation.h"
void main()
{
int (*hfun[5])(int, int) = { add,sub,mul,division,modulo };//初始化函数指针数组
int num1 = 100;
int num2 = 10;
for (int(* *hF)(int, int) = hfun; hF < hfun + 5; hF++)
{
printf("函数地址: %p ;计算结果:%d\n", hF, (*hF)(num1,num2));
}
system("pause");
}
小结:
一个指向整型数的指针 int*p
一个指向整型数指针的指针 int **p
一个有10个整型指针的数组 int *p[10]
一个指向有10个整型数数组的指针 int (*p)[10]
一个指向函数的指针,该函数有一个整型参数,并返回一个整型数 int ( *p)(int)
一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数,并返回一个整型数。 int(*p[10])(int)
一个指向函数指针的指针,所指向的函数有一个整型参数,并返回一个整型数。 int (**p)(int)