C语言: 指针 -3

函数指针:

如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址,称为这个函数的指针。
可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。例如:

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);
}
  1. 挖去函数声明 与与参数类型 void msgBox(char* content, char* title)
  2. 替换函数名 为 () void ()(char* content, char* title)
  3. 在函数名括号内填入指针名称 void (*hmsgBox)(char* content, char* title)
  4. 初始化函数指针 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 &num;
}

/*
	堆中开辟空间,存储数据,并返回指针, 手动开辟空间,要记得释放
*/
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=&num;
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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值