本节重点:1.指针数组 2.字符数组 3.数组指针 4.二维数组传参的本质 5.字符串数组
其实还有一个很重要的函数指针,但是我们之前已经详细解析过了,所以这一次就先跳过
详解函数指针见函数指针与qsort函数
1.指针数组
首先我们要理解,指针数组其实还是数组,只不过其中存储的是指针变量
上面这张图应该很好的解释了数组指针
下面我们尝试用指针数组来模拟二维数组,其实本质不是二维数组,只是
看起来像二维数组,等会我们在字符串指针中会有大作用
用指针数组来模拟二维数组
我们知道,二维数组其实是一维数组的数组
void test()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int* arr[3] = { arr1,arr2,arr3 };
int i;
for (i = 0;i < 3;++i);
{
for (int j = 0;j < 5;++j)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
我们拿i==1;j==2举例子,当i==1时,arr[i][j]->arr2[j]->arr2[2]->4
所以这里看起来像是二维数组,但其实不是二维数组
2.字符数组
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
这是我们对字符指针的最简单的理解
我们知道,c语言没有内置的字符串类型,所以c语言的字符串其实本质上是用字符数组来实现的
创建字符串的方法
方法一:
char ch[]="abcd";
方法二:
char* s="abcd";
下面我们对这两种创建的方法解读一下,其实字符串在内存中是存储在字符串常量区的,这是一块只读的内存区域,不可更改,所以第一种方法就是把字符串"abcd"拷贝一份放到ch数组中去,方法二就是创建一个指针变量s指向了字符串的首地址(其实字符串这东西类似数组)我们在后续会进行分析.
好了,明白了字符串的存储机制,下面让我们来我们看一下下面这道题
#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;
}
思考这个题目的运行结果是什么
结果如下
解析一下,刚才我们说了,当我们写下字符串"hello bit.",这个字符串就在字符串常量区分配了一块内存,而且是只读内存,且在程序运行期间不变(类似静态变量),所以对于str1和str2来说,其实是将这个字符串拷贝一份进入str1,str2数组,数组的地址肯定不一样,所以这个str1跟str2是不一样的,但是对于str3,str4其实是创建了两个指针变量同时指向了这一块内存,所以地址其实是一样的,所以两者相等
3.数组指针
类似前面的指针数组,数组指针其实就是一个指针,指向的是数组而已
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int(*P)[10]=&arr;
这就是数组指针的创建
数组指针各部分解析
通过上面的解析,应该非常清晰,那数组指针的类型是什么呢
例: int (*p) [5] 是一个数组指针,去掉指针变量p就是指针类型,所以类型就是 int (*) [5]
再举一个例子
char* arr[3]={"java","cpp","golang"};
char*(*parr)[3]=&arr;
指针类型是char* (*) [3];
4.二维数组传参的本质
我们都知道数组传参的时候会退化成指针进行传递,所以数组传参传递的是数组首元素的地址,一维数组传参就是传第一个元素的地址(这点也可以可以看我们之前写的,数组实现的指针底层实现)那么同理二维数组传参也是传递的第一个元素的地址,而二维数组是一维数组的数组,而所以对于二维数组来说,传递的就是一维数组的地址,所以要用一个数组指针去接收
在没有学习数组指针之前我们传参的方式
void function1(int arr[3][5], int r, int c)
{
for (int i = 0;i < r;++i)
{
for (int j = 0;j < c;++j)
{
printf("%d ", arr[i][j]);
printf("%d ", *(*(arr + i) + j));
这二者其实就是等价的,详解可以看上一节,数组的指针底层实现
}
printf("\n");
}
}
在学习了数组指针之后我们的传参方式
void function2(int(*parr)[5], int r, int c)
{
for (int i = 0;i < r;++i)
{
for (int j = 0;j < c;++j)
{
printf("%d ", *(*(parr + i) + j));
printf("%d ", parr[i][j]);
这二者其实就是等价的,详解可以看上一节,数组的指针底层实现
}
printf("\n");
}
}
如何理解用指针访问二维数组的方式
parr+i指的是跳过了几个数组
比如我parr=&arr;
parr+1==&arr+1,即使跳到了第二个一维数组
所以第一次解引用是找到第几个一维数组,第二次解引用是访问到每一个
一维数组中的元素
小tip:如何用数组指针访问一维数组?
总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。
一维数组同理
5.字符串数组
01.再探字符串
有了前面的铺垫,我们想要很好的分析一下字符串数组到底是个什么
在上文中我们提到字符串其实类似一个数组
void test03()//测试字符串是不是数组
{
char* s = "abcd";
printf("%c ", "abcd"[2]);
printf("%c ", *("abcd" + 2));
}
运行结果
结果很出乎人意料,也就是说字符串本身其实类似是一个数组,上面分别
是下标引用和指针两种方式访问
02.字符串数组的访问(元素)
char* ch[3] = { "abcd","efgh","ijkl" };
第一种方式我们尝试用数组名来直接访问
printf("%c ", ch[1][1]);
printf("%c ", *(*(ch + 1) + 1));
为什么可以这样访问也是因为我们之前测试了其实字符串类似于数组,所以这里访问
其实是类似二维数组的访问方法
运行结果是 f f
char* ch[3] = { "abcd","efgh","ijkl" };
因为字符串类似数组,那么这个数组是不是类似而二维数组呢
那我们能不能尝试利用数组指针来访问呢
第二种方法
char* (*pc)[3] = &ch;
printf("%c ", *(*((*pc) + 1) + 1));
printf("%c ", (*pc)[1][1]);
运行结果是 f f
char* ch[3] = { "abcd","efgh","ijkl" };
第三种访问访问方法,我们能不能用二级指针来访问呢
char** pcc = ch;
printf("%c ", ch[1][1]);
printf("%c ", *(*(ch + 1) + 1));
因为ch是首元素的地址,而首元素的地址其实是二级指针
运行结果是 f f
03.字符串数组的访问(子字符串)
char* ch[3] = { "abcd","efgh","ijkl" };
printf("%s ", ch[1]+1);
printf("%s ", *(ch+1) + 1);
为什么不能跟上边的字符访问一样用ch[1][1]因为,访问字符串其实需要的是地址
第二次不需要再次解引用了
运行结果 fgh fgh
用数组指针访问
char* (*pc)[3] = &ch;
printf("%s ", (*pc)[1] + 1);
printf("%s ", *((*pc)+1) + 1);
用二级指针访问
char** pcc = ch;
printf("%s ", pcc[1] + 1);
printf("%s ", *(pcc+ 1) + 1);
运行结果都一样
相信通过上面的学习,大家对字符串,指针的理解就更深入了
谢谢观看!