深入理解指针(3)

在学习之前的深入理解指针(2)中我们对数组名以及指针数组等知识进行了深入的理解,并且使用这些知识实现了冒泡排序;指针数组模拟二维数组,现在将继续指针其他指针的学习,一起加油吧!!!

0815b4f6980b45858e23f8787d6d8d2c.png

 

1.字符指针变量

我们知道当要存放一个字符的地址需要用到字符指针,这时就可以对指针进行解引用等操作
 例如以下代码

int main()
{
	char a = 'w';
	char* p = &a;
    *p='g';

	return 0;
}

其实字符指针还有另外一种使用方式,如以下p1指针变量

int main()
{
	int* p1 = "abcdef";

    char arr[] = "abcdef";
    char* p2 = arr;
	return 0;
}

这种方法如果你难以理解,这时可以类比先将字符串存放在数组里,再将首元素地址存放在char*p2中,在这的p2存放的是a的地址

那么这时就有值得思考的点了:以上代码中的两段代码有什么差异呢?指针变量p1里面存放的是整个字符串吗?

a5f927a08fa64daebd4f23e739231ec9.png
其实字符数组里面的值是可以修改的,而在常量字符串是不能被修改的

当对存放常量字符串的字符指针修改里面的内容时,编译器就会报错
9535ba11a3ca462184e5fbadf97181f1.png

其实常量常量字符串也是将首元素的地址赋值给指针变量p1,而不是将整个字符串都放到p1中

打印*p1的值验证以上结论

e68c0dbcd6e8413e99b35c0ce20e6375.png

那么将常量字符串存放在指针变量中时候,要打印出这个常量字符串要怎么做呢?

#include<stdio.h>
int main()
{
	char* p1 = "abcdef";
    printf("%s", p1);
	return 0;
}

这时只需提供首字符的地址就可以打印出整个字符串
1e21c457116b4d33921c18e59a7dc132.png

 我们之前学习过const可以修饰指针变量,当在*左边时可以防止指针所指向的值被修改,所以当出现存储常量字符串的字符指针变量时,就可以加上const

 

练习:

《剑指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;
}

 你能回答以上代码的输出结果吗?

运行程序输出结果如下

8564eac0ab07445a9b3649113c4d1efb.png

为什么输出结果是这样的呢?

首先str1和str2是两个不同的的数组,每个数组分配到了不同的内存空间,又因为数组名表示首元素的地址,所以两数组首元素地址不同,str1!=str2,输出结果就为str1 and str2 are not same

str3和str4指向的是⼀个同⼀个常量字符串,C/C++会把常量字符串存储到单独的⼀个内存区域
当几个指针指向同⼀个字符串的时候,他们实际会指向同一块内存,因此str1与str2的地址相同,输出结果就为str3 and str4 are same

2. 数组指针变量

1.数组指针变量是什么?

之前学过指针数组是存放元素是指针的数组,那数组指针是数组还是指针呢?

我们学习过整形指针,字符指针等,整形指针是指向整形变量的指针字符指针是指向字符的指针,因此就可以类比出数组指针就是指向数组的指针,所以数组指针也是指针

int arr[5]={1,2,3,4,5};

现在我们要写一个数组指针存放arr数组的地址应该怎么表示呢?是写成int*p[5]吗?

以上这种写法是错误的因为p先和[5]结合了所以是一个数组,存放的元素类型是整形,是一个指针数组,所以正确的写法是先让p与*结合使得p是一个指针变量,这就需要将p用括号括起来,int(*p)[5]才是正确的写法,类型是int(*)[5]

 

2.数组指针变量怎么初始化

int main()
{
int arr[5]={1,2,3,4,5};
int(*p)[5]=&arr;

return 0;
}

在数组指针的初始化时,&arr是取出整个数组的地址 

注:在了解完数组指针的类型后就可以解释之前讲过当整形数组元素为10个时,&arr+1会跳过40个字节是因为&arr的类型是int(*)[10]

27ac0153204e4d9d9e823b08414c1ac9.png

 3. 二维数组传参的本质

在之前利用函数打印二维数组是这样写的

#include<stdio.h>

void test(int  arr[3][5], int x, int y)
{
	int i = 0;
	int j = 0;
	
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; 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} };
	test(arr, 3, 5);
	return 0;

	return 0;
}

5ee33bbc788f4df79af57f3a8ec0a91d.png

cc163f856c7a49f582e9acee7b0d3245.png

在学习数组时我们知道二维数组可以看作是由多个一维数组组成,又因为数组名表示首元素的地址,那么二维数组的首元素就是第一行,是个一维数组,因此以上代码实参部分的arr传的是第一行这个一维数组的地址

因为arr表示的是一个数组的地址,这时就需要用到数组指针来接收,所以这时test函数内接收arr的形参就可以改写为int(*arr)[5]

注:现在就可以解释了为什么在之前说二维数组传参的时候,形参内的列不能省略;行可以省略,这是因为形参接收的是一个数组指针,只知道传过来的数组有几个元素,形参内的行没有意义,写上只是便于理解

#include<stdio.h>

void test(int(*arr)[5], int x, int y)
{
	int i = 0;
	int j = 0;
	
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; 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} };
	test(arr, 3, 5);
	return 0;

	return 0;
}

3ea289c6881d4d7aaaf550fc05b78bdf.png

代码运行输出结果
f0fc781d229d4f1bbe7354dc6b06224b.png

 在以上代码中因为*(arr+i)=arr[i],*(*(arr+i)+j)=*(arr+i)[j]所以*(*(arr+i)+j)也可以写成arr[i][j]

总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式,但形参本质接收的是指针

 

4. 函数指针变量

1.函数指针变量的创建

在之前学习中了解到整形变量有地址,数组也有地址,那么函数也有地址吗?
答案是有的


