深入理解指针:【探索指针的高级概念和应用二】

news2024/11/24 16:42:47

目录

一,数组参数、指针参数

1.一维数组传参

2.二维数组传参

3.一级指针传参

4.二级指针传参

二,函数指针

三,函数指针数组

🍂函数指针数组的用途(转移表): 

四,指向函数指针数组的指针


一,数组参数、指针参数

我们在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?下面我们就一起来探究一下。

1.一维数组传参

#include <stdio.h>
//数组传参,形参是可以写成数组形式的,因为这儿传参的本质是数组首元素的地址,所以大小可以不写
void test(int arr[])
{}

//这儿不会去创建一个新的数组,这个大小是无意义的,所以数组里边的大小也可以省略
void test(int arr[10])
{}

//数组传参的本质是传递数组首元素的地址;数组传参,形参也可以是指针
void test(int* arr)
{}

//数组传参,形参用数组形式,数组的大小也可以省略
void test2(int* arr2[20])
{}

//arr2的每个元素类型都是int*,这儿传过来的是首元素地址,即第一个元素的地址(int*的地址),
//所以就是将一级指针的地址取出来放在二级指针里边去
void test2(int** arr2)
{}

int main()
{
	int arr[10] = { 0 };//定义了一个一维数组,数组里边有10个元素,每个元素是int类型
	int* arr2[20] = { 0 };//数组里边有20个元素,每个元素是int*类型
	test(arr);
	test2(arr2);
}

2.二维数组传参

//数组传参,形参的部分写成数组
void test(int arr[3][5])
{}

//错误写法
//数组传参的时候,行可以省略,但是列绝对不能省略
void test(int arr[][])
{}

//正确写法
void test(int arr[][5])
{}

//错误写法
//数组名表示首元素的地址,即第一行的地址;而这是一个整型指针,
//整型指针是用来接受整型变量的地址的,所以这种写法是错误的
void test(int* arr)
{}

//错误写法
//二维数组传过来拿指针数组接收了,应该用数组指针接收
void test(int* arr[5])
{}

//正确写法
//这是一个数组指针,指向5个元素,每个元素是int类型,它可以指向数组中的第一、二、三行
void test(int(*arr)[5])
{}

//错误写法
//二级指针是用来接收一级指针的地址的
void test(int** arr)
{}

int main()
{
	int arr[3][5] = { 0 };//定义了一个三行五列的二维数组
	test(arr);//对数组进行传参
}

🍂总结:

二维数组传参,函数形参的设计只能省略第一个[ ]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,这样才方便运算。

3.一级指针传参

#include <stdio.h>
//一级指针传参的时候形参的部分写成一级指针就可以
void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));//访问数组的每个元素
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;//将数组的数组名赋给了p,本质是将数组首元素的地址赋给了p
	int sz = sizeof(arr) / sizeof(arr[0]);
	
	print(p, sz);//一级指针p,传给函数
	return 0;
}

🌴我们可以思考一下当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

void test(int* p)
{

}

int a = 10;

test(&a);//传整型变量的地址

int* ptr = &a;
test(ptr);//传整型指针

int arr[5];
test(arr);//传整型一维数组的数组名

4.二级指针传参

#include <stdio.h>
void test(int** ptr)//二级指针传过来拿二级指针接收
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);//把pp这个二级指针传给test函数
	test(&p);//p是一级指针变量,&p也是二级指针
	return 0;
}

🌴我们再思考一下当一个函数的参数部分为二级指针的时候,函数能接收什么参数? 

void test(int** p)
{

}

int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	int* arr[6];

	test(&p);
	test(pp);
	test(arr);//数组名表示首元素的地址,就是int*的地址,传参后要用二级指针来接收
	return 0;
}

二,函数指针

我们知道数组指针指向数组的指针,存放的是数组的地址,&数组名就是数组的地址;

函数指针就是指向函数的指针,存放的是函数的地址,那怎么才能得到函数的地址呢?是不是&函数名呢?接下来我们通过一段代码来探究一下函数指针的神秘面纱:

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//&函数名就是函数的地址
	//函数名也是函数的地址
	printf("%p\n", &Add);
	printf("%p\n", Add);

	return 0;
}

🎈输出结果: 

