C语言指针是存储内存地址的变量,作为C语言的“灵魂”,它贯穿程序设计的方方面面:通过&取地址、*解引用实现内存直接操控;支撑数组遍历(arr+i)、字符串处理(以\0结尾的字符指针)、函数参数传递(实现引用调用与交换)、动态内存管理(malloc/free配合二级指针)、函数指针(实现回调、策略模式、事件系统)及复杂数据结构(链表、树)构建;同时需严格遵循安全规范——初始化为NULL、使用前校验、释放后置空、避免越界与悬空指针。指针赋予C语言极致的效率与灵活性,但也要求开发者具备严谨的内存意识:善用则如虎添翼,误用则隐患丛生。掌握指针,即是掌握C语言高效、底层编程的核心能力。

指针是C语言的灵魂,掌握指针是成为C语言高手的必经之路。本文将系统、全面地讲解C语言指针的方方面面,包含核心概念、详细应用场景及完整代码示例。


一、指针基础概念

1.1 什么是指针?

  • 指针本质:存储内存地址的变量("门牌号"概念)
  • 指针变量:专门用于存放地址的变量容器
  • 关键符号
    • &:取地址运算符
    • *:声明指针 / 解引用运算符

1.2 指针声明与初始化

#include <stdio.h>

int main() {
    int var = 42;
    int *ptr = &var;  // 声明并初始化指针
    
    printf("变量var的值: %d\n", var);
    printf("变量var的地址: %p\n", (void*)&var);
    printf("指针ptr存储的地址: %p\n", (void*)ptr);
    printf("通过指针访问的值: %d\n", *ptr);  // 解引用
    
    *ptr = 100;  // 通过指针修改原变量
    printf("修改后var的值: %d\n", var);
    
    return 0;
}

输出

变量var的值: 42
变量var的地址: 0x7ffd4f4a5a4c
指针ptr存储的地址: 0x7ffd4f4a5a4c
通过指针访问的值: 42
修改后var的值: 100

1.3 指针大小与平台关系

#include <stdio.h>

int main() {
    printf("int指针大小: %zu 字节\n", sizeof(int*));
    printf("char指针大小: %zu 字节\n", sizeof(char*));
    printf("double指针大小: %zu 字节\n", sizeof(double*));
    // 32位系统均为4字节,64位系统均为8字节
    return 0;
}

二、指针核心运算规则

2.1 指针±整数:类型决定步长

#include <stdio.h>

int main() {
    int a = 100;
    double b = 3.14;
    char c = 'A';
    
    int *pa = &a;
    double *pb = &b;
    char *pc = &c;
    
    printf("int指针+1: %p -> %p (差%ld字节)\n", (void*)pa, (void*)(pa+1), (long)((pa+1)-pa)*sizeof(int));
    printf("double指针+1: %p -> %p (差%ld字节)\n", (void*)pb, (void*)(pb+1), (long)((pb+1)-pb)*sizeof(double));
    printf("char指针+1: %p -> %p (差%ld字节)\n", (void*)pc, (void*)(pc+1), (long)((pc+1)-pc)*sizeof(char));
    
    return 0;
}

关键结论:指针 + n 实际移动 n × sizeof(指针类型) 字节

2.2 指针相减:计算元素间隔

#include <stdio.h>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *p1 = &arr[1];
    int *p2 = &arr[4];
    
    printf("p2 - p1 = %ld (间隔3个int元素)\n", p2 - p1);
    printf("实际字节差: %ld\n", (char*)p2 - (char*)p1); // 12字节
    
    return 0;
}

三、指针与数组

3.1 一维数组遍历(两种等价写法)

#include <stdio.h>

//int size = sizeof(arr) / sizeof(int);
//在函数内部使用 sizeof(arr) 无法获取原数组的真实长度

//当数组作为参数传递给函数时(sum_array(arrSum)),C语言会将数组退化为指针。在函数 sum_array 内部:
//arr 的实际类型是 int*(指针),而非数组
//sizeof(arr) 返回的是指针本身的大小(32位系统=4字节,64位系统=8字节)
//sizeof(int) 通常是4字节
//因此 size = sizeof(arr)/sizeof(int) 计算结果为:
//32位系统:4/4 = 1
//64位系统:8/4 = 2
//循环只会执行1~2次,导致只累加了前1~2个元素(结果为1或3),而非完整的15。
int sum_array1(int *arr, int size) {
    int total = 0;
    // 写法1:指针算术
    for (int i = 0; i < size; i++) {
        total += *(arr + i);
    }
    // 写法2:指针自增(更高效)
    // int *end = arr + size;
    // while (arr < end) total += *arr++;
    return total;
}

