数组名arr 到底跟 指针(地址) 有啥区别呢?

01.int main()

02.{

  1. int arr[5] = {10, 20, 30, 40, 50};

  2. int* ptr=arr;

  3. int i = 2;

  4. printf("%d\n", arr[i]); // 输出:30

  5. printf("%d\n", *(arr + i)); // 输出:30

  6. printf("%d\n", *(ptr + i)); // 输出:30

10.}

由上面的例子我们可以看出:

  • arr 和 &arr[0]都是表示数组首元素的内存地址
  • arr[i] 等价于 *(arr + i) 等价于 *(ptr + i),这里 arr 退化为指向首元素的指针
  • ptr是指针,在代码的第09行,它的使用方式几乎和数组名字是一样的(08行)

很多教材也在讲:数组名在大多数情况下可以当作指针使用

那么我们到底可以认为数组名就是地址么?可以把数组名字作为指针使用么?

让我们来看一些更详细的例子来做区分:

1. 数组名是地址,但不是指针类型的变量

char str_array[] = "Hello"; // str_array是数组名

char *str_ptr = "Hello"; // str_ptr是指针变量

2. 数组名 vs 指针的实际表现

#include <stdio.h>

int main() {

char str_array[] = "Hello"; // 数组名str_array

char *str_ptr = "Hello"; // 指针 指向内存的.rodata段(存放的是字面量"Hello")只读

char *p = str_array; // 指针

//地址比较

printf("%p\n", str_array); // 地址 0x7ffeed8a42d2

printf("%p\n", &str_array[0]); // 地址 0x7ffeed8a42d2

printf("%p\n", str_ptr); // str_ptr 指向内存的只读.rodata段存放的字面量,

//其首地址是 0x5897ba5b4004

printf("%p\n", p); // 地址 0x7ffeed8a42d2

//sizeof比较

printf("%zu\n", sizeof(str_array)); // 6(数组大小)

printf("%zu\n", sizeof(str_ptr)); // 8(指针大小)

printf("%zu\n", sizeof(p)); // 8(指针大小)

//运算符比较

printf("%p\n", &str_array); // 数组的地址 0x7ffeed8a42d2 但类型不同

printf("%p\n", &str_ptr); // str_ptr指针变量本身的地址 0x7fff0f99b890

//(栈上的某个位置,程序每次运行都会有变化)

printf("%p\n", &p); // p指针变量本身的地址 0xffcee57d058

//指针运算

printf("%p\n", str_array + 1); // 0x7ffeed8a42d3

printf("%p\n", str_ptr + 1); // 只读.rodata段,指针前进1字节 地址是 0x5897ba5b4005

printf("%p\n", p + 1); // 0x7ffeed8a42d3

//注意

// str_array = str_ptr; // 错误!数组名不能出现在赋值左边

return 0;

}
一个常见误解的澄清

// 很多人以为:

int arr[5];

int *p = arr;

// 之后 arr 和 p 完全等价,其实不是!

// 关键区别:

// 1. sizeof(arr) != sizeof(p)

// 2. &arr != &p

// 3. arr 不能赋值,p 可以

3. 数组名的本质

数组名在大多数情况下会退化为指向第一个元素的指针,但这不是指针变量:

情况1:作为函数参数

void func(char arr[]) { // 即使写成char arr[] ,但实际上编译器也要把它看成:char *arr

// 这里arr是指针,不是数组

printf("%zu\n", sizeof(arr)); // 8(指针大小)

}

int main() {

char str[] = "Hello";

printf("%zu\n", sizeof(str)); // 6(数组大小)

func(str); // str退化为指针

return 0;

}

情况2:数组名不是左值

char str1[] = "Hello";

char str2[] = "World";

// str1 = str2; // 编译错误!数组名不能赋值

// str1++; // 编译错误!数组名不能自增

st1++ 等价于 str1 = str1 + 1

但 str1 不是左值,不能出现在赋值左边

所以 str1++ 是语法错误

比较指针:

char str_array[] = "Hello"; // 数组

char *str_ptr = "Hello"; // 指针

char *p = str_array;

char *p1 =p; //可以赋值

p1++; //可以做自增运算

printf("%s\n",p); //"Hello"

printf("%s\n",p1); //"ello"

4.内存空间比较:
数组的内存布局

栈内存

地址 内容 变量名

0x1000 'H' str_array(数组从这里开始)

0x1001 'e'

0x1002 'l'

0x1003 'l'

0x1004 'o'

0x1005 '\0'

str_array的值就是0x1000,但这个"值"不存在于一个单独的变量中
简单解释一下这句话

编译器处理数组名 str_array 时:

  • 没有一个叫做 str_array 的变量来存储地址 0x1000
  • 当你在代码中写 str_array 时,编译器直接使用地址 0x1000
  • 可以理解为:str_array 是一个编译时常量,它的值在编译时就确定了

指针的内存布局

栈内存: 只读段:

地址 内容 变量名 地址 内容

0x2000 0x4000 str_ptr 0x4000 'H'

0x4001 'e'

0x4002 'l'

0x4003 'l'

0x4004 'o'

0x4005 '\0'

str_ptr变量存在栈上,存着地址0x4000

5. 一个更深入的例子

#include <stdio.h>

