1数组指针
1.1数组指针初次理解
什么是数组指针呢,想想看,后缀为指针,那它就必然是一个指针,前缀为数组那它就是指向一个数组,不同于数组名,数组名大多数情况 是首元素地址通过加加 减减 控制下标可以操作任何一个元素,而数组指针,仅仅只是取得了整个数组的地址,当然了,这个地址在数值上是等于首元素地址但是加加 减减 它所跳过的字节数却是整个数组所占大小的字节数
在这里给大家一个学指针的诀窍,不管是什么指针,我们只关注两点
1它指向哪?也就是地址在哪,它指向哪个元素
2它是什么类型的?如果是int类型 那么它既可解引用4个字节,并且+1 -1时往前或往后跳过4个字节如果是其他类型也同理
这才是指针的本质
那么让我们来看看代码了解了解
int main()
{
//定义两个整型数组
int arr[10]={0};
int brr[20]={0};
//这里是数组指针的初值化
int (*pa)[10]=&arr;
int (*pb)[20]=&brr;
return 0;
}
我们先看一看数组指针怎么定义吧!
由于 * [] 这两个操作符优先级是一样的 而数组指针是一个指针所以我们得先打括号确保他是一个指针 我们以第一个数组指针为例
除去(*pa)那么剩下的就是他的类型 int [10] 那它是个啥 是不是一个数组 如果你不信 我们看一下最简单的指针int *p
那它除去*p是不是剩下了int 那它是不是指向了一个int 类型的数,
那么我们的数组指针就该指向一个数组,它的接收值就应该是整个数组的地址,那在这里大家有没有疑问呀?
你看我们之前所说的arr它不是一个首元素地址吗?它取上一个&操作符怎么就变成了整个数组的地址呢?那它不应该是一个首元素地址的地址吗
?他不应该用二级指针来接收吗?大家记住这就是数组的第二种特殊情况,当arr单独出现在&的右侧是 &arr表示的是整个数组的地址这是特殊情况
要牢记 放心咱们以后会专门搞一手来分析他们的区别
下面我们来看代码,来分析分析数组指针到底是个啥玩意
还是要提醒一下,这里是x64的环境所以地址要用16个16进制的数表示
首先 p接收的地址为arr,是arr数组的首元素地址 pa接收的是arr整个数组的地址 在值上 两个地址都是 0000005972E2FF538
大家注意观察. 我们再看 p+1的地址减去p的地址 我们观察发现 前15位他俩一样 所以我们用c-8=4,刚好对应int类型字节数
我们接着看用 &arr+1的地址减去&arr的地址 他两只有后二位不同 我们用他们两位分别化成10进制 60=6*16=96
38=8+3*16=56 最后用96-56=40=4*10 是不是跳过了整个数组的内存大小,那么看到这里,恭喜大家已经初步的了解了数组指针
我们前面说了数组指针可以作为二维数组形参传递给函数
那么这里有涉及到了二维数组名它的一些注意点
我们还是讲解一波吧
int main()
{
int arr[2][5] = { 0 };
printf("%p\n", *arr);
printf("%p\n", *arr + 1);
printf("%p\n", arr);
printf("%p\n", arr + 1);
printf("%lld ",(long long)(*arr+1)-(long long)(*arr));
printf("%lld ",(long long)(arr+1)-(long long)(arr));
return 0;
}
哎呀,知识怎么越讲越多了()里头加入类型是为强制类型转换,我们以后还会再讲,那用long long 是因为vs2022x64中指针为8个字节
long long 8个字节 (不能使用long)这小子肾虚只有4个字节和int一样格式也得注意是%lld
大家还是先来分析 看二维数组,它是要解引用两次的说明它至少是地址的地址,但又不是我们单纯的二级指针
那二级指针第一次++只能是跳过4/8个字节(它是一个指针的大小)之后次解引用后++跳过的是该二级指针所指向的一级指针所指向的数的类型
的大小,那数组呢 二维数组咋回事它的第一次++所能跳过的内存大小一定是列数*类型大小(也就是每一行所有的元素个数*每个元素的大小)
解引用后所操作的内存大小为类型大小,也就是每个元素的大小,这里需要大家对二维数组有所了解,我才好分析比对,抱歉
所以我们才不能用二级指针直接指向二维数组,本质上是他们所能跳过的字节数以及操作的字节数大小不同
那大家就直接跟我来算算结果喽
第一个需要运算的是我们第五次要打印的内容,它已经解引用了一次,那么下次的字节跳动仅仅为int 类型字节大小4
所以答案就是4
那么第二个需要运算的,它没有解引用,那它所跳过的字节数为每一行元素5*类型大小4为20
所以答案就是20
这里其实是要对二级指针有了解的才能看懂
那么现在大家才真正能够理解为什么需要数组指针
2.指针数组
其实讲完数组指针,那你说指针数组,他不过就是一个玩具罢了
看指针数组前缀为指针 后缀为数组,那么他就是一个数组对吧
只不过它的每个元素为指针罢了,但还是得注意,既是数组那么还是空间连续并且类型相同
那么我们还是举例看代码
int main()
{
int brr[10] = { 0 };
//初始化
int* arr[10] = {&brr[0],&brr[1],&brr[2],&brr[3],
&brr[4],& brr[5],& brr[6],& brr[7],& brr[8],&brr[9]};
return 0;
}
我们这里主要看他的定义方式,相对整型指针,他不就多了一个 * 记住除去数组名以及[] 剩下的就是数组类型 这里我们让arr数组的每个元素分别指向brr各个元素
当然,指针数组的初始化同一般数组一样,它可以,不完全初始化
那么剩下的元素是什么呢?我们仍然通过比对整形数组来分析
整型数组它若是不完全初始化,剩下的元素为整型数字0
那么在指针数组中,其实也类似
我们知道NULL指针 其实它指向的内存为0x00000000(以32位为例),这个内存是一个特殊的不能调用的地址,那么此时若指针数组未被完全初始化那么剩下的元素自动默认为空指针
当然这里又涉及到野指针问题,我们仍然会单独抽出一节来讲各种数据初值化,以及未初始化是其默认值
指针数组用的其实比较少毕竟它们的类型要保证相同,而实际问题必然是有各种类型的元素
所以大家据实际来分析,不会就多多在编译器上,调试,这其实比直接在书上看好很多
3函数指针
我们同上面的节奏,继续来讲解函数指针
1先看它是如何定义的
2再看它的作用
不多说都在代码里
int Add(int a, int b)
{
return a + b;
}
int main()
{
//函数指针的初始化
int(*p)(int, int) = Add;
return 0;
}
int (*p)(int ,int);
看看 (*p)表示是一个指针,然后直接与()结合说明指向一个函数 ()里头的int int是所指向函数的
类型红色的int代表该函数的返回值类型,这就是函数指针的初始化
接着让我们看看如何使用函数指针
上代码
看代码,函数指针不是一个指针吗,应该要解引用才能访问呀
我们直接调用函数肯定没问题,使用指针时调不调用都一样
这个是为什么,其实笔者也不知道,但记住准没错,抱歉
4.指针的自由组合
想想看既然我们理解了指针数组,数组指针
那是不是可以无限套娃了呀
你看啊,有指针数组 难道没有指向指针数组的指针吗
有数组指针,难道没有 指针数组 但是每个指针都是数组指针
有没有?那么他就可以无限套娃了呀,当然,我们一般不这么干 容易混淆
我们就写一写
指向指针数组的指针: int *(*p)[10];
解释 (*p)保证为一个指针 它所指向的内容类型为 int * [10]很明显是一个指针数组
指针数组 指针类型为数组指针 :int (*(p[10]))[5];
解释p[10]保证为数组 那么数组的每个元素为int(*)[5]此时类型很明显了先和*结合确定为指针
在和[5]结合确定为数组指针
是不是很神奇,如果不相信大家可以自行在编译器中尝试,指针确实有一定的难度
希望大家多多敲码,有助于理解.
笔者对于指针的讲解不一定完全正确,若有错误,多多指教,谢谢大家阅读