bfe02eec8dad4f2ba3a1b6c607f37eec.png

 例如以上Add函数的地址就可打印出

6b8d356798d04f9a99034500e37dd627.png

在以上代码中可以发现函数名就是函数的地址 

现在就可以把函数地址存放起来就创建函数指针变量

int (*p)(int, int) = &Add;

注意:在创建函数指针时必须要用括号将*与指针变量名括起来,要不然变量就会先与参数结合成函数752095d6c18d430d9fecb5740440f1cf.png

 在函数参数部分,参数名无作用,参数名可写可不写

在之前学习数组指针时数组指针去掉数组名就是指针的类型,在函数指针中也是类似的,去掉函数名就是函数指针的类型

例如以上函数指针的类型就是 int (*)(int, int) = &Add

2.函数指针变量的使用

在学习了数组指针后就可以通过指针来调用函数

#include<stdio.h>

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

int main()
{
	
	int (*p)(int, int) = &Add;//&Add也可以写成Add
	int n = (*p)(2, 3);
	printf("%d", n)

	return 0;
}

 0ea3c6c01b5a46f3922e541bdda6b83f.png

我们之前都是通过函数名的方式调用函数, 其实在函数指针中也可以直接通过指针调用函数

例如以上代码int n = (*p)(2, 3)就可以改写为以下形式

int n = p(2, 3);

3.两段有趣的代码 

代码1

(*(void (*)())0)();

来对这段代码进行分析 

 f7a47af06bc94886be7f5d383982ea19.png

首先void(*)()是指函数指针的类型是(void (*)(),而0前面的括号将0强制类型转换为((void (*)( )),最后通过指针调用函数

所以以上这段代码的意思就是调用0地址处存放的那个函数,其中0地址处的函数是没有参数的,返回类型为void

代码2

void (*signal(int , void(*)(int)))(int);

来对这段代码进行分析
7ad74993d3864ee4b2f66a9112441c62.png

 首先signal(int, void(*)(int))内的intvoid(*)(int)signal的两个参数
 第一个参数int,第二个参数的类型是一种函数指针(该函数指针指向的函数参数类型是int,函数  返回类型是void)
signal是函数名
因为函数名先与参数结合了,所以剩下的void (*)(int)是该函数的返回类型,是一个函数指针类型

有的读者会认为以上代码不是应该写成以下这种形式吗?

void (*)(int) signal(int, void(*)(int))

这种形式看着更易于了解但是错误

因为*signal(int, void(*)(int))内函数参数无参数名也无参数的具体值,所以这是一次函数声明

 

4. typedef 关键字

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

例如, unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用typedef:

typedef unsigned int uint;
//将unsigned int 重命名为uint

那如果是指针的类型重命名,应该怎么表示呢?

typedef int* pint_t;
//将int* 重命名为pint_t

在数组指针与函数指针中的类型重命名与以上的有些差异

在数组指针与函数指针中是将重命名后的类型放在*的旁边

例如以下两个对数组指针返回类型与函数指针返回类型的重命名

typedef int (*parr_t)(int);
typedef int(*ar_t)[5];
int Add(int x, int y)
{
	return x + y;
}

typedef int (*parr_t)(int, int);
typedef int(*arr_t)[5];


int main()
{
	int arr[] = { 1,2,3,4,5 };
	arr_t p1 = arr;
	parr_t p2 = Add;

	return 0;
}

5. 函数指针数组

通过之前的学习我们知道数组是一个存放相同类型数据的存储空间,所以可以得出函数指针数组是指数组内的每个元素类型都是函数指针

例如以下函数指针数组:

int (*parr1[3])();

parr1 先和 [ ] 结合,说明 parr1是数组,数组的内容是什么呢?
int (*)() 类型的函数指针 

 

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(*pf1)(int, int) = Add;
	int(*pf2)(int, int) = Sub;
	int(*pf3)(int, int) = Mul;
	int(*pf4)(int, int) = Div;

	return 0;
}

在以上代码中有什么方法可以简化呢?
由于pf1,pf2,pf3,pf4都是int*(*)(int,int)类型的函数指针,所以就可以创建一个函数指针数组来存放各函数的指针

所以主函数内可改写为int(*pf[4])(int, int) = { Add,Sub,Mul,Div };

 

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=0;
    int y=0;
	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;
}

 在以上这种方法中存在一些问题
首先是以上的代码只能计算+ - * / 的运算,如果要进行其他的运算就需要在do while循环里面继续在switch内加上case语句,这就会使得main函数内代码很臃肿


这时有什么办法能简化代码呢?

在学习了函数指针数组后,就可以使用函数指针数组于以上程序中来简化代码了

#include <stdio.h>
#define count 5
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 = 0;
	int y = 0;
	int input = 1;
	int ret = 0;
	int(*pf[count])(int, int) = { 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 > 0 && input < count)
		{
			printf("请输入两个操作数>:");
			scanf("%d %d", &x, &y);
			int ret = pf[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输入值不在范围内,请重新输入\n");
		}
		
	} while (input);
	return 0;
}

以上代码相比原先代码优点: 

1.在以上代码中是将运算函数的地址存放在数组内,通过数组的下标就可以找到相应的运算函数,
并且将数组下标为0先置为0,这就可以使得输入的input的值正好和数组下标所匹配 

2.无需使用switch case语句,这会使得主函数内很简洁,同时要加入其他运算只需在代码中添加运算函数;修改count的值并且在数组内添加函数名即可

 

测试程序运行结果
d1e4832ebea64ccdbac704f0233f2dde.png

以上就深入理解指针(3)的全部内容,希望看完以上内容你能有所收获,接下来还会继续更新指针的其他内容,未完待续....  

 

  • 92
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 66
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mljy.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值