深⼊理解指针(3)
2026/5/14 8:25:46 网站建设 项目流程

1. 数组名的理解

数组名就是数组⾸元素(第⼀个元素)的地址。

sizeof(数组名),sizeof中好的,我们来详细解释一下C语言中数组名的含义。数组名在不同上下文中确实有不同的含义,理解这一点对于掌握指针和数组操作至关重要。

核心概念总结

上下文数组名arr的含义说明
普通使用首元素地址 (&arr[0])大多数情况下,数组名代表数组第一个元素的地址
sizeof(arr)整个数组单独放在sizeof运算符内时,代表整个数组的大小
&arr整个数组的地址取地址操作得到的是指向整个数组的指针

详细解释与代码验证

1.1数组名通常表示首元素地址

#include<stdio.h>intmain(){intarr[5]={1,2,3,4,5};printf("arr = %p\n",(void*)arr);// 首元素地址printf("&arr[0] = %p\n",(void*)&arr[0]);// 首元素地址return0;}

输出示例

arr = 0x7ffd4a3b2b10 &arr[0] = 0x7ffd4a3b2b10

重点

  • arr&arr[0]的值相同,证明数组名在普通使用时等价于首元素地址。

1.2sizeof(数组名)表示整个数组

#include<stdio.h>intmain(){intarr[5]={1,2,3,4,5};printf("sizeof(arr) = %zu\n",sizeof(arr));// 整个数组大小printf("sizeof(&arr[0]) = %zu\n",sizeof(&arr[0]));// 指针大小(通常4或8字节)return0;}

输出示例

sizeof(arr) = 20 // 5个int × 4字节 = 20 sizeof(&arr[0]) = 8 // 64位系统指针大小为8字节

重点

  • sizeof(arr)计算的是整个数组占用的内存大小(5 × sizeof(int))。
  • arr仅是首元素地址,sizeof(arr)应返回指针大小(如8字节),但实际返回20,证明此处arr代表整个数组。

1.3&数组名表示整个数组的地址

#include<stdio.h>intmain(){intarr[5]={1,2,3,4,5};printf("&arr = %p\n",(void*)&arr);// 整个数组的地址printf("arr = %p\n",(void*)arr);// 首元素地址printf("&arr + 1 = %p\n",(void*)(&arr+1));// 跳过整个数组printf("arr + 1 = %p\n",(void*)(arr+1));// 跳过一个元素return0;}

输出示例

&arr = 0x7ffd4a3b2b10 arr = 0x7ffd4a3b2b10 &arr + 1 = 0x7ffd4a3b2b24 // 比 &arr 大20字节(5×4) arr + 1 = 0x7ffd4a3b2b14 // 比 arr 大4字节(1个int)

重点

  • &arrarr数值相同,但类型不同
    • &arr的类型是int(*)[5](指向长度为5的整型数组的指针)。
    • arr的类型是int*(指向整型的指针)。
  • 指针运算时,&arr + 1跳过整个数组(20字节),而arr + 1仅跳过一个元素(4字节)。

总结

  1. 首元素地址:除sizeof(arr)&arr外,数组名arr始终等价于&arr[0]
  2. 整个数组sizeof(arr)中单独使用的arr代表整个数组的大小。
  3. 数组地址&arr获取的是指向整个数组的指针,类型为int(*)[n](如int(*)[5]),而非int*

2. 使⽤指针访问数组

2.1 数组名与指针的关系

在C语言中,数组名本质上是数组首元素的地址。例如:

intarr[5]={10,20,30,40,50};
  • arr等价于&arr[0](首元素地址)。
  • 因此可以通过指针操作访问数组元素。

2.2 指针访问数组的三种方式

