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

数组指针与函数指针

  • 前言
  • 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【华为HCIP | 华为数通工程师】ISIS 高频题(1)

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

机器人导航+OPENCV透视变换示例代码

透视变换又称四点变换&#xff0c;所以不能用于5边形这样的图形变换&#xff0c;不是真正的透视变换&#xff0c;但是这个方法可以把机器人看到的图像转换为俯视图&#xff0c;这样就可以建立地图&#xff0c;要不然怎么建立地图呢。 void CrelaxMyFriendDlg::OnBnClickedOk()…

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

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

PCL 提取点云边界轮廓-AC方法、平面轮廓

一、概述 PCL点云边界特征检测 &#xff08;附完整代码 C&#xff09;_pcl计算点云特征值_McQueen_LT的博客-CSDN博客 在点云的边界特征检测&#xff08;网格模型的边界特征检测已经是一个确定性问题了&#xff0c;见 网格模型边界检测&#xff09;方面&#xff0c;PCL中有一…

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

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

【Hello Go】Go语言基础类型

Go语言基础类型 基础类型命名变量变量声明变量初始化变量赋值匿名变量 常量字面常量常量定义iota枚举 基础数据类型分类 fmt包的标准输入输出格式说明输入类型转换类型取别名 基础类型 命名 Go语言中的命名遵循下面的几个规则 必须以字母或者是下划线开头不能使用Go语言中的…

Django——模板层、模型层

模板层 一. 模版语法 {{ }}: 变量相关 {% %}: 逻辑相关 1. 注释是代码的母亲 {# ... #} 2. 基本数据类型传值 int1 123 float1 11.11 str1 我也想奔现 bool1 True list1 [小红, 姗姗, 花花, 茹茹] tuple1 (111, 222, 333, 444) dict1 {username: jason, age: 18, i…

单片机的冷启动、热启动、复位

一文看懂STC单片机冷启动和复位有什么区别-电子发烧友网 单片机的冷启动、热启动和复位是不同的启动或重置方式&#xff0c;它们在系统状态和初始化方面有所不同&#xff1a; 1.冷启动&#xff08;Cold Start&#xff09;&#xff1a; 定义&#xff1a; 冷启动是指系统从完全关…

第14届蓝桥杯青少组python试题解析:22年10月选拔赛

选择题 T1. 执行print (5%3) 语句后&#xff0c;输出的结果是 ( ) 0 1 2 3 T2. 以下选项中&#xff0c;哪一个是乘法运算符?&#xff08;&#xff09; % // * ** T3. 已知x3&#xff0c;求x//2x**2的运算结果? 7.5 10 8 10.5 T4. 以下选项中&#xff0c;对下面程序的打印…

Unity地面交互效果目录

大家好&#xff0c;我是阿赵。   之前写了几篇关于地形交互、地面轨迹、脚印效果实现的博文。虽然写的篇数不多&#xff0c;但里面也包含了不少基础知识&#xff0c;比如局部UV采样、法线动态混合、曲面细分等知识&#xff0c;这些都是可以和别的效果组合在一起&#xff0c;做…

【刷题专栏—突破思维】142. 环形链表 II

前言&#xff1a;本篇博客将讲解三个OJ题&#xff0c;前两个作为铺垫&#xff0c;最后完成环形链表的节点的寻找 文章目录 一、160. 相交链表二、141. 环形链表三、142. 环形链表II 一、160. 相交链表 题目链接&#xff1a;LeetCode—相交链表 题目描述&#xff1a; 给你两个单…

排查线程阻塞问题

案例代码 package first;import java.util.concurrent.TimeUnit;public class DeadLock {private static volatile Object lock new Object();public static void main(String[] args) {new Thread(() -> {test1();}).start();new Thread(() -> {test2();}).start();}p…

队列的实现---超详细

队列的实现—超详细 文章目录 队列的实现---超详细一、队列的模型二、代码实现以及测试用例①队列初始化②入队③出队④输出队头⑤输出队尾⑥判断队列是否为空⑦队列的长度⑧队列的销毁⑨测试用例 一、队列的模型 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在…

Semantic Kernel 学习笔记2

本来想白瞟免费Bing Search API如下&#xff0c;但是报错无法链接利用免费的必应 Bing 自定义搜索打造站内全文搜索_bing_subscription_key-CSDN博客 改成按照官方推荐申请&#xff0c;并在.env文件中添加BING_API_KEY""字段。 1. 打开https://www.microsoft.com/en-…

《QT从基础到进阶·二十七》进度条QProgressBar

ui.ProgressBar.setValue(45); //45% ui.ProgressBar.setMin(0); ui.ProgressBar.setMax(255);0到100分为255份&#xff0c;值为215时&#xff0c;进度条为100/255*215 84% 点击主界面弹出进度条QProgressDialog 常用功能&#xff1a; setWindowFlags(Qt::Dialog | Qt::Cu…

基于象群算法优化概率神经网络PNN的分类预测 - 附代码

基于象群算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于象群算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于象群优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

【C语言】自定义类型:结构体、枚举、联合

简单不先于复杂&#xff0c;而是在复杂之后 文章目录 1. 结构体的声明1.1 结构的基础知识1.2 结构的声明1.3 特殊的声明1.4 结构体的自引用1.5 结构体变量的定义和初始化1.6 结构体内存对齐1.7 修改默认对齐数1.8 结构体传参 2. 位段2.1 什么是位段2.2 位段的内存分配2.3 位段的…

解决:java: 错误: 不支持发行版本 5 最有效方法

报错信息如图&#xff1a; 直接上终极方法&#xff1a; 修改配置文件 如图找到settings.xml文件 在标签中间插入如下代码&#xff08;jdk更改为自己电脑上的版本&#xff09; <profile><id>development</id><activation><jdk>11</jdk><…

深入Rust:探索所有权和借用机制

大家好&#xff01;我是lincyang。 今天&#xff0c;我们将一起深入探索Rust语言中的一个核心概念&#xff1a;所有权和借用机制。 这些特性是Rust区别于其他语言的重要特点&#xff0c;它们在内存管理和并发编程中扮演着关键角色。 一、Rust所有权机制 1. 什么是所有权&#x…

USB复合设备构建CDC+HID鼠标键盘套装

最近需要做一个小工具&#xff0c;要用到USB CDCHID设备。又重新研究了一下USB协议和STM32的USB驱动库&#xff0c;也踩了不少坑&#xff0c;因此把代码修改过程记录一下。 开发环境&#xff1a; ST-LINK v2 STM32H743开发板 PC windows 11 cubeMX v6.9.2 cubeIDE v1.13.2 cub…