【C语言学习】指针?指针数组?数组指针?函数指针?函数指针数组?带你理清指针的奥秘!(下篇)

在这里插入图片描述

🎬 乀艨ic:个人主页

⛺️十月要结束啦,上周偷了点懒~

⭐️还是来看看这次的博客吧~


🌳前言

接着上次的博客,这次就带大家看一点指针的进阶内容哇,快上车,这次要开始一路狂飙啦~

在这里插入图片描述

🌳字符指针

在说指针数组,数组指针之前,我们先来看一个看似简单的一个概念吧,想必通过上次的博客,大家都知道什么是字符指针了吧~

我们看一个特别的使用方式:

int main()
{
 	const char* pstr = "hello world.";//这里是把一个字符串放到pstr指针变量里了吗?
 	printf("%s\n", pstr);
 	return 0;
}

如果你在你的编译器输入以上的代码是不会出任何问题的,当然也不会报⚠警告

我们来思考一下,这个难道是把一个字符串放到pstr指针变量里了吗?

答案当然是NO~

其实本质是把字符串 hello world. 首字符的地址放到了pstr中了

就是把一个常量字符串的首字符 h 的地址存放到指针变量里面了

我们可以看一个题目:

#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相同。


🌳指针数组

在看完一个简单的小知识点后,我们来看看这次的重点之一指针数组吧

🌲指针数组的定义

我们先从名字的角度去思考一下,指针数组是个什么东西?

指针?数组?

答案是:数组

我们之前是见过很多不同类型的数组的,比如整型数组浮点型数组等等,那我们类比一下就可以发现,其实指针数组就是一种存放指针类型的数组罢了。

我们来看一个简单的指针数组的例子吧~

int* arr[5];

上面的就是一个简单的整型指针数组,顾名思义就是一个可以存放5个整型指针的数组。

我们当然可以以此类推得出下面的各种类型的指针数组:

int* arr1[5];
float* arr2[3];
char* arr3[6];
double arr4[3];
....

🌲指针数组的使用

说了这么多,想必大家大概知道这是什么东西了,那么他有什么用呢?

我们看一串代码:

