1. 数组与指针的本质联系
在C语言中,数组和指针的关系就像是一枚硬币的两面。当我们声明一个数组时,比如int arr[5],这个数组名arr实际上就是一个指向数组首元素的常量指针。这个特性使得我们可以用两种完全不同的方式来操作数组元素。
1.1 数组名的真实身份
很多人初学C语言时会误以为数组名就是数组本身,但实际上它更像是一个"快捷方式"。让我们通过一个简单的例子来理解:
c复制int numbers[3] = {10, 20, 30};
printf("%p\n", numbers); // 输出数组首地址
printf("%p\n", &numbers[0]); // 同样输出数组首地址
这两个printf语句会输出相同的地址值,这证明了numbers和&numbers[0]是等价的。这种等价性是理解后续指针操作的基础。
注意:虽然数组名表现为指针,但它是一个常量指针,不能进行自增或赋值操作。例如
numbers++这样的语句会导致编译错误。
1.2 指针算术的魔法
指针之所以能替代数组下标,关键在于指针算术的特性。当我们对指针进行加减运算时,编译器会自动考虑数据类型的大小:
c复制int *ptr = numbers;
printf("%d\n", *ptr); // 输出10
printf("%d\n", *(ptr+1));// 输出20
这里的ptr+1并不是简单地将地址值加1,而是加上了sizeof(int)(通常是4字节)。这种自动调整的特性使得指针可以像数组索引一样遍历元素。
1.3 内存视角下的数组访问
从内存角度看,数组就是一块连续的内存区域。假设我们有一个char数组:
c复制char str[] = "Hello";
在内存中的布局可能是这样的:
| 地址 | 值 | 字符 |
|---|---|---|
| 1000 | 'H' | str[0] |
| 1001 | 'e' | str[1] |
| 1002 | 'l' | str[2] |
| 1003 | 'l' | str[3] |
| 1004 | 'o' | str[4] |
| 1005 | '\0' | str[5] |
当我们使用str[3]访问时,编译器实际上将其转换为*(str + 3),也就是从首地址开始,向后移动3个char大小的位置(通常是3字节),然后解引用获取值。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. scanf与printf的指针操作
理解了数组和指针的关系后,我们来看看如何用指针方式实现scanf和printf的数组操作。这是C语言中非常实用且高效的技术。
2.1 scanf的地址传递原理
scanf函数需要知道变量的存储位置才能正确写入数据,这就是为什么我们需要使用&运算符获取变量地址。但对于数组元素,我们有多种传递地址的方式:
c复制int arr[5];
// 传统下标法
scanf("%d", &arr[0]);
// 指针法
scanf("%d", arr); // arr本身就是地址
scanf("%d", arr + 2); // 等价于&arr[2]
对于字符串输入,情况有些特殊:
c复制char name[20];
scanf("%s", na