int sum_array2(int *arr, int size) {
    //int size = sizeof(arr) / sizeof(arr[0]);
    int *end = arr + size;
    int sum = 0;
    while (arr < end) {
        sum += *arr++;
    }
    return sum;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int arrSum_size = sizeof(arrSum) / sizeof(arrSum[0]);
    printf("数组和: %d\n", sum_array(arr, 5)); // 输出15
    printf("数组和: %d\n", sum_array1(arr, arrSum_size)); // 输出15
    printf("数组和: %d\n", sum_array2(arr, arrSum_size)); // 输出15
    return 0;
}

3.2 二维数组与数组指针

#include <stdio.h>

int main() {
    int arr[3][5] = {
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 10},
        {11, 12, 13, 14, 15}
    };
    
    // 指向"包含5个int的数组"的指针
    int (*p)[5] = arr;
    
    printf("第二行第二列元素: %d\n", *(*(p + 1) + 1)); // 输出7
    printf("等价写法: %d\n", p[1][1]); // 同样输出7
    
    // 遍历整个二维数组
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 5; j++) {
            printf("%3d", *(*(p + i) + j));
        }
        printf("\n");
    }
    return 0;
}

四、指针与字符串

4.1 字符串长度计算

#include <stdio.h>

int string_length(char *str) {
    int len = 0;
    while (*str != '\0') { // 遍历直到空字符
        len++;
        str++; // 指针自增
    }
    return len;
}

int main() {
    char str[] = "Hello, Pointer!";
    printf("字符串: \"%s\"\n", str);
    printf("长度: %d (不含\\0)\n", string_length(str));
    printf("标准库验证: %zu\n", strlen(str));
    return 0;
}

4.2 字符串反转(原地操作)

#include <stdio.h>

void reverse_string(char *str) {
    char *start = str;
    char *end = str;
    while (*end) end++; // 移动到末尾
    end--; // 回退到有效字符
    
    while (start < end) {
        char temp = *start;
        *start = *end;
        *end = temp;
        start++;
        end--;
    }
}

int main() {
    char text[] = "Pointer Magic";
    printf("原字符串: %s\n", text);
    reverse_string(text);
    printf("反转后: %s\n", text); // "cigaM retnioP"
    return 0;
}

五、指针与函数

5.1 按引用传递(交换函数)

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

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

5.2 函数指针基础

#include <stdio.h>

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

int main() {
    // 声明函数指针
    int (*func_ptr)(int, int);
    
    func_ptr = add;
    printf("3 + 5 = %d\n", func_ptr(3, 5)); // 8
    
    func_ptr = subtract;
    printf("10 - 4 = %d\n", func_ptr(10, 4)); // 6
    
    return 0;
}

5.3 高级应用:函数指针实现计算器

#include <stdio.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 divide(int a, int b) { return b != 0 ? a / b : 0; }

// 计算器核心:接收函数指针作为参数
int calculator(int (*op)(int, int), int x, int y) {
    return op(x, y);
}

int main() {
    int a = 20, b = 5;
    
    printf("%d + %d = %d\n", a, b, calculator(add, a, b));
    printf("%d - %d = %d\n", a, b, calculator(subtract, a, b));
    printf("%d * %d = %d\n", a, b, calculator(multiply, a, b));
    printf("%d / %d = %d\n", a, b, calculator(divide, a, b));
    
    return 0;
}

5.4 回调函数实战:事件处理系统

#include <stdio.h>

// 回调函数类型定义
typedef void (*EventCallback)(int);

void handle_event(int event_id, EventCallback callback) {
    printf("【事件触发】ID: %d\n", event_id);
    if (callback != NULL) {
        callback(event_id); // 执行回调
    }
}

void on_login(int id) {
    printf("  → 用户登录事件处理 (ID:%d)\n", id);
}

void on_logout(int id) {
    printf("  → 用户登出事件处理 (ID:%d)\n", id);
}

int main() {
    handle_event(101, on_login);
    handle_event(102, on_logout);
    handle_event(103, NULL); // 无回调
    return 0;
}

六、动态内存管理

6.1 安全的内存分配与释放

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

int main() {
    int n = 5;
    // 动态分配整型数组
    int *arr = (int *)malloc(n * sizeof(int));
    
    if (arr == NULL) {
        fprintf(stderr, "内存分配失败!\n");
        return 1;
    }
    
    // 初始化数组
    for (int i = 0; i < n; i++) {
        arr[i] = (i + 1) * 10;
    }
    
    // 使用数组
    printf("动态数组元素: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", *(arr+i));
    }
    printf("\n");
    
    // 释放内存(关键!)
    free(arr);
    arr = NULL; // 避免野指针
    
    return 0;
}