int main()
{
	
	int arr1[4] = { 0,1,2,3 };
	int arr2[4] = { 1,2,3,4 };
	int arr3[4] = { 2,3,4,5 };
	int* arr[3] = { arr1, arr2, arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

这是运行结果:

在这里插入图片描述
是的,我们通过这个模拟实现了一个二维数组,当然跟真正的二维数组存在差异,毕竟我们指针数组存的三个整型数组在内存中的位置并不一定是连续的,但是二维数组是连续的。


🌳数组指针

看完了指针数组,我们来看一个似乎很像的定义——数组指针

🌲数组指针的定义

我们再来思考一下,数组指针是个什么东西?

数组?指针?

答案当然是指针

就像整型指针,字符指针一样,所谓的数组指针就是存放数组地址的指针

我们看一下下面的两个变量:

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

刚刚我们学习过,第一个是指针数组,那么第二个就是我们现在说的数组指针了。

因为数组的[]运算符的优先级比指针的 * 要高,所以我们在表示数组指针的时候需要一个()使变量优先跟 * 结合表明是一个指针变量。

🍃&数组名VS数组名

我们既然知道数组指针是存放数组地址的指针变量,那么就会遇到一个问题,什么是数组的地址?跟首元素地址是一个东西嘛?

我们看一段代码:

#include <stdio.h>
int main()
{
 	int arr[10] = {0};
	printf("%p\n", arr);
	printf("%p\n", &arr);
 	return 0;
}

我们来看一下运行结果:
在这里插入图片描述
可见数组名和&数组名打印的地址是一样的。

我们知道&的意思是取得一个变量的地址,那么按理说&数组就是数组的地址,但是看样子数组名也是这样的

难道两个是一样的吗?

我们再看一段代码:

#include <stdio.h>
int main()
{
 	int arr[10] = { 0 };
 	printf("arr = %p\n", arr);
 	printf("&arr= %p\n", &arr);
 	printf("arr+1 = %p\n", arr+1);
 	printf("&arr+1= %p\n", &arr+1);
 	return 0;
}

运行结果如下:
在这里插入图片描述
首先我们知道指针+数字所跳过的大小取决与指针所指的类型,那么既然arr跟&arr都加1后的值不一样,那么证明他们的意义应该不一样的

实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。

本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型

数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

🌲数组指针的使用

说完了什么是数组指针,那我们来看一下怎么去使用这个东西

我们看一段示例代码:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
 	int i = 0;
 	for(i=0; i<row; i++)
 	{
 		for(j=0; j<col; j++)
 		{
 			printf("%d ", arr[i][j]);
 		}
 		printf("\n");
 	}
}
void print_arr2(int (*arr)[5], int row, int col)
{
 	int i = 0;
 	for(i=0; i<row; i++)
 	{
 		for(j=0; j<col; j++)
 		{
			 printf("%d ", arr[i][j]);
 		}
 	printf("\n");
 	}
}
int main()
{
 	int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
 	print_arr1(arr, 3, 5);
 //数组名arr,表示首元素的地址
 //但是二维数组的首元素是二维数组的第一行
 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
 //可以数组指针来接收
 	print_arr2(arr, 3, 5);
 	return 0;
}

当我们传一个参数是二维数组的时候,我们就可以用数组指针进行接受,当我们传参的时候传的是数组的首元素地址,而二维数组的首元素其实是一维数组,那么一维数组的地址就是数组指针类型,所以我们就可以用一个数组指针进行接受了。


🌳函数指针

接下来让我们看一个比较新的概念

根据刚刚的经验,我们来猜一下什么是函数指针。

那当然就是存着函数地址的指针~

注:如果你学过C#的话,其实C#委托(delegate)函数指针是非常相似的。

可能有人要问:函数也有地址吗?

让我们看一段代码:

#include <stdio.h>
void test()
{
 	printf("hello world\n");
}
int main()
{
 	printf("%p\n", test);
 	printf("%p\n", &test);
 	return 0;
}

看看运行结果:
在这里插入图片描述
由此可见,函数是存在地址的。

当然也有人可能会问,那&函数名跟函数名有区别吗?

跟数组不太一样,这里其实没什么太大的区别,比用特别区分。

那我们接下来就需要一个变量去存函数的地址了

void test()
{
 printf("hello world\n");
}
void (*pfun1)() = test;
void (*pfun2)() = &test;

上面的pfun1跟pfun2其实是没有什么区别的,大家也可以自己去验证哦~
其中函数指针的运用比较常见的是:回调函数
在C语言的库中有一个函数叫做 qsort() 就跟回调函数有关,大家可以自行了解一下~

🌲两段有趣的代码

大家在知道什么是函数指针之后可以看两段有趣的代码:

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

代码来源:《C陷阱和缺陷》

大家可以自己尝试理解,不过这两段代码都写得比较垃圾,下次博客或许我会给出一个解释~


🌳函数指针数组

说完了指针数组,说完了函数指针,我们接下来看看什么是函数指针数组

其实想必有很多人就大概猜到了

函数指针数组,就是存放函数指针类型变量的数组嘛,就是套娃而已~

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

int*arr[10]();

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

函数指针数组的用途:转移表
例子:(计算器)

#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, y;
    int input = 1;
    int ret = 0;
    int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
    while (input)
    {
        printf("*************************\n");
        printf(" 1:add 2:sub \n");
        printf(" 3:mul 4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        if ((input <= 4 && input >= 1))
        {
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = (*p[input])(x, y);
        }
        else
            printf("输入有误\n");
        printf("ret = %d\n", ret);
    }
    return 0;
}

当然也有像函数指针数组指针,不过也不常用就不继续聊了~

在这里插入图片描述

🌳结尾

本期内容先到这里,终于写完指针啦,大家可以期待下一次的博客,可能带来一些结构体的内容,或者写一点Unity的知识,可以点一个关注避免错过哦~

感谢观看,喜欢的话可以给一个大大的三连哦~
期待我们的下次相遇,喜欢的请点个关注再走哦!之后会持续更新更多的内容!
个人主页:传送门

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值