C语言之进阶指针上 -- (字符指针 => 指向函数指针数组的指针)

✨✨✨学习的道路很枯燥,希望我们能并肩走下来!

编程真是一件很奇妙的东西。你只是浅尝辄止,那么只会觉得枯燥乏味,像对待任务似的应付它。但你如果深入探索,就会发现其中的奇妙,了解许多所不知道的原理。知识的力量让你沉醉,甘愿深陷其中并发现宝藏。


前言

我们之前只是简单了解了指针,为了进一步深入,本篇指针进阶的知识,会让我们更全面了解指针的使用,如有错误,请在评论区指正,让我们一起交流,共同进步!

文章重点概况

  • 字符指针
  • 数组指针
  • 指针数组
  • 数组传参和指针传参
  • 函数指针
  • 函数指针数组
  • 指向函数指针数组的指针

我们之前简单了解过指针的内容,本篇就为小伙伴更进一步讲解指针,相信你看完本篇,会对指针有进一步了解!

1.字符指针

字符指针使用:char* ;
[注]:字符指针存储的字符串的首地址(把一个常量字符串的首字符 h 的地址存放到指针变量pa中);
对于字符串“ ”是常量字符串不能被改变.

代码示范:

#include<stdio.h>
int main()
{
	//存储字符时用char
	//存储字符地址时就需要字符指针char*
	char ch = 'w';
	char* pc = &ch;
	*pc = 'w';
	
	//存储字符串可以用字符数组
	//也可以使用字符指针
	char arr[] = "hello word";
	char* pa = "hello word";
	//字符指针存储的字符串的首地址(把一个常量字符串的首字符 h 的地址存放到指针变量pa中)
	//对于字符串“”是常量字符串不能被改变
	*pa = 'w';//err
	return 0;
}

我们来一道题看一下字符指针使用:

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

上述代码答案是:str1 and str2 are not same ,str3 and str4 are same.(str1,str2,str3,str4都是地址)
原因:这是因为字符数组str1和字符数组str2存储“hello word”都会重新开辟一块空间来储存,它们的地址是不一样的; 而字符指针指向的字符串都是相同的常量字符串不能够被改变,C/C++会把常量字符串存储到单独的一个内存区域,不同指针指向同一字符串时指向同一块内存,所以str3,str4指向同一空间,地址相同.

2.指针数组

指针数组:存放指针的数组.

通过字符指针数组,整型指针数组的代码,让我们了解一下,指针数组的使用.
代码示范:

int main()
{
	//字符指针数组
	char* arr[5] = { "abcdef","zhang","wang","zhao","qian" };
	//这里指针数组存储每个字符串的首字符的地址
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s\n", arr[i]);
	}

	//整型指针数组
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int arr4[5] = { 4,5,6,7,8 };
	int arr5[5] = { 5,6,7,8,9 };
	int* arr[5] = { arr1,arr2,arr3,arr4,arr5 };
	int j = 0;
	for (i = 0; i < 5; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
			//也可以这样写printf("%d ", *(*(arr + i) + j));
			//*(arr+i)==arr[i]
			//*(*(arr + i) + j==arr[i][j]
		}
		printf("\n");
	}
	return 0;
}

3.数组指针

3.1数组指针的定义

整形指针:指向整型数据的指针- int * a;
字符指针:指向字符数据的指针 - char * ch;
数组指=:是指针,指向数组的指针
例如:
int ( * p)[10]: p先和 * 结合,说明p是一个指针变量,指向的是一个大小为10个整型的数组。
【注】:区分数组指针与指针数组
[ ]的优先级要高于 * 号的,所以必须加上()来保证 p 先和 * 结合。

int* p1[10]; // ->指针数组
int (*p2)[10]; // ->数组指针

3.2&数组名和数组名

我们之前讲解过,这里我们再来简单的回忆一下.我们知道数组指针存储的是数组的地址,我们这里需要注意arr与&arr的区别!

我们知道arr与&arr的值是一样的,都指向数组首元素,但代表的意义并不相同.
arr与&arr区别:
&arr 表示的是数组的地址,而不是数组首元素的地址.数组的地址+1(整型数组条件下),跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.
arr表示首元素地址,arr+1在整型情况下跳过4个字节.

代码示范:

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);

	printf("%p\n", arr+1);
	printf("%p\n", &arr[0]+1);
	printf("%p\n", &arr+1);
	return 0;
}

代码结果: 在这里插入图片描述

解释:C:十六进制的12,3C=316^1+12=60,
64=6
16^1+4=100;
3C与64之间差40个字节==一个arr数组的长度

3.3数组指针的使用

数组指针在一维数组中使用的较少,一般使用在二维数组.

代码1

	//不常用
	int arr[5] = { 1,2,3,4,5 };
	int(*pa)[5] = &arr;

代码2

void text(int(*pa)[10], int s)
{
	int i = 0;
	for (i = 0; i < s; i++)
	{
		printf("%d ", (*pa)[i]);
		//*pa == *&arr => arr
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	text2(&arr, sz);
	return 0;
}

代码3
【注】数组名arr,表示首元素的地址
但是二维数组的首元素是二维数组的第一行;
这里传递的arr,相当于第一行的地址,是一维数组的地址,需要数组指针来接收.

void text1(int(*pa)[4], int r,int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(pa + i) + j));
			//pa+i找到第i行的地址,解引用指向地址
			//*(pa+i)==pa[i],
			//+j是找到第i行第j列元素的地址,解引用取值
		}
		printf("\n");
	}
}
int main()
{
	int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,0 };
	int sz = sizeof(arr1) / sizeof(arr1[0]);
	text1(arr1, 3, 4);
	return 0;
}

