【C语言】指针的进阶篇,深入理解指针和数组,函数之间的关系

欢迎来CILMY23的博客喔,本期系列为【C语言】指针的进阶篇,深入理解指针和数组,函数之间的关系,图文讲解其他指针类型以及指针和数组,函数之间的关系,带大家更深刻理解指针,以及数组+指针,指针和函数的用法,感谢观看,支持的可以给个赞哇。

前言

在上一篇博客中,我们了解了strlen的模拟实现,以及冒泡排序,并且为了熟悉指针数组,我们还学习了用指针数组来模拟实现二维数组,本期博客将用其他指针类型来开篇,并学习指针和数组,函数之间的关系。 

目录

一、字符指针变量

 二、数组指针变量

三、二维数组传参

四、函数指针变量

五、typedef关键字

六、函数指针数组

七、转移表


一、字符指针变量

 什么是字符指针变量?

看下列代码:

#include<stdio.h>

int main()
{
	char ch = 'c';
	char* pc = &ch;

	return 0;
}

上列代码中,pc就是字符指针变量,那如果后面的不是&ch,而是一个字符串又是如何存放在字符指针变量中的呢?

#include<stdio.h>

int main()
{
	char* pc = "abcde";
	printf("%c ", *pc);

	return 0;
}

我们通过调试发现,最后的输出结果为a。所以字符串在存入指针变量的时候,是将首字符a的地址存入的。 

 接下来看以下代码:

#include<stdio.h>

int main()
{
	char str1[] = "hello SILMY23";
	char str2[] = "hello SILMY23";

	const char* str3 = "hello SILMY23";
	const char* str4 = "hello SILMY23";

	if (str1 == str2)
		printf("same\n");
	else
		printf("%p\n%p\n",str1,str2);

	if (str3 == str4)
		printf("same\n");
	else
		printf("%p\n%p\n", str3, str4);
	return 0;
}

我们假设有两个字符数组,和两个用const修饰的字符指针变量,存放字符串hello SILMY23,我们看看在内存上它们是如何存放的?

运行结果如下:

 

我们发现在内存上,用const修饰的,它们是公用同一块空间的,也就是空间图如下所示:

 

用const修饰的常量字符串是不能被修改的,既然不能修改那么同样的内容就没有必要再开辟另外一个空间进行存放,所以两个str3和str4所指向的空间是相同的。 

 二、数组指针变量

我们在上一篇学习到,指针数组是用来存放指针的数组,那这一块数组指针,我们仍然采用类比的方式,字符指针,指向字符,是用来存放字符地址, 整型指针,指向整型,是用来存放整型地址,所以数组指针是指向数组,用来存放数组地址。例如:

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

&arr拿到数组的地址。 p就是数组指针。

数组指针和指针数组

int* p[10];//指针数组
int (*p)[10];//数组指针

 字符数组指针:

char cha[8];
char (*pc)[8] = &cha;

所以数组指针类型:

int (*)[10]

char (*)[8] 

那数组指针如何初始化呢?就是用数组地址来初始化。

 那数组指针和指针数组的用法得分开

#include<stdio.h>

int main()
{
	//指针数组
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p[] = { arr1 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[0][i]);
	}
	printf("\n");
	//数组指针
	int(*p1)[10] = &arr1;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", (*p)[i]);
	}

	return 0;
}

结果如下:

 

上述代码的分布图如下: 

总结:

指针数组是数组内的元素是一个个指针而数组指针是指向数组的指针

指针数组是数组,而数组指针就是个指针。

三、二维数组传参

我们在上一篇博客中,用指针数组模拟了二维数组,那二维数组传参的本质又是什么呢?

我们先来看二维数组的传参使用

#include<stdio.h>

