指针(一):指针的概念与定义、取地址运算符(&)与解引用运算符(*)、指针的算术运算、指针与数组的关系
指针,是C语言的灵魂,也是很多初学者觉得“难啃”的硬骨头。说实话,我当年学指针时也绕了好一阵子。但一旦你理解了它的本质——指针就是地址,一切就豁然开朗了。
这一章,我们就把指针最核心的几个概念掰开揉碎,讲清楚。
1. 指针的概念与定义
指针是什么?说白了,指针就是一个变量,它存的是另一个变量的内存地址。你想想看,普通变量存的是值,指针变量存的是地址。
定义指针的语法很简单:
int *p; // p是一个指向int类型变量的指针
char *cp; // cp是一个指向char类型变量的指针
double *dp; // dp是一个指向double类型变量的指针
这里的 * 是类型说明符,告诉编译器:我要定义一个指针变量。至于它指向什么类型,由前面的类型名决定。
* 紧挨着变量名,比如 int *p;。这样读起来更自然——“p是一个指针,指向int”。
指针变量本身也占内存空间。在32位系统上,所有指针都是4字节;在64位系统上,都是8字节。不管它指向的是int、char还是结构体,指针变量的大小都一样。
2. 取地址运算符(&)与解引用运算符(*)
这两个运算符是操作指针的左右手。
- &:取地址运算符,获取一个变量的内存地址
- *:解引用运算符,通过指针访问它指向的变量
看个例子:
int a = 10;
int *p = &a; // p保存了a的地址
printf("a的值:%d\n", a); // 输出10
printf("a的地址:%p\n", &a); // 输出a的地址
printf("p的值:%p\n", p); // 输出p的值,也就是a的地址
printf("*p的值:%d\n", *p); // 输出10,通过p访问a
*p = 20; // 等价于 a = 20
printf("a的新值:%d\n", a); // 输出20
这里有个容易混淆的点:定义时的 * 和用时的 * 是两回事。定义时 int *p 的 * 是类型说明符;使用时 *p 的 * 是解引用运算符。
int *p = &a; 是把 &a 赋给了 *p。其实不是!是赋给了 p 本身。记住:p 存地址,*p 才是值。
3. 指针的算术运算
指针也能做加减运算,但它的加减和普通整数不一样。指针加1,不是地址值加1,而是加上它所指向类型的大小。
举个例子:
int arr[3] = {10, 20, 30};
int *p = arr; // p指向arr[0]
printf("p = %p\n", p); // 假设输出 0x1000
printf("p+1 = %p\n", p+1); // 输出 0x1004(int占4字节)
printf("p+2 = %p\n", p+2); // 输出 0x1008
为什么会这样?因为指针的加减运算考虑了类型大小。p+1 实际上跳过了4个字节,直接指向下一个int元素。
| 指针运算 | 实际效果 | 说明 |
|---|---|---|
| p + n | 地址 + n * sizeof(类型) | 向后移动n个元素 |
| p - n | 地址 - n * sizeof(类型) | 向前移动n个元素 |
| p1 - p2 | (地址差) / sizeof(类型) | 两个指针之间相差的元素个数 |
4. 指针与数组的关系
数组名和指针之间有着千丝万缕的联系。说白了,数组名就是指向数组第一个元素的指针常量。
int arr[5] = {1, 2, 3, 4, 5};
// 下面三种写法完全等价
printf("%d\n", arr[0]); // 下标访问
printf("%d\n", *arr); // 解引用数组名
printf("%d\n", *(arr+0)); // 指针运算
// 遍历数组的两种方式
for(int i = 0; i < 5; i++) {
printf("%d ", arr[i]); // 方式一:下标
}
int *p = arr;
for(int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // 方式二:指针
}
但要注意,数组名和指针有一个重要区别:数组名是常量,不能修改。你不能写 arr++,但可以写 p++。
arr++。编译器没报错,但运行结果完全乱套。从那以后,我习惯把数组参数明确写成指针形式,提醒自己别犯这种错。
另外,当数组作为函数参数时,它会退化为指针。也就是说,下面两种函数声明是等价的:
void func(int arr[]); // 数组形式
void func(int *arr); // 指针形式(实际就是这种)
所以在函数内部用 sizeof(arr) 得到的是指针的大小,不是数组的大小。这个坑,我见过太多人踩了。
知识体系总览
下面这张图把本章的核心逻辑串起来了:
这张图把指针的四个核心知识点串在了一起。你看,指针本身只是一个地址,但通过类型信息,它知道怎么解读这块内存、怎么移动、怎么和数组配合。
嗯,这一章的内容就到这里。指针的概念是基础中的基础,后面的章节我们会在这个基础上继续深入。记住我的一句话:指针不可怕,可怕的是你以为你懂了,其实没懂。多写代码,多调试,慢慢就熟了。
公众号:蓝海资料掘金营,微信deep3321