4.数组参数、指针参数

数组和指针都需要传参给函数,那么如何设计正确呢?

4.1一维数组传参

#include <stdio.h>
void test(int arr[])//传入数组首元素地址,数组接收 - 正确
{}
void test(int arr[10])//传入数组首元素地址,数组接收 - 正确,([]中的数写与不写一样)
{}
void test(int *arr)//传入数组首元素地址,指针接收 - 正确
{}
void test2(int *arr[20])//传入数组地址,指针数组接收 - 正确
{}
void test2(int **arr)//传入数组首元素地址,二级指针接收 - 正确(数组每个元素都是int*,每个地址也就是一级指针的地址,需要二级指针接收)
{}
int main()
{
	int arr[10] = {0};
	int *arr2[20] = {0};
	test(arr);
	test2(arr2);
}

4.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])//错误,接收的是地址不是数组
{}
void test(int (*arr)[5])//正确,数组指针接收二维数组第一行的地址
{}
void test(int **arr)//错误,需要数组指针接收,二级指针接收一级指针的地址
{}
int main()
{
	int arr[3][5] = {0};
	test(arr);
	//传入的是第一行的地址
}

4.3一级指针传参

void test1(int *p)
{}
//test1函数能接收什么参数?
//接收一维数组地址
//接收一级指针变量
//接收变量地址

4.4二级指针传参

#include <stdio.h>
void test(int** ptr)
{}
//test函数能接收什么参数?
//接收二级指针变量
//接收一级指针地址&p
//接收指针数组地址,每个元素都是一级指针地址

5.函数指针

函数指针:指向函数的指针

怎么存放函数指针?
我们看一下如下示例来判断一下!

void text()
{
}
int main()
{
	void (*p1)();
	void *p2();
	retunr 0;
}

解释:
p1:p1可以存放函数地址。p1先和*结合,说明p1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
p2:()优先级大于 * 号,p2先与()结合,说明p2是函数,返回值类型为void * .

说了怎么多,函数指针怎么用呢?我们来看两个代码理解一下函数指针.

//代码1
(*(void (*)())0)();//把0当做一个函数的地址
//从0为突破口,0起前面的()为强制类型转换,转换为void (*)()类型的函数指针,再*解引用0的地址,调用函数

//代码2
void (*signal(int , void(*)(int)))(int);
//一步步分析,
//找到signal判断是否为函数调用,定义还是声明
//分析为函数声明
//声明函数叫:signal
//signal与()结合为函数,
//第一个参数类型int,
//signal函数的第二个参数是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void
//signal函数的返回类型也是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void

//怎么看有可能有点复杂,我们可以重定义
typedef void (*)(int) ptf;
ptf signal(int,ptf);
//这样是不是简洁了一些,
//这里我们不一定要写成复杂的样子,只要求我们在遇见时可以看懂即可!

6.函数指针数组

函数指针数组:存放函数指针的数组
指针数组:int * arr[10];
如何定义函数指针数组呢?我们可以通过函数指针,指针数组来模仿.

int arr[10];
int* arr[10];

int (*p)();
int (*parr[10])();
//parr 先和 [] 结合,说明 parr是数组
//除去分析的parr[10],数组中储存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("%d\n", ret);
			break;
		case 2:
			printf("请输入两个整数值:");
			scanf("%d %d", &x, &y);
			ret=Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个整数值:");
			scanf("%d %d", &x, &y);
			ret=Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个整数值:");
			scanf("%d %d", &x, &y);
			ret=Div(x, y);
			printf("%d\n", ret);
			break;
		default :
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

通过上述代码我们看到了使用 switch语句 会有 经常重复的语句 ,当我们增加算数功能时%,移位操作等只能添加case语句,会比较繁琐,为了 更简便,我们使用函数指针数组 ,我们来看一下吧!!!
简化代码:

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;
	int (*parr[5])(int, int) = { 0, Add,Sub,Mul,Div };
	do
	{
		menu();
		printf("请输入:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}

		if (input >= 1 && input <= 4)
		{
			printf("请输入两个整数值:");
			scanf("%d %d", &x, &y);
			ret = parr[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("输入错误\n");
		}
	} while (input);
	return 0;
}

我们通过函数指针数组简化了复杂的switch语句,通过输入的input可以找到存储的计算函数,将x,y参入函数得到返回值.也可以更好的添加内容,

优点:①函数指针数组定义为5,让输入的1,2,3,4对应+ - * / 更加方便计算.
②可改变函数指针数组的大小,添加% ,<<等操作.

7.指向函数指针数组的指针

函数数组指针:指向函数数组的指针
指针指向一个数组,数组元素为函数指针.

//函数指针
void (*p)(int)
//函数指针数组
void (*parr[10])(int)
//函数指针数组 -> 变为函数指针数组指针
void (* (*str)[10])(int);

这里我们只要见到认识就可以了.


总结

✨✨✨各位读友,本篇分享到内容是否更好的让你理解了指针的知识,对指针的使用更加灵活,如果对你有帮助给个👍赞鼓励一下吧!!
🎉🎉🎉一遇挫折就灰心丧气的人,永远是个失败者。而一向努力奋斗,坚韧不拔的人会走向成功。让我们永不放弃,一直向前吧!
感谢每一位一起走到这的伙伴,我们可以一起交流进步!!!一起加油吧!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值