深入理解指针【4】--(本章内容很有趣!)

字符指针变量

整形指针变量就是存放整形数据地址的变量。同理,字符指针变量,就是存放字符数据地址的变量。咱们也比较熟悉了,比如,整形指针变量,就是存放整形数据地址的变量。同理,字符指针变量,就是存放字符数据地址的变量。咱们也比较熟悉了,比如,

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	char a = 'w';
	char* p = &a;
	printf("%p\n", p);
	return 0;
}

这就是个简单的字符指针变量,感觉也没什么特别的,对吧?那么,我们再写一个代码,你就会感到很不可思议!,如代码所示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	char* p = "abcdef";
	printf("%p\n", p);
	return 0;
}

结果运行图:在这里插入图片描述
是不是有点儿很惊讶! 我们知道,指针变量存放的是地址,而我们直接给指针变量赋值字符串abcdef。程序竟然还执行了。这就是字符指针变量的一个好玩的地方——它可以这样被赋值。这种被赋值的情况下,abcdef被称为常量字符串我们所打印的地址,也是这个常量字符串的首元素的地址。说到这里,大家有没有觉得很熟悉——数组名就是首元素的地址。难道它跟数组类似?答案:确实,它就是跟数组类似,它也有一个连续的空间,也用下标的方式来访问字符串内的元素。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	char arr[] = "abcdef";
	char* p1 = arr;
	printf("%p\n", p1);
	char* p2 = "abcdef";
	printf("%p\n", p2);
	return 0;
}

结果运行图:在这里插入图片描述

看到了吧,是首元素的地址吧。它们是可以类比,但它们的本质是不同的——一个是数组,另一个是常量字符串。 展示一下,它们的类似图: 在这里插入图片描述
我们还以用下标对其元素进行访问,进行代码展示:


#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
    char arr[]="abcdef";
	printf("%c\n", arr[1]);
	printf("%c\n", "abcdef"[1]);
	return 0;
}

结果运行图:在这里插入图片描述
对元素的访问方式是一样的,只不过写法不一样。这里我们就可以认为arr="abcdef"语法上可没规定这样的关系,这样写便于咱们理解)。开头的代码有点问题,char * p="abcdef"这样写不全面,我们应该这样写—— const char * p=“abcdef”。常量字符串本身就是常量,常量是无法修改的,一旦修改,系统就会崩的,加个const就是为了防止修改而引起的系统崩盘。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	char* p = "abcdef";
	*p = 'w';
	return 0;
}

结果运行图:在这里插入图片描述
接下来,我们来看个《剑指offer》里面的一个代码,你猜猜输出的结果是啥?

#define  _CRT_SECURE_NO_WARNINGS	1
#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;
}

结果运行图:在这里插入图片描述
不知道你有没有猜对答案。str1str2是两个不同的数组(两个不同的空间),所以 str1 != str2在C语言中有个规定,当赋值给不同变量的值相同时,为了节省内存,只会创建一个空间,把这个空间的地址复用。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	printf("str3=%p\n", str3);
	printf("str4=%p\n", str4);

	return 0;
}

结果运行图:在这里插入图片描述

数组指针变量

前面,咱们讲过了指针数组,里面的元素为指针(地址)。我们见过整形指针变量——int * 字符指针变量——char * 。那么。数组指针变量是什么呢?我们以整形数组为例子——int ( * name)[元素数量 ]。 咱门在上个章节讲过了这个知识。因为[ ]的优先级高于 * ,所以,我们要写成(* name),防止被解析成指针数组了

二维数组传参的本质

有了前面的数组指针铺垫,今天就可以讲一下二维数组传参的本质了。我们先写个以前的二维数组访问的代码,进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
void Printf(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  ,4,5,6,7,8  ,1,8,9,3,5 };
	Printf(arr, 3, 5);
	return 0;
}

结果运行图:在这里插入图片描述
在以前的二维数组中的章节中,我们对于二维数组的传参格式,也是按照二维数组的形式写的形参,这一点和一维数组传参是一样的。前面,咱们讲过——数组名就是首元素的地址,传过去的是数组首元素的地址。对于二维数组是不是也成立呢?——答案:成立的。以前,咱们对于二维数组的假象化理解就是如图所示:在这里插入图片描述
我们讲过了,二维数组其实是一排连续的空间。二维数组就是由一维数组组成,也就是说,二维数组是一维数组的数组,一维数组作为二维数组的元素。既然数组名就是首元素的地址,也就是说arr把它里面的第一个数组的地址给传过去了,也就是说,传过去的是一个一维数组的地址。 那么,我们就可以把参数写成指针的形式。是不是这个原理呢?我们对代码进行修改,进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
void Printf(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  ,4,5,6,7,8  ,1,8,9,3,5 };
	Printf(arr, 3, 5);
	return 0;
}

