C: 指针基础
指针就像门牌号——它本身不是房子,但通过门牌号你能精确找到那栋房子。掌握了地址,你就掌握了C语言最强大的力量。
1. 什么是指针
内存中每个字节都有唯一的编号,称为地址。指针变量就是专门存储地址的变量。
int x = 42;
int *p = &x;
x是整型变量,值为42&x是x在内存中的地址p是指针变量,存储了x的地址*p通过地址访问x的值,即42
指针本身也占内存。在32位系统上指针占4字节,64位系统占8字节,与指向的类型无关。
2. 取地址运算符 &
&用于获取变量的地址:
int a = 10;
printf("a的值: %d\n", a);
printf("a的地址: %p\n", (void *)&a);
%p格式说明符用于打印地址,参数需要转换为void *。
可以取哪些东西的地址:
- 变量:
&x - 数组元素:
&arr[3] - 结构体成员:
&s.age
不能取的东西:
- 字面量:
&10(非法) - 寄存器变量:
&r(如果r是register) - 表达式结果:
&(a+b)(非法)
3. 解引用运算符 *
*用于通过指针访问其指向的数据:
int x = 42;
int *p = &x;
printf("%d\n", *p);
*p = 100;
printf("%d\n", x);
第一次输出42,第二次输出100。*p = 100通过指针修改了x的值。
*在声明中表示"这是指针",在表达式中表示"解引用"(取指向的值)。语境不同,含义不同。
解引用未初始化的指针是严重错误:
int *p;
*p = 10;
p的值是随机的,写入随机地址可能破坏其他数据或直接崩溃。
4. NULL指针
NULL是特殊的指针值,表示"不指向任何地方"。定义在<stddef.h>等多个头文件中,值通常为0。
int *p = NULL;
if (p == NULL) {
printf("指针未指向有效地址\n");
}
解引用NULL指针会导致段错误(segmentation fault),程序崩溃。因此在使用指针前应检查是否为NULL:
void safe_print(int *p) {
if (p != NULL) {
printf("%d\n", *p);
}
}
5. 指针类型与步长
指针类型决定了解引用时读取多少字节,以及指针加减运算时移动多少字节(步长)。
int a = 10;
double b = 3.14;
int *pi = &a;
double *pd = &b;
*pi读取4字节(int大小),*pd读取8字节(double大小)pi+1移动4字节到下一个int位置,pd+1移动8字节到下一个double位置
指针步长等于指向类型的大小:
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%d\n", *p);
printf("%d\n", *(p + 1));
printf("%d\n", *(p + 3));
输出10、20、40。p+1不是地址值加1,而是加了sizeof(int)即4字节,指向下一个int元素。
指针差值的结果也是以元素个数为单位:
int *q = &arr[4];
printf("%ld\n", (long)(q - p));
输出4,表示两个指针之间隔了4个int元素。
int *p = &d;(d是double)会编译警告,因为类型不匹配导致解引用读取的字节数不对。
▶ 示例
通过指针交换两个变量的值:
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main(void) {
int x = 10, y = 20;
printf("交换前: x=%d, y=%d\n", x, y);
swap(&x, &y);
printf("交换后: x=%d, y=%d\n", x, y);
return 0;
}
交换前: x=10, y=20
交换后: x=20, y=10
这里传递地址给函数,函数通过解引用修改了原始变量。这就是"模拟引用传递"的经典手法——函数参数是int *指针,调用时传&x。
6. 指针与const
const与指针组合有两种含义:
(1) 指向常量的指针
const int *p = &x;
*p = 20;
非法。不能通过p修改指向的数据,但p本身可以指向别的变量:
p = &y;
合法。
(2) 指针常量
int * const p = &x;
p = &y;
非法。p不能改变指向,但可以通过p修改数据:
*p = 20;
合法。
记忆技巧:const在*左边修饰数据,在*右边修饰指针本身。
▶ 示例
用指针遍历数组求和:
#include <stdio.h>
int array_sum(const int *arr, int len) {
int sum = 0;
const int *end = arr + len;
while (arr < end) {
sum += *arr;
arr++;
}
return sum;
}
int main(void) {
int data[] = {3, 7, 1, 9, 5};
int total = array_sum(data, 5);
printf("总和: %d\n", total);
return 0;
}
总和: 25
参数const int *arr表示函数不会修改数组内容。用指针end标记终止位置,避免使用下标。
❓ 常见问题
int *p中*属于类型还是变量?int* p, q;声明p是int指针,q是普通int。建议每行只声明一个指针变量。📖 小节
- 指针存储地址,
&取地址,*解引用取值 - 未初始化指针是野指针,务必初始化为NULL
- 指针类型决定步长和解引用读取的字节数
- 传递指针给函数可以修改调用者的变量
const修饰指针有两种位置:修饰数据或修饰指针本身
📝 作业
- 编写函数
void divide(int a, int b, int *quotient, int *remainder),通过指针参数返回商和余数。 - 编写程序,定义int变量和double变量,分别用
int *和double *指向它们,打印地址和值,观察步长差异。 - 编写函数
int *find_max(int *arr, int len),返回数组中最大元素的指针。