C语言数组指针(指向数组的指针)

数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。以 int arr[] = { 99, 15, 100, 888, 252 }; 为例,该数组在内存中的分布如下图所示:
定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素。 在C语言中,我们将第 0 个元素的地址称为数组的首地址。 以上面的数组为例,下图是 arr 的指向:
数组名的本意是表示整个数组,也就是表示多份数据的集合,但在使用过程中经常会转换为指向数组第 0 个元素的指针,所以上面使用了“认为”一词,表示数组名和数组首地址并不总是等价。初学者可以暂时忽略这个细节,把数组名当做指向第 0 个元素的指针使用即可,我们将在VIP教程《 数组和指针绝不等价,数组是另外一种类型》和《 数组在什么时候会转换为指针》中再深入讨论这一细节。
下面的例子演示了如何以指针的方式遍历数组元素:
   
   
  1. #include <stdio.h>
  2. int main(){
  3. int arr[] = { 99, 15, 100, 888, 252 };
  4. int len = sizeof(arr) / sizeof(int); //求数组长度
  5. int i;
  6. for(i=0; i<len; i++){
  7. printf("%d ", *(arr+i) ); //*(arr+i)等价于arr[i]
  8. }
  9. printf("\n");
  10. return 0;
  11. }
运行结果:
99  15  100  888  252

第 4 行代码用来求数组的长度,sizeof(arr) 会获得整个数组所占用的字节数,sizeof(int) 会获得一个数组元素所占用的字节数,它们相除的结果就是数组包含的元素个数,也即数组长度。

第 8 行代码中我们使用了 *(arr+i) 这个表达式,arr 是数组名,指向数组的第 0 个元素,表示数组首地址, arr+i 指向数组的第 i 个元素,*(arr+i) 表示取第 i 个元素的数据,它等价于 arr[i]。
arr 是 int*类型的指针,每次加 1 时它自身的值会增加 sizeof(int),加 i 时自身的值会增加 sizeof(int) * i,这在《 指针变量的运算》中已经进行了详细讲解。
我们也可以定义一个指向数组的指针,例如:
   
   
  1. int arr[] = { 99, 15, 100, 888, 252 };
  2. int *p = arr;
arr 本身就是一个指针,可以直接赋值给指针变量 p。arr 是数组第 0 个元素的地址,所以 int *p = arr; 也可以写作 int *p = &arr[0]; 。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。
再强调一遍,“arr 本身就是一个指针”这种表述并不准确,严格来说应该是“arr 被转换成了一个指针”。这里请大家先忽略这个细节,我们将在VIP教程《 数组和指针绝不等价,数组是另外一种类型》和《 数组在什么时候会转换为指针》中深入讨论。
如果一个指针指向了数组,我们就称它为 数组指针(Array Pointer)

数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关,上面的例子中,p 指向的数组元素是 int 类型,所以 p 的类型必须也是 int *

反过来想,p 并不知道它指向的是一个数组,p 只知道它指向的是一个整数,究竟如何使用 p 取决于程序员的编码。

更改上面的代码,使用数组指针来遍历数组元素:
   
   
  1. #include <stdio.h>
  2. int main(){
  3. int arr[] = { 99, 15, 100, 888, 252 };
  4. int i, *p = arr, len = sizeof(arr) / sizeof(int);
  5. for(i=0; i<len; i++){
  6. printf("%d ", *(p+i) );
  7. }
  8. printf("\n");
  9. return 0;
  10. }
数组在内存中只是数组元素的简单排列,没有开始和结束标志,在求数组的长度时不能使用 sizeof(p) / sizeof(int) ,因为 p 只是一个指向 int 类型的指针,编译器并不知道它指向的到底是一个整数还是一系列整数(数组),所以 sizeof(p) 求得的是 p 这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。

也就是说,根据数组指针不能逆推出整个数组元素的个数,以及数组从哪里开始、到哪里结束等信息。不像字符串,数组本身也没有特定的结束标志,如果不知道数组的长度,那么就无法遍历整个数组。

