ToolkitX
知识库工具箱

指针深入

多级指针、函数指针、指针数组

25min·进阶

01. 什么是指针

指针就是存内存地址的变量。就像快递地址,指针里存的是某个东西放在内存里的哪个位置。声明指针用星号,取地址用 &,解引用用 *(到那个地址去拿东西)。理解指针是 C 语言最重要的基本功,数组、字符串、动态内存都和指针有关。
c
#include <stdio.h>

int main() {
    int a = 42;
    int *p = &a;     // p 存的是 a 的地址
    
    printf("a 的值: %d\n", a);       // 42
    printf("a 的地址: %p\n", &a);    // 某个地址如 0x7fff...
    printf("p 存的值(地址): %p\n", p);
    printf("p 指向的值: %d\n", *p);  // 42
    
    *p = 100;       // 通过指针修改 a 的值
    printf("a 的新值: %d\n", a);     // 100
    
    return 0;
}
星号在声明时表示这个变量是指针,在使用时表示解引用。

02. 指针运算和数组

数组名本身就是指向第一个元素的指针。arr[i] 本质上是 *(arr + i),编译器帮你翻译了。指针加减整数会移动指针的位置,移动的字节数是类型大小乘以整数。指针相减得到两个地址之间隔了几个元素。空指针用 NULL 表示,解引用 NULL 会直接崩溃。
c
#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;      // 指向第一个元素
    
    printf("%d\n", *p);        // 10
    printf("%d\n", *(p + 1));  // 20 - 指针+1跳到下一个int
    printf("%d\n", p[2]);      // 30 - 等价于*(p+2)
    
    // 遍历数组
    for (int *ptr = arr; ptr < arr + 5; ptr++) {
        printf("%d ", *ptr);   // 10 20 30 40 50
    }
    
    // 指针相减
    int *p1 = &arr[4];
    int *p2 = &arr[1];
    printf("相隔 %ld 个元素\n", p1 - p2); // 3
    
    return 0;
}
指针运算超出了数组边界(越界),读到的是垃圾数据,写到外面可能破坏其他变量。

03. 指针和函数

C 语言函数参数是值传递的,传进去的是副本。如果函数里要修改外部的变量,必须传指针(传地址)。swap 交换函数是最经典的例子。指针作为返回值时要小心,不能返回局部变量的地址,因为函数返回后局部变量就释放了。
c
#include <stdio.h>

// 值传递 - 改不了
void bad_swap(int a, int b) {
    int tmp = a;
    a = b;
    b = tmp;
}

// 指针传递 - 可以改
void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

int main() {
    int x = 10, y = 20;
    swap(&x, &y);
    printf("x=%d, y=%d\n", x, y); // x=20, y=10
    
    return 0;
}
需要修改外部变量时传指针,这是 C 语言里最常见的指针用途。

04. 指针数组和数组指针

指针数组是存指针的数组:int *arr[5]。数组指针是指向数组的指针:int (*p)[5],括号不能省因为 [] 优先级比 * 高。最常用的是字符串数组:char *arr[] 存了一堆字符串的指针。main 函数的参数 char *argv[] 就是一个指针数组。
c
#include <stdio.h>

int main() {
    // 指针数组:每个元素是一个指针
    const char *strs[] = {"C语言", "指针", "数组"};
    for (int i = 0; i < 3; i++) {
        printf("%s\n", strs[i]);
    }
    
    // 数组指针:指向一整个数组
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*p)[3] = matrix; // p 指向有3个int的数组
    
    printf("%d\n", p[0][1]); // 2
    printf("%d\n", p[1][2]); // 6
    
    return 0;
}
读 C 声明有个技巧:从名字开始,先看右边再看左边。int *p[5]:p 是数组(先看右边[5]),数组里存的是指针(再看左边*)。

05. 函数指针

函数指针存的是函数的入口地址。声明语法有点奇怪:返回值 (*指针名)(参数列表)。用某个函数名赋值,通过指针名调函数。函数指针常用作回调函数,比如 qsort 排序时要传一个比较函数,信号处理注册回调,还有模仿面向对象的多态。
c
#include <stdio.h>
#include <stdlib.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

// 函数指针作为参数
int calculate(int (*op)(int, int), int x, int y) {
    return op(x, y);
}

// qsort 比较函数
int compare(const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}

int main() {
    int (*func)(int, int); // 函数指针声明
    func = add;
    printf("add: %d\n", func(3, 5)); // 8
    
    func = sub;
    printf("sub: %d\n", func(3, 5)); // -2
    
    printf("calc: %d\n", calculate(add, 10, 20)); // 30
    
    // qsort 使用
    int arr[] = {5, 2, 8, 1, 3};
    qsort(arr, 5, sizeof(int), compare);
    
    return 0;
}
函数指针的声明看起来很复杂,可以用 typedef 简化:typedef int (*Operation)(int, int);

知识测验

1/4正确 0

C 语言中取变量的地址用什么运算符?

下一节

内存管理

下一节