初识C语言指针(4)

目录

[1. 字符指针变量](#1. 字符指针变量)

[2. 数组指针变量](#2. 数组指针变量)

[3. ⼆维数组传参的本质](#3. ⼆维数组传参的本质)

[4. 函数指针变量](#4. 函数指针变量)

[5. typedef 关键字](#5. typedef 关键字)

[6. 函数指针数组](#6. 函数指针数组)

结语


1. 字符指针变量

字符指针变量就是存储字符或字符串首字符地址的变量,字符指针变量有2种使用方式。

最常用的使用方式:

​
​
int main()
{
	char ch[] = "hello bit.";
	char* pc = &ch;
	return 0;
}

​

​

还有一种使用方式:

int main()
{
 const char* pstr = "hello bit.";
 printf("%s\n", pstr);
 return 0;
}

第一种使用方式大家都知道,是将一个字符串中第一个字符(字符数组中第一个元素)的地址存放在字符指针变量中。

但是第二种字符指针 pstr 变量中存放的是这一整个常量字符串的地址吗?还是把字符串 hello bit .放到字符指针 pstr ⾥了?其实都不是,实际上也是把字符串 hello bit. ⾸字符(h)的地址放到了pstr中。

既然这两种方式存放的都是字符串第一个字符的地址,那么它们有什么差别呢?

总结:

这两种方法的相同点:它们存放的地址都是字符串首字符的地址,并且字符串都是一块连续的空间

这两种方法的不同点:第一种方式存放的是字符数组首元素的地址 (字符串首字符的地址),数组的元素是可以修改的。第二种方式存放的是常量字符串首字符的地址,而常量字符串是常量,常量是不能被修改的

下面我们来看一道来自《剑指offer》中⼀道和字符串相关的笔试题:

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 和 str2是不相等的,虽然str1 和 str2中存放的内容是一样的,但是它们两个是不同的数组,既然是两个不同的数组,存放的地址自然就不一样。

而str3 和str4是相同的这是为什么呢?这是因为str3 和 str4中存放的都是字符串常量首字符的地址,而字符串常量是不能修改的,并且有const进行修饰,所以当字符串常量被创建时,字符串常量首字符的地址先赋给了str3,当创建str4变量时,此时计算机发现已经有了一个一模一样字符串常量,就不会重新创建新的字符串常量,则是将原来创建好的字符串常量首字符的地址也赋给了str4,所以此时str3 和 str4指向的是同一个字符串常量。

2. 数组指针变量

整形指针变量存放的是整形变量的地址,字符指针变量存放的是字符变量的地址,那么数组指针变量自然就是用来存放数组的地址,能够指向数组的指针变量。

注意:这里存放的是整个数组的地址,而不是数组首元素的地

数组指针变量解析:

int main()
{
	int arr[10] = { 0 };
	int (*p)[10] = &arr;
	return 0;
}

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

那么此时数组指针变量p的类型是什么呢?如:int a=10中,a变量的类型是int 类型,char ch=' a '中ch的类型是char 类型,我们发现好像去掉变量名就是该变量的类型,所以数组指针变量p的类型就是 int (*)[10]

3. ⼆维数组传参的本质

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

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

这⾥实参是⼆维数组,形参也写成⼆维数组的形式

但是,根据数组名是数组⾸元素的地址这个规则,实际上我们传的是二维数组首元素的地址,而⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。

所以形参在接收的时候我们应该用数组指针变量接收:

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("%d ", (*(p + i))[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;
}

第一种打印方式该如何理解呢?首先p此时是二维数组首元素的地址(第一个一维数组的地址)那么*p就是第一个一维数组首元素地址,*(p+0)就是第1个一维数组首元素地址,那么*(p+0)+0就是第1个一维数组的首元素地址,*(*(p+0)+0)就是第1个一维数组的首元素。所以*(*(p+i)+j)就是第 i +1个一维数组中的 j+1个元素。而第2种和第3种方式在编译器运行时都会转换成第一种打印方式,在前面的博客中有讲过。

https://blog.csdn.net/2402_86304740/article/details/141467527https://blog.csdn.net/2402_86304740/article/details/141467527总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

4. 函数指针变量

其实函数也是有地址的,函数指针变量就是⽤来存放函数地址的,并且能够通过地址调⽤函数。

函数指针变量解析:

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的
	return 0;
}
int (*pf3) (int x, int y)
|	   |	------------
|      |		  |
|      |		  pf3指向函数(Add)的参数类型和个数的交代
|      |
|	  (pf3)函数指针变量名
|
pf3指向函数的返回类型

int (*) (int x, int y) //pf3函数指针变量的类型

那么我们该如何通过指针调用函数呢?其实&函数名和函数名拿到的都是函数的地址,所以&函数名等价于函数名,此时将&Add赋给了ps3,所以通过指针ps3也可以调用Add函数。

5. typedef 关键字

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

例如:

6. 函数指针数组

数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组,那要把多个函数的地址存到⼀个数组中,那这个数组就叫函数指针数组。

那函数指针的数组如何定义呢?

上面我们学过函数指针变量的定义,那么函数指针数组的定义就是在函数指针的变量名旁边加上数组的大小即可 ,类型为函数指针类型即 int (*) (int ,int),那么该怎么调用数组中的函数呢?

我们也是通过数组下标对数组中元素进行调用。

接下来我们通过函数指针数组完成一道练习:制作一个计算器

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 print()
{
	printf("*****************************\n");
	printf("****《1. add 》《2.sub 》****\n");
	printf("****《3. mul 》《4.div 》****\n");
	printf("********《 0. exit 》********\n");
	printf("*****************************\n");
}
void sent(int(*p)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:");
	scanf("%d %d", &x, &y);
	int ret = p(x, y);
	printf("%d\n", ret);
}
int main()
{
	int input = 0;
	int (*p[5])(int, int) = { 0,add,sub,mul,div };
	do
	{
		print();
		printf("请选择:");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			sent(p[input]);
		}
		else if (input == 0)
		{
			printf("退出成功\n");
		}
		else
		{
			printf("选择失败\n");
		}
	} while (input);
	return 0;
}

首先我们将完成加减乘除 的4个函数的地址用函数指针数组 存储起来,然后打印菜单,通过输入 input 的值进行选择需要进行哪种运算,如果选择了1,(p [ 1 ]存放的是add函数的地址)那么就进行加法运算, 然后将**(p[ 1 ]) 加法函数(add** )的地址传给sent 函数,sent 函数的形参部分用函数指针接收实参(p[ 1 ])传过来的的地址,随后输入计算的数字,通过接收到的函数的地址,调用函数,完成数值的计算,最后将计算的值返回到主函数,打印出来。如果不需要计算了,输入0即可退出程序。这道题非常巧妙的体现了函数指针数组的便捷性。

结语

以上就是本章的所有内容,本章的内容可能需要一定的时间进行消化,所以大家可以反复阅读,一定会有所收获。你们的点赞和关注就是小新创作的动力来源,在此感谢大家的观看,谢谢大家!!!

相关推荐
直裾几秒前
scala借阅图书保存记录(三)
开发语言·后端·scala
唐 城22 分钟前
curl 放弃对 Hyper Rust HTTP 后端的支持
开发语言·http·rust
嵌入式科普44 分钟前
嵌入式科普(24)从SPI和CAN通信重新理解“全双工”
c语言·stm32·can·spi·全双工·ra6m5
Web阿成1 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript
雷神乐乐1 小时前
Spring学习(一)——Sping-XML
java·学习·spring
李雨非-19期-河北工职大1 小时前
思考: 与人交际
学习
哦哦~9211 小时前
深度学习驱动的油气开发技术与应用
大数据·人工智能·深度学习·学习
码银2 小时前
【python】银行客户流失预测预处理部分,独热编码·标签编码·数据离散化处理·数据筛选·数据分割
开发语言·python
从善若水2 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust