c语言从入门到实战——数组指针与函数指针

news2024/11/25 14:19:40

数组指针与函数指针

  • 前言
  • 1. 字符指针变量
  • 2. 数组指针变量
    • 2.1 数组指针变量是什么?
    • 2.2 数组指针变量怎么初始化?
  • 3. 二维数组传参的本质
  • 4. 函数指针变量
    • 4.1 函数指针变量的创建
    • 4.2 函数指针变量的使用
    • 4.3 两段有趣的代码
      • 4.3.1 typedef关键字
  • 5. 函数指针数组
  • 6. 转移表


前言

数组指针和函数指针都是指针类型,但是它们指向不同的数据类型。

数组指针:数组指针指向的是数组类型。

函数指针:函数指针指向的是函数类型。


1. 字符指针变量

在指针的类型中我们知道有一种指针类型为字符指针 char* ;

一般使用:

int main()
{
	char ch = 'w';
	char *pc = &ch;
	*pc = 'w';
	return 0;
}

还有一种使用方式如下:

int main()
{
	const char* pstr = "hello bit."; //这里是把一个字符串放到pstr指针变量里了吗?
	printf("%s\n", pstr);
	return 0;
}

代码 const char* pstr = "hello bit."; 特别容易让读者以为是把字符串 hello bit 放到字符指针 pstr 里了,但是本质是把字符串 hello bit. 首字符的地址放到了pstr中。

在这里插入图片描述
上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。

注意:常量字符串把他赋值给一个指针,赋的值是首元素地址,在原理上就和指针是等价的了

  1. 你可以把常量字符串当成指针用,可以这样用,但不建议一个工程里全部使用。
    在这里插入图片描述
  2. 左右相等 可以这样理解,先理解下面的,就比如你用一个指针存放数组的地址,不看其他的,就看他们的名称,他们的名称就是等价的可以相互替换p1[0] == arr[0],就好比这样,随你怎么使用,理解了这个之后,便可以知道他们的返回值也是等价的。

在这里插入图片描述

《剑指offer》中收录了一道和字符串相关的笔试题,我们一起来学习一下:

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char *str3 = "hello bit.";
	const char *str4 = "hello bit.";
	if(str1 ==str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	if(str3 ==str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
	return 0;
}

在这里插入图片描述

这里str3str4指向的是一个同一个常量字符串。

C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1str2不同,str3str4相同。

使用const表示常量字符串,会在空间中存放,如果有其他的使用跟const修饰的常量字符串一样,他就不会开辟其他的空间,而且直接调用原来的那个。

2. 数组指针变量

2.1 数组指针变量是什么?

我的上篇文章讲了指针数组,指针数组是一种数组,数组中存放的是地址(指针)。

那数组指针变量是指针变量?还是数组?

答案是:指针变量。

我们已经熟悉:

  • 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。

  • 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。

下面代码哪个是数组指针变量?

int *p1[10];
int (*p2)[10];

思考一下:p1,p2分别是什么?

p1是指针数组

p2是数组指针

int (*p)[10]; 

解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,所以是数组指针。

这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

2.2 数组指针变量怎么初始化?

数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前文章里讲过的 &数组名 。

int arr[10] = {0};
&arr; //得到的就是数组的地址

如果要存放个数组的地址,就得存放在数组指针变量中,如下:

int(*p)[10] = &arr; 

在这里插入图片描述
我们调试也能看到 &arrp 的类型是完全一致的。

数组指针类型解析:

int (*p) [10] = &arr;
|	 | 	 |
|	 |	 |
| 	 |	 p指向数组的元素个数
|	 p是数组指针变量名
p指向的数组的元素类型

3. 二维数组传参的本质

有了数组指针的理解,我们就能够讲一下二维数组传参的本质了。

过去我们有一个二维数组的需要传参给一个函数的时候,我们是这样写的:

#include <stdio.h>
void test(int a[3][5], int r, int c)
{
	int i = 0;
	int j = 0;
	for(i=0; i<r; i++)
	{
		for(j=0; j<c; j++)
		{
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
	test(arr, 3, 5);
	return 0;
}

这里实参是二维数组,形参也写成二维数组的形式,那还有什么其他的写法吗?

首先我们再次理解一下二维数组,二维数组起始可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组。

如下图:

在这里插入图片描述
所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。

根据上面的例子,第一行的一维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5] 。那就意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的。

如下:

#include <stdio.h>
void test(int (*p)[5], int r, int c)
{
	int i = 0;
	int j = 0;
	for(i=0; i<r; i++)
	{
		for(j=0; j<c; j++)
		{
			printf("%d ", *(*(p+i)+j));
		}
	printf("\n");
	}
}
int main()
{
	int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
	test(arr, 3, 5);
	return 0;
}

总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式。

4. 函数指针变量

4.1 函数指针变量的创建

什么是函数指针变量呢

根据前面文章得到整型指针,数组指针的时候,我们类比关系,我们不难得出结论:

函数指针变量应该是用来存放函数地址的,在写代码的时候可以通过地址能够调用函数。
那么函数是否有地址呢?

我们做个测试:

#include <stdio.h>
void test()
{
	printf("hehe\n");
}
int main()
{
	printf("test: %p\n", test);
	printf("&test: %p\n", &test);
	return 0;
}

输出结果如下:

test: 005913CA
&test: 005913CA

确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名的方式获得函数的地址。

如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针非常类似。

如下:

void test()
{
	printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)()= test;
int Add(int x, int y)
{
	return x+y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add; //x和y写上或者省略都是可以的

函数指针类型解析:

int (*pf3) (int x, int y)
|	 | ------------
| 	 |	   |
| 	 | 		pf3指向函数的参数类型和个数的交代
| 	函数指针变量名
pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型

4.2 函数指针变量的使用

通过函数指针调用指针指向的函数。

#include <stdio.h>
int Add(int x, int y)
{
	return x+y;
}
int main()
{
	int(*pf3)(int, int) = Add;
	printf("%d\n", (*pf3)(2, 3));
	printf("%d\n", pf3(3, 5));
	return 0;
}

输出结果:

5
8

4.3 两段有趣的代码

代码1

(*(void (*)())0)(); 

代码2

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

两段代码均出自:《C陷阱和缺陷》这本书

4.3.1 typedef关键字

typedef是用来类型重命名的,可以将复杂的类型,简单化。

比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:

typedef unsigned int uint;
//将unsigned int 重命名为uint

如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:

typedef int* ptr_t; 

但是对于数组指针和函数指针稍微有点区别:

比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5]; //新的类型名必须在*的右边

函数指针类型的重命名也是一样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:

typedef void(*pfun_t)(int); //新的类型名必须在*的右边

那么要简化代码2,可以这样写:

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

5. 函数指针数组

数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组,

比如:

int *arr[10];
//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];

答案是:parr1

parr1 先和 [] 结合,说明parr1是数组,数组的内容是什么呢?

int (*)() 类型的函数指针。

6. 转移表

函数指针数组的用途:转移表
举例:计算器的一般实现:

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \n");
		printf("*************************\n");
		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;
}

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

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a*b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
	do
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \n");
		printf("*************************\n");
		printf( "请选择:" );
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf( "输入操作数:" );
			scanf( "%d %d", &x, &y);
			ret = (*p[input])(x, y);
			printf( "ret = %d\n", ret);
		}
		else if(input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf( "输入有误\n" );
		}
	}while (input);
	return 0;
}

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

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