(1) 下标法(传统方式)
for(inti=0;i<5;i++){printf("%d ",arr[i]);// 输出:10 20 30 40 50}

(2) 指针变量法
int*ptr=arr;// ptr指向数组首地址for(inti=0;i<5;i++){printf("%d ",*ptr);// 输出当前指针指向的值ptr++;// 指针移动到下一个元素地址}

(3) 数组名作为指针常量
for(inti=0;i<5;i++){printf("%d ",*(arr+i));// 等价于 arr[i]}

2.3 指针运算的核心机制

  • 地址偏移计算
    指针加减整数n时,实际移动的字节数为:
    偏移量=n×sizeof(数据类型) \text{偏移量} = n \times \text{sizeof(数据类型)}偏移量=n×sizeof(数据类型)
    例如ptr + 2int数组中移动2×4=82 \times 4 = 82×4=8字节(假设int占4字节)。

  • 解引用操作
    *ptr表示获取指针当前指向的值。


2.4 内存布局示例

假设数组arr起始地址为0x1000

地址 | 值 0x1000 | 10 (arr[0]) 0x1004 | 20 (arr[1]) 0x1008 | 30 (arr[2]) ...
  • ptr = arr→ 指向0x1000
  • ptr + 1→ 指向0x1004(地址增加sizeof(int)

2.5 完整代码示例

#include<stdio.h>intmain(){intarr[5]={10,20,30,40,50};int*ptr=arr;// ptr指向arr[0]// 方法1:指针遍历printf("指针遍历: ");for(inti=0;i<5;i++){printf("%d ",*(ptr+i));// 输出arr[i]}// 方法2:指针自增printf("\n指针自增: ");ptr=arr;// 重置指针for(inti=0;i<5;i++){printf("%d ",*ptr);ptr++;// 移动到下一个元素}// 方法3:数组名作为指针printf("\n数组名指针: ");for(inti=0;i<5;i++){printf("%d ",*(arr+i));// 等价于arr[i]}return0;}

输出

指针遍历: 10 20 30 40 50 指针自增: 10 20 30 40 50 数组名指针: 10 20 30 40 50

2.6 注意事项

  1. 数组名是常量指针
    arr++非法(不能修改常量指针),但ptr++合法。
  2. 越界访问危险
    指针移动超出数组范围会导致未定义行为。
  3. 类型匹配
    指针类型需与数组元素类型一致(如int *对应int[])。

总结

  • 指针访问数组的核心是地址计算与解引用
  • 三种访问方式本质相同,但指针变量更灵活。
  • 理解*(arr + i) = arr[i]的等价性是关键。

通过指针操作,可高效遍历数组,尤其在动态内存和函数参数传递中优势显著。


3. ⼀维数组传参的本质

核心观点

  1. 数组名的本质:在大多数情况下,数组名arr代表数组首元素arr[0]的地址。即arr等价于&arr[0]
  2. 传参的本质:当将数组名作为参数传递给函数时,实际上传递的是这个首元素的地址。
  3. 形参的灵活性:因为函数接收的是一个地址(指针),所以形参部分可以有两种等效的写法:
    • 数组形式int arr[]
    • 指针形式int *arr

重点详解

3.1 数组名即首元素地址

考虑以下数组:

intmain(){intarr[5]={1,2,3,4,5};// 定义一个整型数组printf("arr: %p\n",(void*)arr);// 打印数组名本身的值printf("&arr[0]: %p\n",(void*)&arr[0]);// 打印首元素地址return0;}

运行这段代码,你会发现arr&arr[0]打印出来的地址值是完全相同的。这证明了arr在表达式中(除了sizeof(arr)&arr等少数情况)代表的就是数组首元素的地址。


3.2 传参传递地址

当我们将数组名arr传递给函数func时:

func(arr);// 传递数组名

实际上传递的是arr[0]的地址,相当于传递了&arr[0]


3.3 形参的两种等效写法

函数接收到的参数是一个指向int的指针。因此,在定义函数时,形参可以有下面两种写法,它们是完全等价的:

  • 写法一:数组形式 (语法糖)

    voidfunc(intarr[],intsize){// 看起来像数组,实际是指针for(inti=0;i<size;i++){printf("%d ",arr[i]);// 依然可以使用下标访问}}
    • int arr[]看起来像是接受一个数组,但这只是C语言提供的一种便利的写法(语法糖)。
    • 编译器看到int arr[]时,自动将其解释为int *arr
    • 方括号[]在这里只是表明arr指向的是数组的首元素,它并不意味着函数内会创建一个新的数组副本。方括号内的数字(如果有)也会被编译器忽略。
    • 函数内部仍然可以使用下标arr[i]来访问元素,这是因为指针支持下标操作。
  • 写法二:指针形式 (本质)

    voidfunc(int*arr,intsize){// 明确写成指针for(inti=0;i<size;i++){printf("%d ",*(arr+i));// 通过指针运算和解引用访问// 等价于 printf("%d ", arr[i]);}}
    • int *arr直接表明形参arr是一个指向整型的指针。
    • 在函数内部,可以通过指针算术(arr + i)和解引用*(arr + i)来访问数组元素,这等价于使用下标arr[i]

关键验证:sizeof操作符

验证形参本质是指针而非数组的最直接方法是使用sizeof操作符:

voidcheckSize(intarr[]){printf("Sizeof(arr) inside function (array form): %zu bytes\n",sizeof(arr));// 通常打印 8 (64位系统) 或 4 (32位系统)}intmain(){intmyArr[10];printf("Sizeof(myArr) in main: %zu bytes\n",sizeof(myArr));// 打印 10 * sizeof(int) = 40 (假设 int 为 4 字节)checkSize(myArr);return0;}
  • main函数中,sizeof(myArr)计算的是整个数组myArr在内存中占用的总字节数 (这里是10×4=4010 \times 4 = 4010×4=40字节)。
  • checkSize函数内部,sizeof(arr)计算的却是一个指针变量的大小(在 64 位系统上通常是 8 字节,在 32 位系统上是 4 字节),而不是数组的大小。
  • 这个差异明确无误地证明了:函数内部接收到的arr是一个指针(存储地址的变量),而不是整个数组本身。

总结

  1. 传递地址:一维数组传参时,传递的是数组首元素的地址 (&arr[0])。
  2. 形参等效:函数形参int arr[]int *arr在功能和意义上完全等价。编译器都将arr视为一个指向int的指针。
  3. 语法糖int arr[]这种写法是一种语法糖,让代码看起来像是直接传递了数组,更直观易读,但其底层实现依然是指针。
  4. 操作本质:在函数内部,对形参arr的下标操作arr[i]会被编译器转换为基于指针的算术和解引用操作*(arr + i)
  5. 影响原数组:由于传递的是地址,在函数内通过指针或下标修改形参arr指向的内存,会直接影响调用函数中的原数组元素。
  6. 需要传递大小:因为函数内部无法通过形参arr获取原数组的长度信息(sizeof(arr)得到的是指针大小),所以通常需要额外传递一个参数(如int size)来指明数组的元素个数。

4. 冒泡排序

核心思想:重复遍历待排序序列,依次比较相邻元素,如果顺序错误则交换它们。每轮遍历会将一个最大(或最小)元素“浮”到正确位置,如同气泡上浮。

算法步骤

  1. 比较相邻元素:如果第一个比第二个大,则交换它们。
  2. 对每一对相邻元素做同样工作:从开始第一对到最后一对。此时,最后一个元素应是最大值。
  3. 对所有元素重复上述步骤(除了最后一个已排序的元素)。
  4. 持续每次对越来越少的元素重复上述步骤,直到没有元素需要比较。

时间复杂度

  • 最好情况(已有序):O(n)O(n)O(n)
  • 最坏/平均情况:O(n2)O(n^2)O(n2)

C语言代码示例

#include<stdio.h>voidprint_arr(intarr[],intsz){inti=0;for(i=0;i<sz;i++){printf("%d ",arr[i]);}printf("\n");}voidbubble_sort(intarr[],intsz){inti=0;//确定趟数for(i=0;i<sz;i++){intflag=1;//标记是否有序:假设已经有序//一趟内部比较intj=0;for(j=0;j<sz-1-i;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;}}}intmain(){intarr[]={9,8,7,6,5,4,3,2,1,0};//对数组进行排序 - 升序intsz=sizeof(arr)/sizeof(arr[0]);bubble_sort(arr,sz);//输出print_arr(arr,sz);return0;}

5. 二级指针

定义:指向指针的指针。即一个指针变量存储的是另一个指针变量的地址。

用途

  • 动态创建指针数组。
  • 在函数中修改一级指针本身的值(例如,改变指针指向的地址)。
  • 处理多维动态数组。

声明int **pp;

  • pp是一个二级指针。
  • *pp是一个一级指针(指向int)。
  • **pp是一个int值。

C语言代码示例

#include<stdio.h>intmain(){intvalue=42;int*p=&value;// p 是一级指针,指向 valueint**pp=&p;// pp 是二级指针,指向 pprintf("value: %d\n",value);// 直接访问printf("*p: %d\n",*p);// 通过一级指针访问printf("**pp: %d\n",**pp);// 通过二级指针访问// 通过二级指针修改 value 的值**pp=100;printf("Modified value: %d\n",value);return0;}

6. 指针数组

定义:一个数组,其每个元素都是指针。

声明int *arr[5];

  • arr是一个包含 5 个元素的数组。
  • 每个元素arr[i]的类型是int *(指向int的指针)。

特点

  • 数组本身在栈上分配连续内存(用于存储指针)。
  • 每个指针元素可以指向不同大小的内存块(通常在堆上动态分配)。

用途

  • 存储多个字符串(字符串数组)。
  • 模拟二维数组。
  • 存储不同长度的数据集合。

C语言代码示例

#include<stdio.h>#include<stdlib.h>intmain(){// 声明一个指针数组,包含 3 个 int 指针int*ptrArr[3];// 为每个指针动态分配内存并赋值for(inti=0;i<3;i++){ptrArr[i]=(int*)malloc(sizeof(int));// 分配一个 int 空间*ptrArr[i]=i*10;// 通过指针赋值}// 访问和打印for(inti=0;i<3;i++){printf("ptrArr[%d] points to value: %d\n",i,*ptrArr[i]);}// 释放内存for(inti=0;i<3;i++){free(ptrArr[i]);}return0;}

7. 指针数组模拟二维数组

核心思想:利用指针数组(一级指针数组)来模拟二维数组的行为。

  1. 创建一个指针数组int **arr(二级指针指向指针数组)。
  2. 为指针数组的每个元素(一级指针)分配一个一维数组(代表二维数组的一行)。
  3. 访问元素使用arr[i][j]的形式,类似于二维数组。

优势

  • 行可以有不同的长度(不规则二维数组)。
  • 动态分配内存,大小可运行时确定。
  • 内存不一定连续(与真正的二维数组不同)。

C语言代码示例

#include<stdio.h>#include<stdlib.h>intmain(){introws=3,cols=4;int**simulated2D;// 二级指针// 步骤1:分配行指针数组 (模拟有多少行)simulated2D=(int**)malloc(rows*sizeof(int*));if(simulated2D==NULL)exit(1);// 步骤2:为每一行分配内存 (模拟每行的列)for(inti=0;i<rows;i++){simulated2D[i]=(int*)malloc(cols*sizeof(int));if(simulated2D[i]==NULL)exit(1);}// 步骤3:赋值 (使用双重下标)for(inti=0;i<rows;i++){for(intj=0;j<cols;j++){simulated2D[i][j]=i*cols+j;// 或 *(*(simulated2D + i) + j)}}// 步骤4:打印printf("Simulated 2D Array:\n");for(inti=0;i<rows;i++){for(intj=0;j<cols;j++){printf("%2d ",simulated2D[i][j]);}printf("\n");}// 步骤5:释放内存 (先释放每行,再释放行指针数组)for(inti=0;i<rows;i++){free(simulated2D[i]);}free(simulated2D);return0;}

关键点

  • simulated2D指向一个指针数组(int *数组)。
  • simulated2D[i]指向第i行的一维数组。
  • simulated2D[i][j]访问第i行第j列的元素。
  • 内存释放顺序很重要:先释放每一行的内存,再释放存储行指针的数组。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询