C: 指针基础

指针就像门牌号——它本身不是房子,但通过门牌号你能精确找到那栋房子。掌握了地址,你就掌握了C语言最强大的力量。

1. 什么是指针

内存中每个字节都有唯一的编号,称为地址。指针变量就是专门存储地址的变量。

C
int x = 42;
int *p = &x;

指针本身也占内存。在32位系统上指针占4字节,64位系统占8字节,与指向的类型无关。


2. 取地址运算符 &

&用于获取变量的地址:

C
int a = 10;
printf("a的值: %d\n", a);
printf("a的地址: %p\n", (void *)&a);

%p格式说明符用于打印地址,参数需要转换为void *

可以取哪些东西的地址:

不能取的东西:


3. 解引用运算符 *

*用于通过指针访问其指向的数据:

C
int x = 42;
int *p = &x;

printf("%d\n", *p);
*p = 100;
printf("%d\n", x);

第一次输出42,第二次输出100。*p = 100通过指针修改了x的值。

💡 *在声明中表示"这是指针",在表达式中表示"解引用"(取指向的值)。语境不同,含义不同。

解引用未初始化的指针是严重错误:

C
int *p;
*p = 10;

p的值是随机的,写入随机地址可能破坏其他数据或直接崩溃。


4. NULL指针

NULL是特殊的指针值,表示"不指向任何地方"。定义在<stddef.h>等多个头文件中,值通常为0。

C
int *p = NULL;

if (p == NULL) {
    printf("指针未指向有效地址\n");
}

解引用NULL指针会导致段错误(segmentation fault),程序崩溃。因此在使用指针前应检查是否为NULL:

C
void safe_print(int *p) {
    if (p != NULL) {
        printf("%d\n", *p);
    }
}
⚠️ 指针定义后如果不立即赋值,务必初始化为NULL。未初始化的"野指针"值不确定,比NULL指针更危险,因为它无法通过NULL检查发现。


5. 指针类型与步长

指针类型决定了解引用时读取多少字节,以及指针加减运算时移动多少字节(步长)。

C
int a = 10;
double b = 3.14;

int *pi = &a;
double *pd = &b;

指针步长等于指向类型的大小:

C
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元素。

指针差值的结果也是以元素个数为单位:

C
int *q = &arr[4];
printf("%ld\n", (long)(q - p));

输出4,表示两个指针之间隔了4个int元素。

💡 不同类型的指针之间不能直接赋值。int *p = &d;(d是double)会编译警告,因为类型不匹配导致解引用读取的字节数不对。

▶ 示例

通过指针交换两个变量的值:

C
#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;
}
▶ 试一试
TEXT 📖 仅展示
交换前: x=10, y=20
交换后: x=20, y=10

这里传递地址给函数,函数通过解引用修改了原始变量。这就是"模拟引用传递"的经典手法——函数参数是int *指针,调用时传&x


6. 指针与const

const与指针组合有两种含义:

(1) 指向常量的指针

C
const int *p = &x;
*p = 20;

非法。不能通过p修改指向的数据,但p本身可以指向别的变量:

C
p = &y;

合法。

(2) 指针常量

C
int * const p = &x;
p = &y;

非法。p不能改变指向,但可以通过p修改数据:

C
*p = 20;

合法。

记忆技巧:const*左边修饰数据,在*右边修饰指针本身。

▶ 示例

用指针遍历数组求和:

C
#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;
}
▶ 试一试
TEXT 📖 仅展示
总和: 25

参数const int *arr表示函数不会修改数组内容。用指针end标记终止位置,避免使用下标。

❓ 常见问题

Q 指针变量占多少字节?
A 取决于系统地址宽度。32位系统4字节,64位系统8字节,与指向类型无关。
Q int *p*属于类型还是变量?
A 属于变量。int* p, q;声明p是int指针,q是普通int。建议每行只声明一个指针变量。
Q NULL和未初始化的指针有什么区别?
A NULL是确定的值0,可以检查判断;未初始化指针值随机,无法可靠检测,更危险。
Q 解引用NULL指针一定会崩溃吗?
A 不保证。地址0在某些嵌入式系统可能是有效地址。但在主流操作系统上,访问地址0会触发段错误。

📖 小节

📝 作业

  1. 编写函数void divide(int a, int b, int *quotient, int *remainder),通过指针参数返回商和余数。
  2. 编写程序,定义int变量和double变量,分别用int *double *指向它们,打印地址和值,观察步长差异。
  3. 编写函数int *find_max(int *arr, int len),返回数组中最大元素的指针。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