int main() {

char arr[] = "Hello";

char *ptr = "Hello";

//地址对比

printf("%p\n", arr);

printf("%p\n", &arr); // 地址和arr相同!但类型不同(后面有详细的解释)

printf("%p\n", &arr[0]); // 地址和arr相同 类型是指针 char*

printf("%p\n", ptr); // 和arr首元素地址不同,它是指向字面量,.rodata的一个地址

printf("%p\n", &ptr); // 和ptr不同!这是指针变量的地址

printf("%c\n", *ptr); // 'H'

//类型不同

// arr的类型是 char[6](数组类型)

// 在表达式使用时,会退化成char* 指向首元素 即只在需要时会变成指针

// &arr的类型是 char(*)[6](指向整个数组char[6]的指针)

//不能赋值

char arr2[] = "World";

// arr = arr2; // 错误:数组名不是左值

char *ptr2 = "World";

ptr = ptr2; // 正确:指针可以重新赋值

return 0;

}

6. 为什么 &arr 和 arr 的地址相同但类型不同?

看一个例子:

char arr[6] = "Hello";

arr 和 &arr 在数值上是同一个地址,但类型不同,所以指针运算的单位不同。

假设内存从地址 0x1000 开始:

地址 内容 说明

0x1000 'H' ← arr[0]

0x1001 'e' ← arr[1]

0x1002 'l' ← arr[2]

0x1003 'l' ← arr[3]

0x1004 'o' ← arr[4]

0x1005 '\0' ← arr[5]

0x1006 未知 ← 数组后面的内存

0x1007 未知

...
arr(指向单个char的指针)

// arr 退化为 char* 类型

// 它"认为"自己指向的是一个char

printf("arr = %p\n", arr); // 输出: 0x1000

printf("arr + 1 = %p\n", arr + 1); // 输出: 0x1001 (前进1字节)

// 因为char大小是1字节
&arr(指向整个数组的指针)

// &arr 是 char(*)[6] 类型

// 它"认为"自己指向的是一个"char[6]"的数组

printf("&arr = %p\n", &arr); // 输出: 0x1000

printf("&arr + 1 = %p\n", &arr + 1); // 输出: 0x1006 (前进6字节)

// 因为整个数组大小是6字节
类型的不同,导致操作的不同
表达式 类型 可以进行的操作

arr 编译时:char[6] 使用时:char*(退化的) arr[i], *arr, *(arr+i)

&arr char(*)[6] 不能直接[]索引,需要先转为char*

所以请牢记:

  • arr 在表达式中退化为 char* 类型
  • 但 arr 本身的类型是数组类型 char[6]
  • &arr 的类型是 char(*)[6](指向整个数组的指针)
  • 数值上相同,但语义不同:
  • arr + 1 → 指向 arr[1]
  • &arr + 1 → 指向 arr后面的下一个"char[6]"数组

类比

char book[100]; // 一本100页的书

book → 第1页的地址

&book → 整本书的地址

book + 1 → 第2页的地址(前进1页)

&book + 1 → 下一本书的地址(前进100页)

7. 总结---数组名和指针

特性 数组名 char arr[6] 指针 char *ptr

内存位置 数组元素所在的位置 栈/堆上的一个变量

存储内容 不单独存储(是地址常量) 存储一个地址值

sizeof 数组总大小 指针大小

&运算符 得到相同地址 得到指针变量的地址

能否赋值 不能(不是左值) 能(是变量)

能否++ 不能 能

作为参数传递 退化为char* 直接传递

类型 char[6](数组类型) char*(指针类型)

编译时确定 大小和类型编译时确定 运行时动态

作为右值 退化为 char* 直接是 char*

指针运算 arr + n 前进 n 个元素 ptr + n 前进 n 个元素

8. 最准确的表述

"数组名在大多数表达式中会计算为指向其第一个元素的地址,但它本身不是一个指针变量,而是表示整个数组对象的标识符。"

"数组名在大多数表达式中会退化为 指向其首元素的指针(右值),但它本身是数组类型的标识符,不是指针变量。编译器在编译时知道它是数组,所以 sizeof 返回数组大小,且它不能出现在赋值左边。"

换句话说:
数组名是地址常量:它的值不能改变
指针是地址变量:它的值可以改变

相关推荐
2401_841495645 小时前
【LeetCode刷题】两两交换链表中的节点
数据结构·python·算法·leetcode·链表·指针·迭代法
极客代码6 天前
深入解析C语言中的函数指针:原理、规则与实践
c语言·开发语言·指针·状态机·函数·函数指针
zhongvv11 天前
对单片机C语言指针的一些理解
c语言·数据结构·单片机·指针·汇编语言
寻星探路12 天前
【算法通关】双指针技巧深度解析:从基础到巅峰(Java 最优解)
java·开发语言·人工智能·python·算法·ai·指针
SoveTingღ15 天前
【C语言】什么是野指针?
c语言·指针·嵌入式软件
源代码•宸15 天前
Golang基础语法(go语言指针、go语言方法、go语言接口、go语言断言)
开发语言·经验分享·后端·golang·接口·指针·方法
星火开发设计16 天前
链表详解及C++实现
数据结构·c++·学习·链表·指针·知识
一起养小猫22 天前
LeetCode100天Day7-移动零与搜索插入位置
数据结构·算法·leetcode·指针
SunkingYang1 个月前
QT如何将char*转QString
qt·qstring·指针·转换·char