结果运行图:在这里插入图片描述
结果运行是一样的,所以原理是一样的。由于我们传过去的是一个一维数组的整个地址,所以就要写成数组指针变量的形式(一维数组指针变量的形式)二维数组就是由一个个一维数组组成的,而且每个一维数组的类型还相同——int (*)[5]。 所以,当二维数组arr(地址)+ 1时,就会跳过一个个整的一维数组,如图所示:在这里插入图片描述
前面,咱们讲过这个知识:

//	int arr[5];
//	int (* p)[5]=&arr;
	p=&arr ;	//整个数组的地址
	*p=*&arr ;
	*p=arr;	//*p后,得到的是数组名。

咱们讲过,二维里面的一维数组名为,arr[0] ,arr[1] ……。也就是说,可以用一下代码进行解说:

int arr[3][5]={1,2,3,4,5 ,6,5,4,3,2 ,9,5,6,7,8};
int (* p)[5]=arr;
//	解引用后,得到各个一维数组的数组名
	*(p+0)=arr[0];
	*(p+1)=arr[1];
	*(p+2)=arr[2];
//	当我们对每个一维数组进行访问时,就可以写成如下的方式
	*(*(p+1)+0)=arr[1][0]=6;
	*(*(p+1)+1)=arr[1][1]=5;
			|
			|
			|
	*(*(p+1)+4)=arr[1][4]=2;

各位,看到了吧,* (*(p+i)+j)=arr[i][j]是相同的。对于 *( *(p+i)+j)里面的*(p+i)我们还可以这样写arr[i],咱们在上面才讲过。和一维数组一样,arr[i][j]这种形式,对于我们便于理解,* ( *(p+i)+j)才是本质按照咱们在一维数组自己定的规则——指针变量就是数组名,p=arr。 进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
void Printf(int(* p)[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 ", p[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5  ,4,5,6,7,8  ,1,8,9,3,5 };
	Printf(arr, 3, 5);
	return 0;
}

结果运行图:在这里插入图片描述

函数指针变量

整形指针变量,字符指针变量,数组指针变量。这些咱们都已经讲过了,取它们的地址用&。今天,我们所讲的函数指针变量,对于它的地址,我们也是用&,(这很好理解,取地址用&,我们自然而然就想到)。对于数组我们知道,数组名就是地址。那么,对于函数来讲,函数名是否就是地址呢?进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", &add);
	printf("%p\n", add);

	return 0;
}

结果运行图:在这里插入图片描述
从结果运行图中可以看出来,函数名就是这个函数的地址。当然,为了符合我们的习惯,我们一般写成&add。随你怎么写都OK。接下来,我们就用指针变量来进行访问。我们已经知道了,整形指针的变量类型,字符指针变量的类型,数组指针变量的类型。那么函数指针变量的类型是什么呢?我们直接上答案:在这里插入图片描述
它这样写的原因与数组指针变量是相同的,在这里咱们不多讲了。如果我们这样写,int * pf3(int x ,int y) ,pf3就会和(int x ,int y)结合成函数的形式,就单独抛下了int *,就会被解析成pf3(int x ,int y)这个函数的返回值类型为int *。所以我们不要那样写。函数指针变量里面的x,y可以省略,我们直接写出它的数据类型就OK。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*p)(int ,int) = &add;
	int c = (*p)(5, 3);
	printf("%d\n", c);
	int b = add(5, 3);
	printf("%d\n",b);

	return 0;
}

结果运行图:在这里插入图片描述
我们可以看出来结果是一样的,所以,*p等价于add(函数名)。其实*不写,也OK,直接写成p就OK。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*p)(int ,int) = &add;
	int c = (p)(5, 3);
	printf("%d\n", c);
	int b = add(5, 3);
	printf("%d\n",b);

	return 0;
}

结果运行图:在这里插入图片描述
我们甚至还可以写成这样(*******p)都OK。其实,*就是个摆设

  • 知识点小插曲:我们创建多个数据类型相同变量的时候,可以这样写:
	int a=0 , b=0 ,c=0 ......;	//局部变量的写法
	float a , b, c.......;		//全局变量的写法