上节我们讲到,对指针变量进行加法和减法运算时,是根据数据类型的长度来计算的。如果一个指针变量 p 指向了数组的开头,那么 p+i 就指向数组的第 i 个元素;如果 p 指向了数组的第 n 个元素,那么 p+i 就是指向第 n+i 个元素;而不管 p 指向了数组的第几个元素,p+1 总是指向下一个元素,p-1 也总是指向上一个元素。

更改上面的代码,让 p 指向数组中的第二个元素:
   
   
  1. #include <stdio.h>
  2. int main(){
  3. int arr[] = { 99, 15, 100, 888, 252 };
  4. int *p = &arr[2]; //也可以写作 int *p = arr + 2;
  5. printf("%d, %d, %d, %d, %d\n", *(p-2), *(p-1), *p, *(p+1), *(p+2) );
  6. return 0;
  7. }
运行结果:
99, 15, 100, 888, 252

引入数组指针后,我们就有两种方案来访问数组元素了,一种是使用下标,另外一种是使用指针。
1) 使用下标
也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。
2) 使用指针
也就是使用 *(p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 *(arr+i) 来访问数组元素,它等价于 *(p+i)。

不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。

更改上面的代码,借助自增运算符来遍历数组元素:
   
   
  1. #include <stdio.h>
  2. int main(){
  3. int arr[] = { 99, 15, 100, 888, 252 };
  4. int i, *p = arr, len = sizeof(arr) / sizeof(int);
  5. for(i=0; i<len; i++){
  6. printf("%d ", *p++ );
  7. }
  8. printf("\n");
  9. return 0;
  10. }
运行结果:
99  15  100  888  252

第 8 行代码中,*p++ 应该理解为 *(p++),每次循环都会改变 p 的值(p++ 使得 p 自身的值增加),以使 p 指向下一个数组元素。该语句不能写为 *arr++,因为 arr 是常量,而 arr++ 会改变它的值,这显然是错误的。

关于数组指针的谜题

假设 p 是指向数组 arr 中第 n 个元素的指针,那么 *p++、*++p、(*p)++ 分别是什么意思呢?

*p++ 等价于 *(p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素,上面已经进行了详细讲解。

*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。

(*p)++ 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0  个元素,并且第 0 个元素的值为 99,执行完该语句后,第 0  个元素的值就会变为 100。
<think>好的,用户想了解C语言中的数组指针指针数组。我得先回忆一下这两个概念的区别,确保自己理解正确。数组指针,应该是指向数组指针,而指针数组则是一个数组,里面存放的都是指针。不过,用户可能容易混淆这两个术语,因为它们看起来结构相似,只是顺序不同。 首先,我应该分别定义这两个概念,给出各自的声明方式。比如,数组指针的例子是int (*p)[n],这里p是一个指针指向含有n个整数的数组。而指针数组则是int *p[n],这里p是一个数组,每个元素都是指向整数的指针。需要明确两者的区别,声明中的括号位置不同,导致含义完全不同。 接下来,可能需要用具体的例子来说明它们的用法。比如数组指针在二维数组遍历时的应用,可以指向某一行,然后通过指针来访问该行的元素。而指针数组则可以用于存储多个字符串,每个元素指向不同的字符串,这样处理字符串数组会更方便。 还要注意用户可能的疑问点,比如为什么数组指针的声明中括号是必要的,如果不加括号会变成什么。比如int *p[n]和int (*p)[n]的区别,前者因为[]的优先级高于*,所以p先被定义数组,再是指针类型,而后者因为有括号,p先被定义指针,再指向数组。 另外,可能需要比较它们在内存中的结构。数组指针本身是一个指针,占用的空间通常是一个指针的大小,而指针数组一个数组,每个元素都是指针,所以总大小是n个指针的大小之和。 应用场景方面,数组指针适合处理多维数组,尤其是当需要传递二维数组到函数中时,可以使用数组指针作为参数。指针数组则适合管理多个独立的字符串或者动态分配的内存块,比如命令行参数argv就是一个典型的指针数组。 用户可能也会对如何正确使用它们感到困惑,比如初始化、赋值和访问元素的具体方法。这时候举出代码示例会很有帮助,比如如何用数组指针遍历二维数组,或者用指针数组存储不同的字符串并逐个输出。 最后,要总结两者的区别,强调名称中的“指针”和“数组”的位置决定了它们的本质,帮助用户通过名称来记忆和理解。可能还需要提醒常见的错误,比如错误地声明导致编译器报错,或者在使用时出现的内存访问问题。 总之,我需要用清晰的结构,先分别介绍每个概念,再比较它们的区别,最后给出应用示例,确保用户能够正确区分并理解两者的用法。</think>好的,我来帮你清晰理解C语言数组指针指针数组的区别及用法。这两个概念名称相似但含义完全不同,掌握它们的核心在于分析变量声明结构。 --- ### 一、指针数组(Array of Pointers) **本质**:首先是个数组数组中的每个元素都是指针 #### 声明格式: ```c 类型 *数组名[数组长度]; ``` #### 示例: ```c int *ptr_arr[5]; // 包含5个int型指针数组 ``` #### 特点: 1. 每个元素都是一个独立指针 2. 常用于存储字符串数组: ```c char *strs[3] = {"Apple", "Banana", "Cherry"}; ``` 此时内存结构: ``` strs[0] --> "Apple\0" strs[1] --> "Banana\0" strs[2] --> "Cherry\0" ``` 3. 元素可以指向不同长度的内存区域 4. 总内存大小 = 数组长度 × 指针大小(如64位系统为8字节×n) --- ### 二、数组指针(Pointer to Array) **本质**:首先是个指针指向整个数组 #### 声明格式: ```c 类型 (*指针)[数组长度]; ``` #### 示例: ```c int (*arr_ptr)[4]; // 指向包含4个int元素的数组指针 ``` #### 特点: 1. 常用于处理二维数组: ```c int matrix[3][4] = {0}; int (*p)[4] = matrix; // 指向一个包含4个int的子数组 ``` 2. 指针运算以整个数组为单位: ```c p++; // 移动sizeof(int[4])字节,即指向下一行 ``` 3. 通过指针访问元素: ```c (*p)[2] = 5; // 访问当前行的第3个元素 ``` --- ### 三、核心区别对比 | 特征 | 指针数组 | 数组指针 | |----------------|----------------------------------|----------------------------------| | 本质 | 数组元素为指针 | 指针指向整个数组 | | 声明 | `int *arr[5]` | `int (*ptr)[5]` | | sizeof结果 | 5×指针大小(如5×8=40字节) | 单个指针大小(如8字节) | | 典型应用场景 | 字符串数组、动态指针集合 | 二维数组操作、数组的整体传递 | | 指针运算单位 | 单个元素(指针)大小 | 整个数组的大小 | | 元素访问方式 | `arr[i]` 直接访问指针元素 | `(*ptr)[i]` 需解引用 | --- ### 四、经典应用示例 #### 指针数组处理字符串: ```c char *colors[] = {"Red", "Green", "Blue"}; for(int i=0; i<3; i++) { printf("%s\n", colors[i]); // 直接输出各字符串 } ``` #### 数组指针处理二维数组: ```c void print_matrix(int (*p)[4], int rows) { for(int i=0; i<rows; i++) { for(int j=0; j<4; j++) { printf("%2d ", p[i][j]); // 等价于*(*(p+i)+j) } printf("\n"); } } int main() { int mat[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; print_matrix(mat, 3); } ``` --- ### 五、常见误区提醒 1. 声明混淆: - `int *a[5]` ❗ 是指针数组 - `int (*a)[5]` ❗ 是数组指针 2. 数组指针初始化必须匹配列数: ```c int arr[3][5]; int (*p)[5] = arr; // 正确 int (*p)[4] = arr; // 错误!列数不匹配 ``` 3. 指针数组需要单独初始化每个指针: ```c int *ptrs[5]; for(int i=0; i<5; i++) { ptrs[i] = malloc(sizeof(int)*10); // 必须逐个分配内存 } ``` --- 掌握这两个概念的关键在于: 1. 分析声明语句中的符号优先级([]比*优先级高) 2. 理解指针运算的步长差异 3. 通过具体的内存布局图辅助理解 建议通过调试器观察变量内存地址的变化,这会帮助你建立更直观的理解。比如观察指针数组各元素的地址间隔(通常为4或8字节),对比数组指针进行p++操作后的地址变化量。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值