指针与数组深度攻略:数组名、传参、冒泡、二级指针
专栏:C语言
C语言:指针3
- 一.数组名的理解
- 1.1 普通情况
- 1.2 两个例外
- 1.3 arr 与 &arr 的核心区别
- 二.使用指针访问数组
- 三.一维数组传参的本质
- 3.1函数内无法计算数组长度
- 四.冒泡排序(指针 + 数组)
- 4.1基础版(函数部分)
- 4.2进化版(函数部分)
- 五.二级指针
- 六.指针数组
- 七.指针数组模拟二维数组
- 总结
- 易错点
本文是 C 语言指针系列第三篇,我们将揭开数组名的真实面纱,用指针遍历数组的每一寸空间,深入理解一维数组传参、冒泡排序的实现逻辑;并进一步探索二级指针这一 “指针的指针”,学习指针数组如何搭建起模拟二维数组的桥梁。
前言:
指针是C语言的核心特性,也是学习路上的重点与难点。数组与指针的关联、二级指针、指针数组更是从入门走向进阶的必经之路。许多初学者因混淆数组名本质、不理解数组传参机制、无法区分二级指针与指针数组,导致代码逻辑混乱、错误频发。
我会以清晰的逻辑、通俗的讲解与代码案例,系统梳理上述知识点,从原理到实践层层拆解,帮助大家真正理解并掌握,为后续深入学习打下坚实基础。
一.数组名的理解
在学习数组和指针时,数组名绝对是第一个要搞明白的关键点。
先总结一下:数组名就是数组首元素的地址,但记住 ----->> 它有两个非常重要的例外,这是学习必坑点
1.1 普通情况
在前面数组提及过数组名 = 首元素地址
#include<stdio.h>intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};printf("&arr[0] = %p\n",&arr[0]);printf("arr = %p\n",arr);return0;}运行结果:两个地址完全相同,证明数组名就是首元素地址。
强调一下:绝大多数场景下,数组名都代表首元素地址。
1.2 两个例外
sizeof(数组名):数组名表示整个数组,计算整个数组的字节大小。& 数组名:数组名表示整个数组,取出的是整个数组的地址。
arr的类型是int*(指向 int 的指针)&arr的类型是int(*)[10](数组指针,指向整个数组)
两者地址值相同,但意义完全不同
1.3 arr 与 &arr 的核心区别
arr/&arr[0]:首元素地址,+1 跳过1 个元素(4 字节)。&arr:整个数组的地址,+1 跳过整个数组(40 字节)。
#include<stdio.h>intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};printf("&arr[0] = %p\n",&arr[0]);printf("&arr[0]+1 = %p\n",&arr[0]+1);printf("arr = %p\n",arr);printf("arr+1 = %p\n",arr+1);printf("&arr = %p\n",&arr);printf("&arr+1 = %p\n",&arr+1);return0;}运行结果:
二.使用指针访问数组
理解数组名后,用指针访问数组就更加轻松了。
先总结一下:数组下标访问,本质就是指针解引用。
如下所示:
#include<stdio.h>intmain(){intarr[10]={0};inti=0;intsz=sizeof(arr)/sizeof(arr[0]);int*p=arr;for(i=0;i<sz;i++){scanf("%d",p+i);}for(i=0;i<sz;i++){printf("%d ",p[i]);}return0;}数组名可直接赋值给指针,arr[i]等价于*(arr+i),p[i]等价于*(p+i)。
- 数组下标访问本质:
arr[i]-->*(arr+i)-->*(i+arr)–>i[arr] - 所以
arr[i]==i[arr]语法合法(因为编译器会翻译成*(arr+i)和*(i+arr),结果一样,这种写法了解即可) - 指针访问数组效率更高,编译器更易优化
运行结果:
三.一维数组传参的本质
先总结一下:一维数组传参,传的不是数组,是首元素地址。
所以函数形参里,数组写法本质还是指针。
3.1函数内无法计算数组长度
举个例子:
#include<stdio.h>voidtest(intarr[]){//sizeof(arr)是指针大小,结果为1intsz2=sizeof(arr)/sizeof(arr[0]);printf("sz2 = %d\n",sz2);}intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};intsz1=sizeof(arr)/sizeof(arr[0]);printf("sz1 = %d\n",sz1);test(arr);return0;}运行结果:
在这里sz2的值为2是因为我在vs2022编辑器里面用的是x64,说明在x64里面sz2的值为2,如果我换成x86,那么sz2的值就为1了:
注意:
- 函数内部,绝对不能用
sizeof求数组长度 - 传参必须把数组长度一起传过去
- 形参三种写法完全等价:
void test(int arr[])
void test(int arr[10])
void test(int* arr)
四.冒泡排序(指针 + 数组)
核心:两两相邻元素比较,交换逆序对;进化版可提前终止有序数组。
4.1基础版(函数部分)
voidbubble_sort(intarr[],intsz){inti=0;for(i=0;i<sz-1;i++){intj=0;for(j=0;j<sz-i-1;j++){if(arr[j]>arr[j+1]){inttmp=arr[j];arr[j]=arr[j+1];arr[j+1]=tmp;}}}}4.2进化版(函数部分)
voidbubble_sort(intarr[],intsz){inti=0;for(i=0;i<sz-1;i++){intflag=1;//设一个flag当判断intj=0;for(j=0;j<sz-i-1;j++){if(arr[j]>arr[j+1]){flag=0;inttmp=arr[j];arr[j]=arr[j+1];arr[j+1]=tmp;}}if(flag==1)break;}}加了flag,有序数组可以直接提前退出,效率高很多。
五.二级指针
指针变量也是变量,是变量就有地址,存放指针地址的变量,就是二级指针。
一级指针:int*--> 存放普通变量地址
二级指针:int**--> 存放一级指针地址
三级指针:int***--> 存放二级指针地址
二级指针常用于:动态二维数组、函数修改一级指针
例如:
#include<stdio.h>intmain(){inta=10;//一级指针:存放变量a的地址intb=0;int*pa=&a;//二级指针:存放一级指针pa的地址int**ppa=&pa;*ppa=&b;// 等价于pa = &b**ppa=30;// 等价于a = 30return0;}我们可以看到:*ppa:找到pa**ppa:找到a
六.指针数组
指针数组是存放指针 (地址) 的数组。
指针数组:是数组,里面每个元素是指针
数组指针:int(*p)[10]是指针,指向整个数组
区分口诀:括号优先是指针,括号靠后是数组
举个例子:
#include<stdio.h>intmain(){inta=10;intb=20;intc=30;int*parr[3]={&a,&b,&c};return0;}定义:int* parr[3],表示数组有3 个元素,每个元素是int*类型指针。
七.指针数组模拟二维数组
指针数组可模拟二维数组效果,但每行内存不连续。
补充:
- 真实二维数组:整段连续内存
- 指针数组模拟:每行独立,不连续
- 适用场景:字符串数组、不规则行列数据
举例:
#include<stdio.h>intmain(){intarr1[]={1,2,3,4,5};intarr2[]={2,3,4,5,6};intarr3[]={3,4,5,6,7};int*parr[3]={arr1,arr2,arr3};inti=0,j=0;for(i=0;i<3;i++){for(j=0;j<5;j++){printf("%d ",parr[i][j]);//i是哪一行数组,j是数组第几个元素}printf("\n");}return0;}运行结果:
总结
- 数组名默认是首元素地址,仅
sizeof(数组名)、&数组名表示整个数组。 - 数组访问本质是
*(地址+偏移量),指针与数组名可通用。 - 一维数组传参传递首元素地址,函数内不能用 sizeof 求长度。
- 二级指针存储一级指针地址,指针数组是存放指针的数组。
- 指针数组可模拟二维数组,但内存不连续。
易错点
- 混淆
arr与&arr,导致指针偏移错误。 - 函数内用
sizeof(形参数组)计算长度,结果错误。 - 二级指针解引用层级错误,
*ppa与**ppa混用。 - 把指针数组当成二维数组,误以为内存连续。
- 冒泡排序边界条件写错,导致越界或漏排。
如果这篇文章对友友们有帮助,期待友友们的支持!!!
点赞 ❤️ 干货收藏,助力指针吃透
收藏 📂 反复回看,攻克难点盲区
关注 🔔 持续更新,C 语言不迷路
留言 💬 交流探讨,学习共同进步
友友们的每一次互动,都是我持续更新C语言硬核知识的动力!