void print(int arr[2][5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr1[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print(arr1, 2, 5);

	return 0;
}

二维数组的传参本质其实还是传了数组首元素的地址,传的是数组第一行第一列的地址,所以我们函数写形参还是可以写指针形式接收。

二维数组的每一行可以看作一个一维数组,这个一维数组可以看作是二维数组的一个元素,所以二维数组也可以认为是一维数组的数组,所以二维数组的首元素地址就是第一行的地址,也就是一个一维数组的地址。所以:

*(arr+i) == arr[i]

代码形参可以改造成这种,形参部分写成指向第一行的数组指针。而arr[i][j]==*(*(arr+i)+j)

#include<stdio.h>

void print(int (*arr)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(arr + i) + j));
		}
		printf("\n");
	}
}

int main()
{
	int arr1[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print(arr1, 2, 5);

	return 0;
}

四、函数指针变量

我们已经接触过了很多指针变量,整型指针,数组指针,字符指针,那现在要新认识一个函数指,那顾名思义,函数指针,就是用来存放函数的地址,那函数名是否就是地址呢?

我们看之前写下的代码:

#include<stdio.h>

void print(int (*arr)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(arr + i) + j));
		}
		printf("\n");
	}
}

int main()
{
	int arr1[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p ", print);
	printf("%p ", &print);
	return 0;
}

结果如下: 

 

我们发现,无论是函数名还是&函数名,都是函数的地址,而在数组当中,数组名和&数组名是不一样的。 

那现在要将print存入指针变量,在写法上还是类似数组指针的

那如何通过p调用print函数呢?

	(*p)(arr1, 2, 5);

首先是对p解引用获得函数地址,其次是传参。这样就可以通过函数指针调用函数了。

 但其实不写也是可以的。但写上(*)更容易理解。 

五、typedef关键字

typedef关键字是用来进行类型重命名的,可以将复杂的类型简单化:

//重命名数据类型
typedef unsigned int uint;
typedef double dl;
//重命名指针类型
typedef char* pcr;
typedef int* pint;
//重命名数组指针类型
typedef int(*parr_t)[5];
//重命名函数指针类型
typedef void(*pfun_t)(int);
//新的类型名必须在*的右边

六、函数指针数组

函数指针数组?说白了就是一个数组,这里面存放的都是函数指针的地址

在第四个知识点我们写了print函数,现在我们想把它放进一个函数指针数组里

void (*parr[1])(int, int, int) = { print };

函数指针类型是需要一样的,唯一不一样的是变量名我们给了一个数组来实现函数指针数组。 

七、转移表

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

普通四则计算器的实现:

#include <stdio.h>

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;
}


void menu()
{
	printf("***************************\n");
	printf("*****  1. add  2.sub  *****\n");
	printf("*****  3. mul  4.div  *****\n");
	printf("*****  0. exit        *****\n");
	printf("***************************\n");
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	int(*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div};
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
	
		if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else if(input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else
		{
			printf("选择错误,请重新选择");
		}
	} while (input);

	return 0;
}

如果利用函数指针数组来简化代码,代码就会变得相对简短一些,增添一些功能,也只会从pfArr中添加。我们把这种用函数指针数组拿来做中间板的情况,就叫转移表。

感谢各位同伴的支持,本期指针进阶篇就讲解到这啦,如果你觉得写的不错的话,可以给个赞,若有不足,欢迎各位在评论区讨论。  

最近更新

  1. TCP协议是安全的吗?

    2024-02-15 17:06:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-15 17:06:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-15 17:06:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-15 17:06:01       18 阅读

热门阅读

  1. Rust的if let语法:更简洁的模式匹配

    2024-02-15 17:06:01       26 阅读
  2. 【ASP.NET 6 Web Api 全栈开发实战】--前言

    2024-02-15 17:06:01       26 阅读
  3. 作业2024/2/15

    2024-02-15 17:06:01       26 阅读
  4. D. Yet Another Sorting Problem - 树状数组求逆序数

    2024-02-15 17:06:01       28 阅读
  5. AGV-产品设计概述

    2024-02-15 17:06:01       32 阅读
  6. 聚集索引选取规则

    2024-02-15 17:06:01       30 阅读
  7. sql深度优化

    2024-02-15 17:06:01       34 阅读