那么,指针变量是否也可以这样写呢?我们先来实验一下:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	int* p1, p2;
	return 0;
}

结果运行图:在这里插入图片描述
会发现,事与愿违。这就是多个指针变量创建的特殊之处,我们要每个变量都要加个*

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main()
{
	int* p1,* p2;
	return 0;
}

结果运行图:在这里插入图片描述
这回就对了!!!

  • 看两个有趣的代码:
//	第一个代码:
(*(void (*)())0) ();

//	第二个代码:
 void (*signal(int , void(*)(int)))(int);

第一个代码:就是把一个整形0,这个普通的数值进行强制类型转换成一个函数指针的形式——变成了一个函数的地址,而且这个函数是无参且无返回值的,然后再对其解引用操作,再调用。
第二个代码:我们要把这个代码拆开来看。signal(int , void(*)(int)),是个函数,里面的参数为int void( *)(int),这个函数的返回值类型为void,参数为int

  • typedef关键字:这个关键字,是重命名的作用,跟#define的作用一样,但写法不同。它是这样写的,进行代码展示:
//	unit 对 unsigned char 进行了重命名
typedef	unsigned char  unit ;

//	size_t 对 int  进行了重命名
typedef	int 	size_t ;

这种命名的方式,可以进行多指针变量的定义,进行代码展示:

typedef	int * unit ;
unit p ,pp,ppp;	//就不会出现,咱们上面所示的情况了

就不会出现,咱们上面所示的情况了。大家可以试试把上面两个难理解的代码,进行重命名,就会理解了。

函数指针数组

前面,咱们见过了指针数组——也就是存放指针的数组。那么,就可以类比了,函数指针数组——就是用来存放函数指针的数组。那么,该怎么写代码格式呢?进行代码展示:

//	已返回值为整形的函数为例。
//	int (*arr[5])(int int )={add,sub,div,dul };

转移表

函数指针数组就是一个转移表,就是起到一个中间商的作用,我们通过里面的各个函数的地址,跳转到对应的函数,执行我们的指令。接下来,我们写一个程序:

//	写一个计算器,完成 + - * / 的运算

进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
void menu()
{
	printf("***********************\n");
	printf("*****1.add  2.sub *****\n");
	printf("*****3.dul  4.mul  ****\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 dul(int x, int y)
{
	return x * y;
}

int mul(int x, int y)
{
	return x / y;
}


int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int c = 0;
	do
		{
		menu();
		printf("请输入--> ");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				printf("请输入两个操作数--> ");
				scanf("%d %d", &x, &y);
				c = add(x, y);
				printf("%d\n", c);
				break;
			case 2:
				printf("请输入两个操作数--> ");
				scanf("%d %d", &x, &y);
				c = sub(x, y);
				printf("%d\n", c);
				break;
			case 3:
				printf("请输入两个操作数--> ");
				scanf("%d %d", &x, &y);
				c = dul(x, y);
				printf("%d\n", c);
				break;
			case 4:
				printf("请输入两个操作数--> ");
				scanf("%d %d", &x, &y);
				c = mul(x, y);
				printf("%d\n", c);
				break;
			case 0:
				printf("退出计算\n ");
				break;
			default:
				printf("重新输入:\n");
				break;
		}
		
	} while (input);
	return 0;
}

这样些的代码太繁琐,执行效率还不高,我们进行更改,用转移表的方式。进行代码展示:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
void menu()
{
	printf("***********************\n");
	printf("*****1.add  2.sub *****\n");
	printf("*****3.dul  4.mul  ****\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 dul(int x, int y)
{
	return x * y;
}

int mul(int x, int y)
{
	return x / y;
}


int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int c = 0;
	int (*arr[5])(int, int) = { 0,add,sub,dul,mul };	//这就是转移表
	do
	{
		menu();
		printf("请输入-->");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数-->\n");
			scanf("%d %d", &x, &y);
			c = arr[input](x, y);
			printf("%d\n", c);
		}
		else if (input == 0)
		{
			printf("退出计算\n");
			break;
		}
		else
		{
			printf("重新输入\n");
		}
	} while (input);
	return 0;
}

彩蛋时刻!!!

https://www.bilibili.com/video/BV1jT42167Xb/?spm_id_from=333.999.0.0&vd_source=7d0d6d43e38f977d947fffdf92c1dfad
在这里插入图片描述
每章一句:梦想很大,但要先迈出第一步。感谢你能看到这里,点赞+关注+收藏+转发是对我最大的的鼓励,咱们下期间!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值