我们可以看到输出的是两个相同的地址,而这两个地址都是Add函数的地址 ,所以&函数名函数名都能得到函数的地址。那我们的函数想要保存起来,该怎么做呢?看代码:

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf1)(int, int) = &Add;//pf1就是函数指针变量

	int ret = (*pf1)(3, 5);//通过函数指针变量找到函数地址并且调用它
    printf("%d\n", ret);

	return 0;
}

上面代码中的pf1是函数指针变量,它可以将我们的函数保存起来,pf1先和*结合,说明pf1是指针,指针指向的是一个函数,指向的函数有两个int类型的参数,返回值类型为int类型。


🍂 接下来我们再阅读两段有趣的代码(出自:《C陷阱和缺陷》):

🍃代码一:

int main()
{
	( *(void (*)())0 )();

	return 0;
}

在上面这段代码中我们从最熟悉的0开始下手,0是个数字;0前面的void (*)()这部分是指针指向了一个函数,函数没有参数,返回类型是void,所以这部分是一个函数指针类型;将类型放在括号里边就是要强制类型转换,转换完后前面加个*,就是要解引用,去调用这个函数,调用的这个函数也没有参数;

总结起来就是上面这段代码是在调用0地址处的函数,这个函数没有参数,返回类型是void。

🍃代码二:

int main()
{
	void (*signal(int, void(*)(int)))(int);

	return 0;
}

 上面这段代码比较复杂,我们可以将它简化为以下两部分

void (*  )(int);

signal(int, void(*)(int));

现在我们再来看这段代码就比较好分析了,首先它是一次函数声明,声明的是signal函数,signal 函数的参数有两个,第一个是int 类型,第二个是函数指针类型,该类型是void (*)(int),该函数指针指向的函数,参数是int,返回类型是void;signal函数的返回类型也是函数指针类型,该类型是void (*)(int),该函数指针指向的函数,参数是int,返回类型是void。

三,函数指针数组

通过前面的学习我们知道数组是一个存放相同类型数据的存储空间,例如指针数组:

int* arr[10];

 这是一个整型指针数组,存放的是整型指针,数组的每个元素是int*。

由上边的例子我们就可以知道函数指针数组也是一个数组,它存放的是函数指针(即函数的地址 ):

int (*parr1[10])();
//parr1 先和 [] 结合,说明 parr1是数组,
//数组的内容是int (*)() 类型的函数指针

🍂函数指针数组的用途(转移表): 

🌴例子(计算器):

void menu()
{
	printf("************************\n");
	printf("***  1.Add    2.Sub  ***\n");
	printf("***  3.Mul    4.Div  ***\n");
	printf("***  0.exit          ***\n");
	printf("************************\n");
}

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:> ");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入操作数:");
			scanf("%d%d", &x, &y);
			ret = Add(x, y);
			printf("ret= %d\n", ret);
			break;
		case 2:
			printf("请输入操作数:");
			scanf("%d%d", &x, &y);
			ret = Sub(x, y);
			printf("ret= %d\n", ret);
			break;
		case 3:
			printf("请输入操作数:");
			scanf("%d%d", &x, &y);
			ret = Mul(x, y);
			printf("ret= %d\n", ret);
			break;
		case 4:
			printf("请输入操作数:");
			scanf("%d%d", &x, &y);
			ret = Div(x, y);
			printf("ret= %d\n", ret);
			break;
		case 0:
			printf("退出计算机\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);

	return 0;
}
  • 上面这段代码现在只具有加减乘除的功能,但是如果我想让它实现 a&&b、a||b、a&b、a|b、a>>b、a<<b等功能时,我们的运算会越来越多,菜单里边的功能相应的也会越来越多,同时类似加减乘除的函数也会越来越多,而且switch语句会越来越长,代码会冗余。
  • 那有没有什么办法让函数变得简洁呢,其实是有的。通过观察,会发现这些函数除了函数名和里边的计算不一样外,函数的参数都是两个int,返回类型都是int,所以我们可以通过函数指针数组来改写它。

🌴使用函数指针数组的实现:

void menu()
{
	printf("************************\n");
	printf("***  1.Add    2.Sub  ***\n");
	printf("***  3.Mul    4.Div  ***\n");
	printf("***  0.exit          ***\n");
	printf("************************\n");
}

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:> ");
		scanf("%d", &input);
		//函数指针数组 - 转移表
		int (*pfarr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//放个NULL的原因是将这些操作函数的下标往右挤一位
	    if (0 == input)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入操作数:>");
			scanf("%d%d", &x, &y);
			ret = (pfarr[input])(x, y);
			printf("ret= %d\n", ret);
		}
		else
		{
			printf("选择错误,请重新选择!\n");
		}
	} while (input);

	return 0;
}