6.2 二级指针:修改指针本身

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

// 通过二级指针动态分配内存(改变外部指针)
void create_array(int **ptr, int size) {
    *ptr = (int *)malloc(size * sizeof(int));
    for (int i = 0; i < size; i++) {
        (*ptr)[i] = i * 2;
    }
}

int main() {
    int *data = NULL;
    create_array(&data, 4);
    
    printf("动态创建的数组: ");
    for (int i = 0; i < 4; i++) {
        printf("%d ", *(data + i)); // 0 2 4 6
    }
    
    free(data);
    return 0;
}

七、指针数组 vs 数组指针

7.1 指针数组:存储多个字符串

#include <stdio.h>

int main() {
    // 指针数组:每个元素是指向字符串的指针
    char *days[] = {
        "Monday", "Tuesday", "Wednesday",
        "Thursday", "Friday", "Saturday", "Sunday"
    };
    
    printf("一周七天:\n");
    for (int i = 0; i < 7; i++) {
        printf("%d,%s\n", i + 1, *(days + i));
    }
    
    return 0;
}

7.2 数组指针:指向整个数组

#include <stdio.h>

int main() {
    int arr[3][4] = {
        {1,2,3,4},
        {5,6,7,8},
        {9,10,11,12}
    };
    
    // 指向"包含4个int的数组"的指针
    int (*p)[4] = arr;
    
    printf("通过数组指针访问:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%3d", *(*(p + i) + j));
        }
        printf("\n");
    }
    
    return 0;
}

八、指针使用安全规范(避坑指南)

8.1 野指针防护三原则

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

int main() {
    // 原则1:初始化为NULL
    int *safe_ptr = NULL;
    
    // 原则2:使用前检查
    if (safe_ptr != NULL) {
        *safe_ptr = 10; // 安全
    }
    
    // 原则3:释放后置NULL
    int *temp = (int *)malloc(sizeof(int));
    if (temp) {
        *temp = 42;
        free(temp);
        temp = NULL; // 防止二次释放
    }
    
    // 避免:未初始化指针
    // int *danger;
    // *danger = 100; // 严重错误!
    
    return 0;
}

8.2 常见陷阱示例

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

void dangerous() {
    // 陷阱1:返回局部变量地址
    // char *str = "Hello"; // 字符串常量在只读区,相对安全
    // 但以下危险:
    // char local[] = "Danger";
    // return local; // 局部数组,函数结束后内存失效
    
    // 陷阱2:内存泄漏
    // int *leak = (int *)malloc(100);
    // 未free -> 内存泄漏
    
    // 陷阱3:越界访问
    int arr[3] = {1,2,3};
    // arr[5] = 10; // 未定义行为
}

九、综合实战:学生成绩管理系统(指针核心应用)

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

typedef struct {
    char name[50];
    int score;
} Student;

// 按分数排序(函数指针实现灵活排序)
void sort_students(Student *students, int n, 
                   int (*compare)(Student*, Student*)) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (compare(&students[j], &students[j+1]) > 0) {
                Student temp = students[j];
                students[j] = students[j+1];
                students[j+1] = temp;
            }
        }
    }
}

int compare_by_score(Student *a, Student *b) {
    return a->score - b->score; // 升序
}

int compare_by_name(Student *a, Student *b) {
    return strcmp(a->name, b->name);
}

int main() {
    Student *class = (Student *)malloc(3 * sizeof(Student));
    
    // 初始化数据(指针操作)
    strcpy((class+0)->name, "赵六"); (class+0)->score = 85;
    strcpy((class+1)->name, "张三"); (class+1)->score = 92;
    strcpy((class+2)->name, "李明"); (class+2)->score = 78;
    
    printf("=== 按分数排序 ===\n");
    sort_students(class, 3, compare_by_score);
    for (int i = 0; i < 3; i++) {
        printf("%s: %d分\n", (class+i)->name, (class+i)->score);
    }
    
    printf("\n=== 按姓名排序 ===\n");
    sort_students(class, 3, compare_by_name);
    for (int i = 0; i < 3; i++) {
        printf("%s: %d分\n", class[i].name, class[i].score);
    }
    
    free(class);
    return 0;
}

十、核心总结与最佳实践

类别关键要点安全建议
基础指针=地址,*解引用,&取地址始终初始化指针
运算ptr+n移动n×sizeof(type)字节避免越界访问
数组数组名≈首元素指针(除sizeof等场景)用arr+i替代&arr[i]提升效率
字符串以\0结尾,字符指针操作字符串检查空指针,避免缓冲区溢出
函数函数指针实现回调、策略模式用typedef简化复杂声明
内存malloc/calloc/realloc + free分配后检查NULL,释放后置NULL
多级二级指针修改指针本身理解*层级关系
安全野指针、内存泄漏、悬空指针遵循"分配-使用-释放-置NULL"流程

重要提醒

  1. C++中优先使用智能指针(unique_ptr/shared_ptr)替代原始指针
  2. 现代C开发中,对复杂场景考虑使用迭代器、容器等更安全的抽象
  3. 指针是强大工具,但"能力越大,责任越大"——严谨的编码习惯是安全基石

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

int sum_array1(int *arr, int size) {
    //int size = sizeof(arr) / sizeof(int);
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += *(arr + i);
    }
    return sum;
}

int sum_array2(int *arr, int size) {
    //int size = sizeof(arr) / sizeof(arr[0]);
    int *end = arr + size;
    int sum = 0;
    while (arr < end) {
        sum += *arr++;
    }
    return sum;
}

int string_length(char *str) {
    int length = 0;
    //遍历直到空字符
    while (*str != '\0') {
        length++;
        //指针自增
        str++;
    }
    return length;
}

void reverse_string(char *str) {
    char *start = str;
    char *end = str;
    while (*end) {
        end++;
    }
    end--;
    while (start < end) {
        char temp = *start;
        *start = *end;
        *end = temp;
        start++;
        end--;
    }
}

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

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 divide(int a, int b) {
    return a / b;
}

//计算器核心:接收函数指针作为参数
int calculator(int (*op)(int, int), int x, int y) {
    return op(x, y);
}

//回调函数类型定义
typedef void (*EventCallback)(int);

void handle_event(int event_id, EventCallback callback) {
    printf("事件触发id:%d\n", event_id);
    if (callback != NULL) {
        //执行回调
        callback(event_id);
    }
}

void on_login(int id) {
    printf("->用户登录事件处理:ID:%d\n", id);
}

void on_logout(int id) {
    printf("->用户登出事件处理:ID:%d\n", id);
}

void malloc_demo() {
    int n = 10;
    //动态分配整形数组
    int *arr = (int *) malloc(n * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "内存分配失败!\n");
        exit(1);
    }
    //初始化数组
    for (int i = 0; i < n; i++) {
        arr[i] = (i + 1) * 10;
    }
    //使用数组
    printf("动态数组元素:");
    for (int i = 0; i < n; i++) {
        printf("%d ", *(arr + i));
    }
    printf("\n");
    //释放内存!!关键
    free(arr);
    arr = NULL;
    printf("方法执行完成\n");
}

//通过二级指针动态分配内存(改变外部指针)
void create_array(int **ptr, int size) {
    *ptr = (int *) malloc(size * sizeof(int));
    for (int i = 0; i < size; i++) {
        (*ptr)[i] = (i + 1) * 10;
    }
}

//指针核心应用
typedef struct {
    char name[50];
    int score;
} Student;

//按分数排序(函数指针实现灵活排序)
void sort_student(Student *student, int n, int (*compare)(Student *, Student *)) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (compare(&student[j], &student[j + 1]) > 0) {
                Student temp = student[j];
                student[j] = student[j + 1];
                student[j + 1] = temp;
            }
        }
    }
}

int compare_by_score(Student *a, Student *b) {
    return a->score - b->score; //升序
}

int compare_by_name(Student *a, Student *b) {
    return strcmp(a->name, b->name);
}

int main() {
    int var = 42;
    int *ptr = &var;

    printf("变量var的值:%d\n", var);
    printf("变量var的地址:%p\n", (void *) &var);
    printf("指针ptr存储的地址:%p\n", (void *) ptr);
    printf("通过指针访问的值:%d\n", *ptr);

    *ptr = 100;
    printf("修改后var的值:%d\n", var);

    printf("int指针大小:%zu\n", sizeof(int *));
    printf("char指针大小:%zu\n", sizeof(char *));
    printf("double指针大小:%zu\n", sizeof(double *));


    int a = 100;
    double b = 3.14;
    char c = 'A';

    int *pa = &a;
    double *pb = &b;
    char *pc = &c;

    printf("int指针+1:%p -> %p (差%llu个字节)\n", pa, pa + 1, ((pa + 1) - pa) * sizeof(int));
    printf("double指针+1:%p -> %p (差%llu个字节)\n", pb, pb + 1, ((pb + 1) - pb) * sizeof(double));
    printf("char指针+1:%p -> %p (差%llu个字节)\n", pc, pc + 1, ((pc + 1) - pc) * sizeof(char));

    int arr[5] = {10, 20, 30, 40, 50};
    int *p_arr1 = &arr[1];
    int *p_arr2 = &arr[4];
    printf("p_arr2 - p_arr1:%lld\n", p_arr2 - p_arr1);
    printf("实际相差字节:%lld\n", (char *) p_arr2 - (char *) p_arr1);

    //
    printf("--------------------------------------\n");
    int arrSum[] = {1, 2, 3, 4, 5};
    int arrSum_size = sizeof(arrSum) / sizeof(arrSum[0]);
    printf("数组和1:%d\n", sum_array1(arrSum, arrSum_size));
    printf("数组和2:%d\n", sum_array2(arrSum, arrSum_size));

    int arr_1[3][5] = {
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 10},
        {11, 12, 13, 14, 15}
    };

    int (*p)[5] = arr_1;
    printf("第二行第二列元素:%d\n", *(*(p + 1) + 1));
    printf("等价写法:%d\n", arr_1[1][1]);

    //遍历整个数组
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 5; j++) {
            printf("%3d", *(*(p + i) + j));
        }
        printf("\n");
    }

    //字符串长度计算
    printf("--------------------------------------\n");
    char str_1[] = "Hello,C!";
    printf("字符串:%s\n", str_1);
    printf("长度不包含'\\0':%d\n", string_length(str_1));
    printf("标准库验证:%llu\n", strlen(str_1));

    //字符串反转(原地操作)
    char str_2[] = "Hello,C!";
    printf("原字符串:%s\n", str_2);
    reverse_string(str_2);
    printf("反转后:%s\n", str_2);

    //按引用传递
    int x_1 = 5, y_1 = 10;
    printf("交换前x_1=%d,y_1=%d\n", x_1, y_1);
    swap(&x_1, &y_1);
    printf("交换后x_1=%d,y_1=%d\n", x_1, y_1);

    //函数指针基础
    //声明函数指针
    int (*func_ptr)(int, int);
    func_ptr = add;
    printf("3+5=%d\n", func_ptr(3, 5));
    func_ptr = subtract;
    printf("3-5=%d\n", func_ptr(3, 5));

    printf("--------------------------------------\n");
    //函数指针实现计算器
    int a_1 = 20, b_1 = 5;
    printf("%d+%d=%d\n", a_1, b_1, calculator(add, a_1, b_1));
    printf("%d-%d=%d\n", a_1, b_1, calculator(subtract, a_1, b_1));
    printf("%d*%d=%d\n", a_1, b_1, calculator(multiply, a_1, b_1));
    printf("%d/%d=%d\n", a_1, b_1, calculator(divide, a_1, b_1));

    printf("--------------------------------------\n");
    //事件处理系统
    handle_event(1001, on_login);
    handle_event(1002, on_logout);
    handle_event(1003, nullptr); //无回调

    //安全的内存分配与释放
    malloc_demo();

    //二级指针:修改指针本身
    int *dd_date = NULL;
    create_array(&dd_date, 10);
    //动态创建的数组
    printf("动态创建的数组:");
    for (int i = 0; i < 10; i++) {
        printf("%d ", *(dd_date + i));
    }
    printf("\n");
    free(dd_date);

    //指针数组:存储多个字符串
    //指针数组:每个元素是指向字符串的指针
    char *days[] = {
        "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
    };
    printf("一周七天:\n");
    for (int i = 0; i < 7; i++) {
        printf("%d,%s\n", i + 1, *(days + i));
    }

    printf("--------------------------------------\n");
    Student *class = (Student *) malloc(3 * sizeof(Student));
    //初始化数据(指针操作)
    strcpy((class + 0)->name, "张三");
    (class + 0)->score = 85;

    strcpy((class + 1)->name, "李明");
    (class + 1)->score = 92;

    strcpy((class + 2)->name, "赵六");
    (class + 2)->score = 78;

    //按分数排序
    printf("按分数排序:\n");
    printf("排序前:\n");
    for (int i = 0; i < 3; i++) {
        printf("%s,%d\n", (class + i)->name, (class + i)->score);
    }
    sort_student(class, 3, compare_by_score);
    printf("排序后:\n");
    for (int i = 0; i < 3; i++) {
        printf("%s,%d\n", (class + i)->name, (class + i)->score);
    }

    printf("按照名字排序:");
    sort_student(class, 3, compare_by_name);
    for (int i = 0; i < 3; i++) {
        printf("%s,%d\n", (class + i)->name, (class + i)->score);
    }
    free(class);

    return 0;
}

掌握指针,就掌握了C语言的精髓。通过本文系统学习+大量实践,你将能自信地应对各类指针场景,编写高效、安全的C程序!