目录
🏉前言
🚀 数组名的理解
🚀使用指针访问数组
✈一维数组传参的本质
✈冒泡排序
🏆二级指针
🏆指针数组
🏆指针数组模拟二维数组
🎉结束语
🏉前言
上一章,小赵和各位聊了关于C语言指针的开篇章,我们聊了指针最初的模样,但指针这个东西远远不止上一章那样简单,因为我们开辟的内存远远不止这么简单,毕竟还有数组的开辟,还有函数等等这些,包括我们的指针它也是有内存的,那我们究竟该如何去访问它们的地址,如何使用它们的地址内存,这样的地址内存又究竟对我们编写代码会带来什么样革命性的改变,下面小赵讲讲一一为你解答。
🚀 数组名的理解
首先想和各位说的一点就是,其实我们的数组名就是我们这个数组的地址,那这个数组的地址究竟是整个数组的地址呢?还是说是我们的数组首元素的地址,还是说是我们数组结尾的那个地址。这里我们打印出来看看。
那我们通过我们对我们的数组打印地址,我们发现我们的首元素地址和我们的数组名代表的地址是一样的,那么我们的数组的地址其实也就明白了。
那如果我们对我们的地址名取地址呢?这里我们也可以打印看看。
这里我们惊讶的发现这两个地址,居然是一样的,那这样是否可以说明我们&arr与arr,是一样的?
这里小赵给大家的解释一下,虽然这两个地址是一样的但含义却是极大的不同,这个就和我们之前聊的单位一样,(char int 等),&arr取得是整个数组的地址,所以如果你对它加一,它的单位实际上就是加了一个数组,那另外一个呢?如果只是arr,它其实代表的就是首元素地址,几乎等价于&arr[0],那它加的就是以每一个数组的元素的大小为单位,那么它的加一,就是加到数组的下一个数组的元素。
🚀使用指针访问数组
好了有了上面的知识作为支撑,我们就可以试着去探讨如何用我们的指针和我们的数组相联系了。
int main()
{int arr[10] = { 0};int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);//数组有多少个元素int* p = arr;//取首元素地址for (int i = 0; i < sz; i++){scanf("%d", p + i);//给数组每个位置赋值}for (i = 0; i < sz; i++){printf("%d", *(p + i));//打印数组每个位置}return 0;
}
这个代码小赵要和大家解释一下,这里我们的数组是定义好的,而且我们的指针指向的是首元素的地址,那么我们其实对它加加就相当于从数组首元素向后走,其实还要给大家补个概念,其实我们的指针指向那个地址,那我们的p其实就相当于这个地址,这也就是我们说的指针=地址。那这里就是地址就不用加&,而下面我们打印的是地址里面的东西所以要解地址操作。
那这时候有人要问了,能不能超过这个数组进行访问了,其实是不行的,因为这里就是一种越界。
int main()
{int arr[10] = { 0 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);//数组有多少个元素int* p = arr;//取首元素地址for (i = 0; i < sz+1; i++){printf("%d", *(p + i));//打印数组每个位置}return 0;
}
那这个空间就是未知打印的东西也是未知的。再看这个
int main()
{int arr[3] = { 0 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);//数组有多少个元素int* p = arr;//取首元素地址for ( i = 0; i < sz+1; i++){scanf("%d", p + i);//给数组每个位置赋值}for (i = 0; i < sz+1; i++){printf("%d", *(p + i));//打印数组每个位置}return 0;
}
在这里我们就使用了未被定义的空间,那么我们的程序是会报错的。所以这种未被定义的空间是不能使用的,我们指针指向它的时候,其实我们的指针也就相当于野指针。
✈一维数组传参的本质
#include <stdio.h>
void test(int arr[])
{int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz1 = sizeof(arr) / sizeof(arr[0]);printf("sz1 = %d\n", sz1);test(arr);return 0;
在讲一维数组传参本质前,先给大家看一段代码,这个代码应该就是我们学指针前所用的数组传入函数的方式,但我们其实不知道的是,我们传入的究竟是整个数组的,还是数组的某一个呢?这个代码刚好可以帮我门验证一下这一点。
在这里我们可以看到我们代码运行后的结果是 10,1这就证明了,我们传入的数组其实只传了一个,那么为什么会这样呢?其实结合小赵这章所聊的内容,我们可以知道我们传入函数的是数组名,那数组名是什么呢?其实就是指针啦,那就证明我们传入函数的其实是个数组首元素指针。那这就好办了,那我们以后数组,不就可以用指针传了吗?而且指针改的是地址,可以让我们在函数中的操作,进入我们的数组中。(这里补的一点是,sizeof(arr),计算的是这个所代表的存在里面东西的大小,如果在函数外面,它表示就是整个数组(这一点比较特殊,只有在sizeof这里才代表这个意思。),而我们在函数里面它代表的就是我们传入的首元素的地址,计算的也只有数组首元素的大小。)
void test(int* arr)//参数写成指针形式
{printf("%d\n", sizeof(arr) / sizeof(arr[0]));//计算⼀个指针变量的⼤⼩
}int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz1 = sizeof(arr) / sizeof(arr[0]);printf("sz1 = %d\n", sz1);test(arr);return 0;
}
这里我们用指针试试 ,发现结果一样,与我们的猜想是一致的。
✈冒泡排序
有了上面的知识我们就可以试着用指针来编我们的冒泡排序了。
#include <stdio.h>
void BOBBLE(int* a)//优化版冒泡排序
{for (int m = 0; m < 4; m++){int flag = 1;//判断是否排序完成for (int j = 0; j < 4 - m; j++){if (a[j] >a[j + 1])//如果a[j+i]都大于arr[j],则证明排序已经完成了{flag = 0;//如果排序未完成,那么将falg置为零int t = a[j + 1];a[j + 1] = a[j];a[j] = t;}}if (flag == 1)//如果完成,结束循环break;}}
int main()
{int a[5] = { 0 };int i;for (i = 0; i < 5; i++){scanf("%d", &a[i]);}BOBBLE(a);for (int m = 0; m < 5; m++){printf("%d", a[m]);}return 0;
}
我们会发现有了指针的加入,让我们原本冗杂的长的代码变得更容易看懂,可以将我们的冒泡排序加入到我们的函数中去了,而且由于我们的指针改的其实是数组地址里面的内容,那这就让我们的数组可以真正的改变了(这个有点类似(1)的交换 )。
同时我们对我们的之前的冒泡排序进行了优化,让我们的代码可以不用执行的很复杂,大大加快代码了运行速度。
🏆二级指针
那好了数组解决了吧,那我们一级指针的地址咋说,谁能来管管呢?就这样我们的二级指针诞生了。
当然,我们得先看看我们指针是否有地址,看看它的地址。
我们看到我们指针指向的地址,与我们指针本身的地址是不一样的,这也就间接证明了,我们的指针其实是有自己独立的地址。
那我们的二级指针是什么样的,其实也蛮简单的,我们可以用上一章的思想解决,打开一个空间要一个钥匙,那我们现在打开两个呢?那不就是两个吗。
int main()
{int a = 0;int* p = &a;int** b = &p;printf("%p\n", &a);printf("%p\n", p);printf("%p\n", &p);printf("%p", b);
}
那我怎么才能访问到里面的a能其实也就是开两次锁。
🏆指针数组
那紧接着下来就是,我们有木有这样一个数组里面存的是指针呢?那其实也是有的。我们之前聊到,数组前面的那个就是它里面的元素类型,那我们只要将前面写成int*,char*等就可以了。
int main()
{int a = 0;int* p = &a;int* b[1] = { p };//指针数组printf("%d", *(b[0]));
}
🏆指针数组模拟二维数组
那一维数组聊完了,那二维数组呢?这里就不得不提小赵前面的概念了,二维数组里面每个元素其实就是一维数组,那既然是一维数组,我们是不是可以试着用一维数组的首元素地址代替呢?然后再对这个元素地址进行解指针操作,加一加二等,这样是不是就让指针数组模拟二维数组的操作完成了。
#include <stdio.h>
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };int* parr[3] = { arr1, arr2, arr3 };//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 5; j++){printf("%d ", parr[i][j]);//这里要解释一下,其实我们a[1]=*(a+1)的操作,那我们这里的arr[1][2]其实也就等于*(arr[1]+2)的操作。}printf("\n");}return 0;
}
这里要和家人们 解释的其实就是我批注的那一块,我们的arr[]这个操作其实就是在对我们的arr进行解指针的操作,而括号里面的数就是我们arr+数字,地址的移动,然后进行解指针。
🎉结束语
好了小赵今天的分享就到这里了,如果大家有什么不明白的地方可以在小赵的下方留言哦,同时如果小赵有什么地方说得不对也希望得到大家的指点,谢谢各位家人们的支持。你们的支持是小赵创作的动力,加油。
如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持小赵,如有不足还请指点,小赵及时改正,感谢大家支持!!!