四,指向函数指针数组的指针

🎈我们先来看一下整型指针数组:

int a = 10;
int b = 20;
int c = 30;

//整型指针数组,数组的每个元素是int*类型
int* arr[] = { &a, &b, &c };

//p是指针,是指向整型指针数组的指针
int* (*p)[3] = &arr;

有了上面的例子我们再来看指向函数指针数组的指针:

指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针

void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	//函数指针pfun
	void (*pfun)(const char*) = test;

	//函数指针的数组pfunArr
	void (*pfunArr[5])(const char* str);

	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void (*(*ppfunArr)[5])(const char*) = &pfunArr;

	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1185651.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git 代码库 gogs 部署私服及 https 配置手册

背景 玩了一下 Git &#xff0c;想到一个问题&#xff1a;企业内部怎么用 Git 呢&#xff1f;仓库哪里来呢&#xff1f; 理一理 Git 及其相关产品的区别&#xff1a; Git 分布式版本管理工具。GitHub 和 Gitee &#xff0c;基于 Git 的互联网代码托管平台&#xff0c;一个是…

【小技巧】WPS统计纯汉字(不计标点符号)

【小技巧】WPS统计纯汉字&#xff08;不计标点符号&#xff09; 首先&#xff0c;CtrlF打开查找页面&#xff1a; 选择“高级搜索”&#xff0c;然后勾选“使用通配符”&#xff0c;然后在“查找内容”后面输入&#xff1a;[一-﨩]。注意&#xff1a;一定要带“[]”和“-”且…

FreeRTOS_空闲任务

目录 1. 空闲任务详解 1.1 空闲任务简介 1.2 空闲任务的创建 1.3 空闲任务函数 2. 空闲任务钩子函数详解 2.1 钩子函数 2.2 空闲任务钩子函数 3. 空闲任务钩子函数实验 3.1 main.c 空闲任务是 FreeRTOS 必不可少的一个任务&#xff0c;其他 RTOS 类系统也有空闲任务&a…

Android MotionLayout

MotionLayout exends ConstraintLayout(动画框架 过渡) View动画 API1 属性动画API11 过渡动画API18 root.width RootViewWidth TransitionManager.beginDelayedTransition(view) 过渡动画 可以改变其大小和流畅性 Fade 可以改变透明度 通过TrasitinManager管理 Go:动态替…

adb and 软件架构笔记

Native Service&#xff0c;这是Android系统里的一种特色&#xff0c;就是通过C或是C代码写出来的&#xff0c;供Java进行远程调用的Remote Service&#xff0c;因为C/C代码生成的是Native代码&#xff08;机器代码&#xff09;&#xff0c;于是叫Native Service。 native服务…

Linux C语言进阶-D14指针函数

指针函数&#xff1a;指一个函数的返回值为地址量的函数 <数据类型>* <函数名称>&#xff08;<参数说明>&#xff09; { 语句序列; } 返回值&#xff1a;全部变量的地址、静态变量的地址、字符串常量的地址、堆上的地址 注意&#xff1a;不可返回局部变量…

Redis Java 开发简单示例

文章目录 一、概述二、Jedis 开发示例2.1 导入 maven 依赖2.2 使用连接池读写2.3 使用集群读写2.4 完整示例代码2.5 测试集群的搭建 三、Lettuce 开发示例3.1 导入 maven 依赖3.2 读写数据 四、Spring Boot Redis 开发示例4.1 导入 maven 依赖4.2 配置Redis服务地址4.3 基于 Re…

52基于MATLAB的希尔伯特Hilbert变换求包络谱

基于MATLAB的希尔伯特Hilbert变换求包络谱&#xff0c;对原始信号进行初步滤波&#xff0c;之后进行包络谱分析。可替换自己的数据进行优化。程序已调通&#xff0c;可直接运行。 52的尔伯特Hilbert变换包络谱 (xiaohongshu.com)

混沌系统在图像加密中的应用(基于哈密顿能量函数的混沌系统构造1.1)

