C语言之深入理解指针(3)

1. 字符指针变量

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

int main()
{
	char ch = 'w';
	char* pc = &ch;//pc就是字符指针

	const char* p = "abcdef";//将首字符的地址存放在p中
	printf("%c\n", *p);//a

	//1.可以将字符串想象为一个字符数组,但是这个数组是不能修改的
	printf("%c\n", "abcdef"[3]);//d
	printf("%c\n", p[4]);//e

	//2.当常量字符串出现在表达式中的时候,它的值是第一个字符的地址


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

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

2. 数组指针变量

数组指针变量是什么?

之前我们学习了指针数组,指针数组是一种数组,数组中存放的是地址(指针)。

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

答案:指针变量

我们已经熟悉:

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

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

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

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

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

在这里插入图片描述

数组指针变量

int (*p)[10];

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

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

int main()
{
	int n = 100;
	int* pn = &n;

	char ch = 'w';
	char* pc = &ch;

	float f = 3.14f;
	float* pf = &f;

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int (*parr)[10] = &arr;//取出的是数组的地址
	//parr 就是数组指针
	return 0;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p1 = arr;
	//int*     int*
	int* p2 = &arr[0];
	//int*		int*

	int (*p3)[10] = & arr;//p3是数组指针
	//int(*) [10]	int(*) [10]

	return 0;
}

数组指针变量怎么初始化

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

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

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

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

数组指针类型解析:

在这里插入图片描述

3. 二维数组传参的本质

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

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

//二维数组传参,形参写的是二维数组
void print(int arr[3][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 arr[3][5] = { { 1,2,3,4,5 }, { 2,3,4,5,6 }, { 3,4,5,6,7 } };
	print(arr,3,5);//打印arr数组的内容

	return 0;
}

一维数组传参,形参可以是数组,也可以是指针
为什么?

  1. 写成数组更加直观,为了方便理解
  2. 写成指针是因为数组传参,传递的是数组第一个元素的地址

二维数组传参,形参写成数组也是可以的,非常直观,容易理解!

形参能写成指针吗?

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

在这里插入图片描述

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

//二维数组传参,形参写的是二维数组
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 arr[3][5] = { { 1,2,3,4,5 }, { 2,3,4,5,6 }, { 3,4,5,6,7 } };
	print(arr,3,5);//打印arr数组的内容

	return 0;
}

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

4. 函数指针变量

函数指针变量的创建和使用

什么是函数指针变量呢?

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

函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。

那么函数是否有地址呢?

数组名–数组首元素的地址
&数组名–整个数组的地址

函数名:函数的地址
&函数名:函数额地址

函数名就是函数的地址,当然也可以通过&函数名的方式获得函数的地址。

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

函数指针类型解析:

在这里插入图片描述

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

int main()
{
	int a = 10;
	int* pa = &a;//整型指针

	int arr[10] = { 0 };
	int (*parr)[10] = &arr;//数组指针

	//函数指针变量,是用来存放函数的地址的
	printf("%p\n", &add);
	printf("%p\n", add);

	int (*pf)(int,int) = &add;//pf就是函数指针变量
	int ret = add(3, 5);
	printf("%d\n", ret);//8

	int ret2 = (*pf)(4, 9);
	printf("%d\n", ret2);//13

	int(*pf2)(int, int) = add;
	int ret3 = (*pf2)(5, 6);
	printf("%d\n", ret3);//11

	int ret4 = pf2(5, 6);
	printf("%d\n", ret4);//11 

	return 0;

}

简单的一段小代码

char* test(int a, char c)
{
	return NULL;
}

int main()
{
	char* (*pt)(int,char)=test;//____=test
	return 0;
}

两段有趣的代码

代码1

int main()
{
	(*(void(*)())0)();
	return 0;
}

在这里插入图片描述

代码2

int main()
{
	void (*signal(int, void(*)(int)))(int);
	return 0;
}

在这里插入图片描述

typedef关键字

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

比如:你觉得unsgned int 写起来不方便,如果能写成uint就方便多了

typedef unsigned int uint;

int main()
{
	unsigned int num;
	uint num2;

	return 0;
}

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

typedef int* ptr_t;

但是对于驻足指针和函数指针稍微有点区别

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

typedef int(*pArr_t)[10];

int main()
{
	pArr_t pa;
	int(*bb)[10];
	return 0;
}

函数指针类型的重命名也是一样的

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

typedef int(*pf_t)(int, int);

int main()
{
	pf_t pf;
	int(*pf2)(int, int);

	return 0;
}

5. 函数指针数组

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

指针数组

char* arr[ 5 ];//字符指针数组
int* arr[ 6 ];//整型指针数组
指针可以放在数组中

把函数的地址存放到一个数组中,那这个数组就叫函数指针数组

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 (*pf)(int, int) = add;//pf是函数指针
	int (*pfarr[4])(int, int) = {add, sub, mul, div};//存放函数指针的数组
								 //0   1   2     3
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = pfarr[i](6, 2);
		printf("%d\n", ret);
	}
	return 0;
}

6. 转移表

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

举例:计算器的一般实现:

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("请输入两个操作数");
			ret = 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;
		case 0:
			printf("退出计算器");
			break;
		default:
			printf("选择错误,重新选择");
			break;
		}
	} while (input);
	return 0;
}

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

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();
		int (*pfarr[])(int, int) = { NULL,add,sub,mul,div };
		//							//0   1   2   3    4
		printf("请选择:");
		scanf("%d", &input);
		//函数指针数组的方式解决


		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数");
			scanf("%d %d", &x, &y);
			ret = pfarr[input](x, y);
			printf("%d\n", ret);
		}
		else {
			printf("选择错误,重新选择");
		}
	} while (input);
		
	return 0;
}
  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值