相关文章

【AIGC】一起学习prompt提示词(4/4)【经典】【15种提示词技巧】

写的时候并没有设计好&#xff0c;要做多少期&#xff0c;还是有始有终的比较好&#xff0c;为了方便阅读&#xff0c;我把之前的3期&#xff0c;改下名字&#xff0c;放到这里。 【AIGC】一起学习prompt提示词&#xff08;1/4&#xff09; 内容摘要&#xff1a;提示词是什么…

借教室——二分、前缀和、差分

题目 思路 当某一份订单可以满足的时候&#xff0c;那么他前面的所有订单都可以满足&#xff0c;当某一份订单不能满足的时候&#xff0c;那么他后面的所有订单都不能完成&#xff0c;所以可以使用二分查找来降低时间复杂度每次二分找到一份订单&#xff0c;利用二分与前缀和将…

【JavaSE语法】类和对象(二)

六、 封装 6.1 封装的概念 面向对象程序三大特性&#xff1a;封装、继承、多态。而类和对象阶段&#xff0c;主要研究的就是封装特性。 封装&#xff1a;将数据和操作数据的方法进行有机结合&#xff0c;隐藏对象的属性和实现细节&#xff0c;仅对外公开接口来和对象进行交互…

opencv:从0到实现人脸识别

目录 opencv 人脸检查原理&#xff1a; 整体目录&#xff1a; 1.读取并展示图片 2.人脸检测 3.视频人脸检测 4.拍照保存 5 数据训练 6 人脸识别 opencv 人脸检查原理&#xff1a; OpenCV 中的人脸检测是基于哈尔特征分类器&#xff08;Haar Feature-based Cascade Cla…

zabbix中监控数据,报错返回给钉钉查看

### 在钉钉中创建群聊(同组的同学创建一个群聊)&#xff0c;在群里面添加自定义机器人 1.通过自定义webhook接入自定义服务 webhook&#xff1a;记住webhook的地址 安全设置&#xff1a;设置加签&#xff0c;只有信息内容包含签才会被机器人发送。 2.配置钉钉告警脚本 #### **安…

【计算思维】少儿编程蓝桥杯青少组计算思维题考试真题及解析B

STEMA考试-计算思维-U8级(样题) 1.浩浩的左⼿边是&#xff08; &#xff09;。 A.兰兰 B.⻉⻉ C.⻘⻘ D.浩浩 2.2时30分&#xff0c;钟⾯上时针和分针形成的⻆是什么⻆&#xff1f;&#xff08; &#xff09; A.钝⻆ B.锐⻆ C.直⻆ D.平⻆ 3.下⾯是⼀年级同学最喜欢的《⻄游记》…

如何下载 Apache + PHP + Mysql 集成安装环境并结合内网穿透工具实现公网访问内网服务

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、Cpolar杂谈 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. WampServer下载安装二. WampServer启动三. 安装cpolar内网穿透3.1 注册账号…

人工智能基础_机器学习037_多项式回归升维实战4_使用随机梯度下降模型_对天猫双十一销量数据进行预测_拟合---人工智能工作笔记0077

上一节我们使用线性回归模型最终拟合了双十一天猫销量数据,升维后的数据. 我们使用SGDRegressor的时候,随机梯度下降的时候,发现有问题, 对吧,怎么都不能拟合我们看看怎么回事现在 可以看到上面是之前的代码 上面是对数据的准备 这里我们还是修改,使用 poly=PolynomialFeatur…

开启创造力之门:掌握Vue中Slot插槽的使用技巧与灵感

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、s…

Linux_一款好用的查看系统信息的桌面软件_包名hardinfo、软件名system profiler and Benchmark

1、安装软件 对源进行更新&#xff0c;sudo apt update 安装&#xff0c;sudo apt install hardinfo 打开&#xff0c;system profiler and Benchmark 2、查看系统信息 2.1、系统基本信息_操作系统信息、内核版本、处理器等 “Summary”汇总了一些基本信息&#xff1a; 处…

鸿蒙:从0到“Hello Harmony”

效果展示 一.概述 明年华为鸿蒙就不再兼容Android生态了&#xff0c;作为拥有7亿终端用户的华为&#xff0c;建立自己的生态也是理所当然。 所以对HarmonyOS的研究也是众多开发者绕不开的坎了。 今天这篇博文主要实现一个“Hello Harmony&#xff01;”的Demo。 二.官方链接…

winform窗体、控件的简单封装,重做标题栏

1标题栏封装中使用了以下技术&#xff1a; 知识点&#xff1a; 1.父类、子类的继承&#xff1b; 2.窗体之间的继承&#xff1b; 3.自定义控件的绘制&#xff1b; 4.多线程在窗体间的应用&#xff1b; 5.窗体和控件的封装&#xff1b; 6.回调函数&#xff08;委托&#xff09;&…

OpenCV入门2——图像视频的加载与展示一些API

文章目录 题目OpenCV创建显示窗口OpenCV加载显示图片题目 OpenCV保存文件利用OpenCV从摄像头采集视频从多媒体文件中读取视频帧将视频数据录制成多媒体文件OpenCV控制鼠标关于[np.uint8](https://stackoverflow.com/questions/68387192/what-is-np-uint8) OpenCV中的TrackBar控…

探索人工智能领域——每日30个名词详解【day3】

目录 前言 正文 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请事先与我联系以…

【C++初阶】STL详解(一)string类

本专栏内容为&#xff1a;C学习专栏&#xff0c;分为初阶和进阶两部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握C。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;C &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&…

春秋云境靶场CVE-2022-25578漏洞复现(利用htaccess文件进行任意文件上传)

文章目录 前言一、CVE-2022-25578靶场概述二、CVE-2022-25578复现需要知道的知识点1、什么是htaccess文件2、上传htaccess文件的条件是什么&#xff1f;3、htaccess文件的作用是什么&#xff1f; 三、CVE-2022-32991漏洞复现1、信息收集2、找上传点3、上传后蚁剑连接getshell 总…

vmware安装MacOS以及flutter遇到的问题

安装过程&#xff1a;参考下面的文章 链接&#xff1a; 虚拟机VMware安装苹果系统macOS&#xff0c;超级详细教程&#xff0c;附文件下载&#xff0c;真教程&#xff01;&#xff01; 无限重启情况&#xff1a; &#xff08;二&#xff09; 配置虚拟机找到你的虚拟机安装文件…

氢原子波函数等概率面的绘制

氢原子波函数等概率面的绘制 归一化后的氢原子波函数

如何解决3d max渲染效果图全白这类异常问题?

通过3d max渲染效果图时&#xff0c;经常会出现3Dmax渲染效果图全黑或是3Dmax渲染效果图全白这类异常问题。可能遇到这类问题较多的都是新手朋友。不知如何解决。 3dmax渲染出现异常的问题&#xff0c;该如何高效解决呢&#xff1f;今天小编这里整理几项知识点&#xff0c;大家…

数据结构与算法【递归】Java实现

递归 递归是一种解决计算问题的方法&#xff0c;其中解决方案取决于同一类问题的更小子集。 特点&#xff1a; 自己调用自己&#xff0c;如果说每个函数对应着一种解决方案&#xff0c;自己调用自己意味着解决方案是一样的&#xff08;有规律的&#xff09;每次调用&#xf…