Skip to content

指针

指针是 C 语言最强大也最复杂的特性,是理解 C 语言的核心。

指针基础

什么是指针

指针是一个变量,其值为另一个变量的地址。

c
#include <stdio.h>

int main() {
    int a = 10;
    int *p;      // 声明指针变量

    p = &a;      // 将 a 的地址赋给 p

    printf("a 的值: %d\n", a);
    printf("a 的地址: %p\n", (void*)&a);
    printf("p 的值: %p\n", (void*)p);
    printf("*p 的值: %d\n", *p);  // 解引用

    return 0;
}

指针声明与初始化

c
#include <stdio.h>

int main() {
    // 声明指针
    int *p1;       // 推荐:* 靠近变量名
    int* p2;       // 也可以
    int * p3;      // 不推荐

    // 初始化
    int a = 10;
    int *p = &a;   // 初始化为 a 的地址
    int *null_p = NULL;  // 空指针

    // 未初始化的指针是危险的
    int *wild_p;   // 野指针,包含随机地址

    // 指针的大小
    printf("指针大小: %zu bytes\n", sizeof(p));
    // 32位系统: 4 bytes, 64位系统: 8 bytes

    return 0;
}

指针运算符

c
#include <stdio.h>

int main() {
    int a = 10;
    int *p = &a;

    // & 取地址运算符
    printf("a 的地址: %p\n", (void*)&a);
    printf("p 的值: %p\n", (void*)p);

    // * 解引用运算符
    printf("*p = %d\n", *p);

    // 通过指针修改值
    *p = 20;
    printf("修改后 a = %d\n", a);

    // 指针的指针
    int **pp = &p;
    printf("**pp = %d\n", **pp);

    return 0;
}

指针与数组

数组名与指针

c
#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;  // 数组名就是首元素地址

    printf("arr = %p\n", (void*)arr);
    printf("&arr[0] = %p\n", (void*)&arr[0]);
    printf("p = %p\n", (void*)p);

    // 访问数组元素
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d, *(p+%d) = %d\n",
               i, arr[i], i, *(p + i));
    }

    // 指针下标访问
    for (int i = 0; i < 5; i++) {
        printf("p[%d] = %d\n", i, p[i]);
    }

    return 0;
}

指针算术运算

c
#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;

    printf("*p = %d\n", *p);      // 10

    p++;  // 移动到下一个元素
    printf("*p = %d\n", *p);      // 20

    p += 2;  // 移动2个元素
    printf("*p = %d\n", *p);      // 40

    p--;  // 回退一个元素
    printf("*p = %d\n", *p);      // 30

    // 指针相减
    int *p1 = arr;
    int *p2 = arr + 3;
    printf("p2 - p1 = %ld\n", p2 - p1);  // 3

    // 指针比较
    printf("p1 < p2: %d\n", p1 < p2);    // 1

    return 0;
}

指针与多维数组

c
#include <stdio.h>

int main() {
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    // 数组名是首行地址
    printf("matrix = %p\n", (void*)matrix);
    printf("matrix[0] = %p\n", (void*)matrix[0]);
    printf("&matrix[0][0] = %p\n", (void*)&matrix[0][0]);

    // 访问元素
    printf("matrix[1][2] = %d\n", matrix[1][2]);
    printf("*(*(matrix + 1) + 2) = %d\n", *(*(matrix + 1) + 2));

    // 使用指针遍历
    int *p = &matrix[0][0];
    for (int i = 0; i < 12; i++) {
        printf("%d ", *(p + i));
    }
    printf("\n");

    // 行指针
    int (*row)[4] = matrix;  // 指向包含4个int的数组
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", row[i][j]);
        }
        printf("\n");
    }

    return 0;
}

指针与函数

指针作为参数

c
#include <stdio.h>

// 交换两个变量的值
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 通过指针返回多个值
void minmax(int arr[], int size, int *min, int *max) {
    *min = arr[0];
    *max = arr[0];

    for (int i = 1; i < size; i++) {
        if (arr[i] < *min) *min = arr[i];
        if (arr[i] > *max) *max = arr[i];
    }
}

int main() {
    int x = 10, y = 20;
    printf("交换前: x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("交换后: x = %d, y = %d\n", x, y);

    int arr[] = {5, 2, 8, 1, 9, 3};
    int min, max;
    minmax(arr, 6, &min, &max);
    printf("最小值: %d, 最大值: %d\n", min, max);

    return 0;
}

指针作为返回值

c
#include <stdio.h>

// 返回数组中的最大元素地址
int* find_max(int arr[], int size) {
    int *max = arr;
    for (int i = 1; i < size; i++) {
        if (arr[i] > *max) {
            max = &arr[i];
        }
    }
    return max;
}

// 返回静态数组的指针
int* get_static_array() {
    static int arr[5] = {1, 2, 3, 4, 5};
    return arr;
}

int main() {
    int arr[] = {5, 2, 8, 1, 9, 3};
    int *max = find_max(arr, 6);
    printf("最大值: %d\n", *max);
    printf("最大值位置: %ld\n", max - arr);

    int *static_arr = get_static_array();
    printf("静态数组: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", static_arr[i]);
    }
    printf("\n");

    return 0;
}

函数指针

c
#include <stdio.h>
#include <stdlib.h>

// 普通函数
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }

// 使用函数指针作为参数
int calculate(int a, int b, int (*op)(int, int)) {
    return op(a, b);
}

// 回调函数示例
void process_array(int arr[], int size, void (*process)(int*)) {
    for (int i = 0; i < size; i++) {
        process(&arr[i]);
    }
}

void double_value(int *n) { *n *= 2; }
void print_value(int *n) { printf("%d ", *n); }

int main() {
    // 声明函数指针
    int (*fp)(int, int);

    // 赋值
    fp = add;
    printf("add(5, 3) = %d\n", fp(5, 3));

    fp = subtract;
    printf("subtract(5, 3) = %d\n", fp(5, 3));

    // 函数指针数组
    int (*operations[])(int, int) = {add, subtract, multiply};
    char *names[] = {"加法", "减法", "乘法"};

    for (int i = 0; i < 3; i++) {
        printf("%s: %d\n", names[i], operations[i](10, 5));
    }

    // 使用函数指针作为参数
    printf("calculate(10, 5, add) = %d\n", calculate(10, 5, add));

    // 回调函数
    int arr[] = {1, 2, 3, 4, 5};
    printf("原数组: ");
    process_array(arr, 5, print_value);
    printf("\n");

    process_array(arr, 5, double_value);
    printf("翻倍后: ");
    process_array(arr, 5, print_value);
    printf("\n");

    // qsort 使用函数指针
    int nums[] = {5, 2, 8, 1, 9, 3};
    int compare(const void *a, const void *b) {
        return (*(int*)a - *(int*)b);
    }
    qsort(nums, 6, sizeof(int), compare);

    printf("排序后: ");
    for (int i = 0; i < 6; i++) {
        printf("%d ", nums[i]);
    }
    printf("\n");

    return 0;
}

指针与字符串

字符串指针

c
#include <stdio.h>
#include <string.h>

int main() {
    // 字符数组
    char str1[] = "Hello";
    str1[0] = 'h';  // 可以修改
    printf("str1: %s\n", str1);

    // 字符串常量指针
    char *str2 = "World";
    // str2[0] = 'w';  // 错误:修改字符串常量是未定义行为
    printf("str2: %s\n", str2);

    // 遍历字符串
    char *p = str1;
    while (*p != '\0') {
        printf("%c ", *p);
        p++;
    }
    printf("\n");

    // 字符串函数使用指针
    char src[] = "Hello";
    char dest[20];
    strcpy(dest, src);
    printf("复制后: %s\n", dest);

    // 自定义字符串长度函数
    size_t my_strlen(const char *s) {
        const char *p = s;
        while (*p) p++;
        return p - s;
    }
    printf("字符串长度: %zu\n", my_strlen("Hello"));

    return 0;
}

字符串数组

c
#include <stdio.h>

int main() {
    // 二维字符数组
    char fruits[][10] = {
        "Apple",
        "Banana",
        "Cherry"
    };

    for (int i = 0; i < 3; i++) {
        printf("%s\n", fruits[i]);
    }

    // 指针数组(更灵活)
    char *colors[] = {
        "Red",
        "Green",
        "Blue"
    };

    for (int i = 0; i < 3; i++) {
        printf("%s\n", colors[i]);
    }

    // 命令行参数
    // int main(int argc, char *argv[])
    // argv 就是指针数组

    return 0;
}

指针高级用法

void 指针

c
#include <stdio.h>
#include <string.h>

// 通用交换函数
void generic_swap(void *a, void *b, size_t size) {
    char temp[size];
    memcpy(temp, a, size);
    memcpy(a, b, size);
    memcpy(b, temp, size);
}

// 通用打印函数
void print_value(void *ptr, char type) {
    switch (type) {
        case 'i':
            printf("int: %d\n", *(int*)ptr);
            break;
        case 'f':
            printf("float: %f\n", *(float*)ptr);
            break;
        case 'd':
            printf("double: %lf\n", *(double*)ptr);
            break;
        case 'c':
            printf("char: %c\n", *(char*)ptr);
            break;
    }
}

int main() {
    // void 指针可以指向任何类型
    int a = 10;
    float b = 3.14f;
    char c = 'A';

    void *vp;

    vp = &a;
    printf("int: %d\n", *(int*)vp);

    vp = &b;
    printf("float: %f\n", *(float*)vp);

    vp = &c;
    printf("char: %c\n", *(char*)vp);

    // 通用交换
    int x = 10, y = 20;
    printf("交换前: x=%d, y=%d\n", x, y);
    generic_swap(&x, &y, sizeof(int));
    printf("交换后: x=%d, y=%d\n", x, y);

    float f1 = 1.5f, f2 = 2.5f;
    printf("交换前: f1=%.2f, f2=%.2f\n", f1, f2);
    generic_swap(&f1, &f2, sizeof(float));
    printf("交换后: f1=%.2f, f2=%.2f\n", f1, f2);

    return 0;
}

const 指针

c
#include <stdio.h>

int main() {
    int a = 10;
    int b = 20;

    // 指向常量的指针(不能通过指针修改值)
    const int *p1 = &a;
    // *p1 = 100;  // 错误
    p1 = &b;       // 正确:可以改变指向

    // 常量指针(指针本身不能改变)
    int * const p2 = &a;
    *p2 = 100;     // 正确:可以修改值
    // p2 = &b;    // 错误:不能改变指向

    // 指向常量的常量指针
    const int * const p3 = &a;
    // *p3 = 200;  // 错误
    // p3 = &b;    // 错误

    printf("a = %d\n", a);

    return 0;
}

指针与结构体

c
#include <stdio.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
};

void print_person(struct Person *p) {
    printf("姓名: %s, 年龄: %d\n", p->name, p->age);
}

void update_age(struct Person *p, int new_age) {
    p->age = new_age;
}

int main() {
    struct Person person = {"张三", 25};
    struct Person *p = &person;

    // 访问成员
    printf("姓名: %s\n", (*p).name);
    printf("年龄: %d\n", p->age);  // 使用 -> 操作符

    // 修改成员
    strcpy(p->name, "李四");
    p->age = 30;

    print_person(p);
    update_age(p, 35);
    print_person(&person);

    // 结构体指针数组
    struct Person people[] = {
        {"张三", 25},
        {"李四", 30},
        {"王五", 28}
    };

    struct Person *pp = people;
    for (int i = 0; i < 3; i++) {
        printf("%s: %d\n", pp[i].name, pp[i].age);
    }

    return 0;
}

指针常见问题

空指针

c
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = NULL;

    // 使用前检查
    if (p != NULL) {
        printf("%d\n", *p);
    } else {
        printf("指针为空\n");
    }

    // malloc 可能返回 NULL
    int *arr = (int*)malloc(1000000000000);
    if (arr == NULL) {
        printf("内存分配失败\n");
    }

    // 使用后置空
    free(arr);
    arr = NULL;

    return 0;
}

野指针

c
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 未初始化的指针(野指针)
    int *p1;  // 危险!

    // 正确做法
    int *p2 = NULL;

    // 悬空指针
    int *p3 = (int*)malloc(sizeof(int));
    *p3 = 10;
    free(p3);
    // p3 现在是悬空指针
    // *p3 = 20;  // 危险!

    // 正确做法
    p3 = NULL;

    // 返回局部变量地址
    int* bad_function() {
        int local = 10;
        return &local;  // 危险!返回局部变量地址
    }

    // 正确做法
    int* good_function() {
        static int local = 10;
        return &local;  // 正确:静态变量
    }

    return 0;
}

指针越界

c
#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;

    // 正确访问
    for (int i = 0; i < 5; i++) {
        printf("%d ", p[i]);
    }
    printf("\n");

    // 越界访问(危险!)
    // printf("%d\n", p[5]);   // 越界
    // printf("%d\n", p[-1]);  // 越界

    // 指针越界后的运算
    int *end = arr + 5;  // 指向数组末尾的下一个位置
    // 可以比较,但不能解引用
    while (p < end) {
        printf("%d ", *p++);
    }
    printf("\n");

    return 0;
}

实践示例

动态数组

c
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int *data;
    int size;
    int capacity;
} DynamicArray;

DynamicArray* create_array(int capacity) {
    DynamicArray *arr = (DynamicArray*)malloc(sizeof(DynamicArray));
    arr->data = (int*)malloc(capacity * sizeof(int));
    arr->size = 0;
    arr->capacity = capacity;
    return arr;
}

void push_back(DynamicArray *arr, int value) {
    if (arr->size == arr->capacity) {
        arr->capacity *= 2;
        arr->data = (int*)realloc(arr->data, arr->capacity * sizeof(int));
    }
    arr->data[arr->size++] = value;
}

int get(DynamicArray *arr, int index) {
    if (index < 0 || index >= arr->size) {
        printf("索引越界\n");
        return -1;
    }
    return arr->data[index];
}

void free_array(DynamicArray *arr) {
    free(arr->data);
    free(arr);
}

int main() {
    DynamicArray *arr = create_array(5);

    for (int i = 0; i < 10; i++) {
        push_back(arr, i * 10);
    }

    for (int i = 0; i < arr->size; i++) {
        printf("%d ", get(arr, i));
    }
    printf("\n");

    free_array(arr);
    return 0;
}

链表实现

c
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node *next;
} Node;

Node* create_node(int data) {
    Node *node = (Node*)malloc(sizeof(Node));
    node->data = data;
    node->next = NULL;
    return node;
}

void append(Node **head, int data) {
    Node *new_node = create_node(data);

    if (*head == NULL) {
        *head = new_node;
        return;
    }

    Node *current = *head;
    while (current->next != NULL) {
        current = current->next;
    }
    current->next = new_node;
}

void print_list(Node *head) {
    Node *current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

void free_list(Node *head) {
    Node *current = head;
    while (current != NULL) {
        Node *temp = current;
        current = current->next;
        free(temp);
    }
}

int main() {
    Node *head = NULL;

    append(&head, 1);
    append(&head, 2);
    append(&head, 3);
    append(&head, 4);
    append(&head, 5);

    print_list(head);

    free_list(head);
    return 0;
}