混沌系统在图像加密中的应用&#xff08;基于哈密顿能量函数的混沌系统构造1.1&#xff09; 前言一、基于广义哈密顿系统的一类混沌系统构造1.基本动力学特性分析2.数值分析 待续 前言 本文的主题是“基于哈密顿能量函数的混沌系统构造”&#xff0c;哈密顿能量函数是是全文研…

【Git】快速入门安装及使用git与svn的区别常用命令

一、导言 1、什么是svn&#xff1f; SVN是Subversion的简称&#xff0c;是一个集中式版本控制系统。与Git不同&#xff0c;SVN没有分布式的特性。在SVN中&#xff0c;项目的代码仓库位于服务器上&#xff0c;团队成员通过向服务器提交和获取代码来实现版本控制。SVN记录了每个文…

Go 接口:nil接口为什么不等于nil?

本文主要内容:深入了解接口类型的运行时表示层。 文章目录 一、Go 接口的地位二、接口的静态特性与动态特性2.1 接口的静态特性与动态特性介绍2.2 “动静皆备”的特性的好处 三、nil error 值 ! nil四、接口类型变量的内部表示第一种&#xff1a;nil 接口变量第二种&#xff1a…

JDBC(二)

第4章 操作BLOB类型字段 4.1 MySQL BLOB类型 MySQL中&#xff0c;BLOB是一个二进制大型对象&#xff0c;是一个可以存储大量数据的容器&#xff0c;它能容纳不同大小的数据。 插入BLOB类型的数据必须使用PreparedStatement&#xff0c;因为BLOB类型的数据无法使用字符串拼接写…

Hundred Finance 攻击事件分析

背景知识 Hundred Finance 是 fork Compound 的一个借贷项目&#xff0c;在2023/04/15遭受了黑客攻击。攻击者在发起攻击交易之前执行了两笔准备交易占据了池子&#xff0c;因为发起攻击的前提是池子处于 empty 的状态&#xff08;发行的 hToken 数量为 0&#xff09;。 准备交…

​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​

软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】 课本里章节里所有蓝色字体的思维导图

CAN 协议常见面试题总结

0.讲一下CAN通讯的过程 第一段&#xff1a;需要发送的通讯设备&#xff0c;先发送一个显性电平0&#xff0c;告诉其他通讯设备&#xff0c;需要开始通讯。 第二段&#xff1a;就是发送仲裁段&#xff0c;其中包括ID帧和数据帧类型&#xff0c;告诉其他通讯设备&#xff0c;需…

记录一次校园CTF--wp

一.第一题简单nc 这题直接nc 地址端口即可得到flags没有套路 二.第二题pwn:ezstack 这是一题栈溢出题目&#xff0c;查看保护&#xff1a; 没有开启PIE&#xff0c;运行下查看效果&#xff1a; 题目是一个文字购物游戏。 接着扔进IDA中分析&#xff1a; 在主函数中我们找到…

C++ string赋值和添加值

在MFC中使用C的string&#xff0c;要先#include <string>&#xff0c;然后&#xff0c;std::string s2("") 这样就可以了&#xff1b; void CStrnewView::OnDraw(CDC* pDC) {CStrnewDoc* pDoc GetDocument();ASSERT_VALID(pDoc);// TODO: add draw code for n…

【Redis】list常用命令内部编码使用场景

文章目录 前置知识列表类型的特点 命令LPUSHLPUSHXRPUSHRPUSHXLRANGELPOPRPOPLINDEXLREMLINSERTLTRIMLSETLLEN 阻塞版本命令BLPOPBRPOP 命令总结内部编码测试内部编码 使用场景消息队列分频道的消息队列 模拟栈和队列 前置知识 列表类型是⽤来存储多个有序的字符串&#xff0c…

项目管理之如何出道(上)

前言 终于有时间更新了&#xff0c;大家是不是等不及了&#xff1f;那么书接上文&#xff0c;言归正传。 各位盆友&#xff0c;时间之轮已划入夜晚&#xff0c;尝试静下心来&#xff0c;思考一番。 Q1&#xff1a;是否曾经期待自己做一名项目经理&#xff0c;干了几年的coder甚…

MinGW32丢失dll文件

问题现象 执行Makefile的时候&#xff0c;突然出现这个提示&#xff0c;还有好几个类似的&#xff0c;提示我找不到dll文件&#xff0c;建议重装。 问题分析 重装软件 最直接的办法肯定是按照建议来重装&#xff0c;但是发现重装了好几次&#xff0c;不是缺这个就是缺那个&a…