Skip to content

指针

指针是C语言最重要也是最具特色的概念之一。指针允许直接操作内存地址,是C语言强大功能的核心。本章将详细介绍指针的概念和使用方法。

指针基础

什么是指针

c
/*
 * 指针的基本概念
 */

#include <stdio.h>

int main(void)
{
    int num = 10;
    
    /* 
     * 指针是一个变量,存储的是另一个变量的地址
     * & 取地址运算符:获取变量的内存地址
     * * 解引用运算符:获取指针指向的值
     */
    
    /* 声明指针变量 */
    int *ptr;       /* 声明一个指向int类型的指针 */
    
    /* 将num的地址赋给ptr */
    ptr = &num;     /* &num 获取num的地址 */
    
    printf("num的值: %d\n", num);
    printf("num的地址: %p\n", (void*)&num);
    printf("ptr的值(存储的地址): %p\n", (void*)ptr);
    printf("ptr指向的值: %d\n", *ptr);  /* *ptr 解引用 */
    
    /* 通过指针修改值 */
    *ptr = 20;
    printf("\n通过指针修改后:\n");
    printf("num的值: %d\n", num);
    
    /* 指针的大小 */
    printf("\n指针的大小: %zu 字节\n", sizeof(ptr));
    printf("int的大小: %zu 字节\n", sizeof(int));
    
    return 0;
}

指针的声明和初始化

c
/*
 * 指针的声明和初始化
 */

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

int main(void)
{
    /* 声明指针 */
    int *p1;        /* 方式1:*靠近变量名 */
    int* p2;        /* 方式2:*靠近类型 */
    int * p3;       /* 方式3:*在中间(不推荐) */
    
    /* 注意:声明多个指针时,每个都需要* */
    int *pa, *pb;   /* pa和pb都是指针 */
    int* pc, pd;    /* pc是指针,pd是int变量! */
    
    /* 初始化指针 */
    int num = 10;
    int *ptr1 = &num;       /* 初始化为变量的地址 */
    int *ptr2 = NULL;       /* 初始化为NULL(推荐) */
    int *ptr3 = 0;          /* 等价于NULL */
    
    /* 空指针 */
    int *null_ptr = NULL;
    printf("空指针的值: %p\n", (void*)null_ptr);
    
    /* 使用前检查空指针 */
    if (null_ptr != NULL) {
        printf("值: %d\n", *null_ptr);
    } else {
        printf("指针为空\n");
    }
    
    /* 野指针(危险) */
    int *wild_ptr;          /* 未初始化,包含随机值 */
    /* printf("%d", *wild_ptr); */  /* 未定义行为! */
    
    /* 悬空指针 */
    int *dangling_ptr;
    {
        int local = 20;
        dangling_ptr = &local;
    }  /* local被销毁,ptr成为悬空指针 */
    /* printf("%d", *dangling_ptr); */  /* 未定义行为! */
    
    /* 正确做法:指针不用时设为NULL */
    dangling_ptr = NULL;
    
    return 0;
}

指针的类型

c
/*
 * 不同类型的指针
 */

#include <stdio.h>

int main(void)
{
    /* 整型指针 */
    int i = 10;
    int *ip = &i;
    printf("int指针: *ip = %d\n", *ip);
    
    /* 字符指针 */
    char c = 'A';
    char *cp = &c;
    printf("char指针: *cp = %c\n", *cp);
    
    /* 浮点指针 */
    float f = 3.14f;
    float *fp = &f;
    printf("float指针: *fp = %f\n", *fp);
    
    /* 双精度指针 */
    double d = 3.14159;
    double *dp = &d;
    printf("double指针: *dp = %lf\n", *dp);
    
    /* void指针(通用指针) */
    void *vp;
    vp = &i;
    printf("void指针: *(int*)vp = %d\n", *(int*)vp);
    
    vp = &f;
    printf("void指针: *(float*)vp = %f\n", *(float*)vp);
    
    /* 指针的大小(同一平台上所有指针大小相同) */
    printf("\n指针大小:\n");
    printf("int*: %zu\n", sizeof(int*));
    printf("char*: %zu\n", sizeof(char*));
    printf("void*: %zu\n", sizeof(void*));
    
    return 0;
}

指针运算

指针的算术运算

c
/*
 * 指针的算术运算
 */

#include <stdio.h>

int main(void)
{
    int arr[] = {10, 20, 30, 40, 50};
    int *ptr = arr;  /* 指向数组首元素 */
    
    printf("=== 指针算术运算 ===\n");
    
    /* 指针加法 */
    printf("ptr = %p, *ptr = %d\n", (void*)ptr, *ptr);
    printf("ptr + 1 = %p, *(ptr + 1) = %d\n", 
           (void*)(ptr + 1), *(ptr + 1));
    printf("ptr + 2 = %p, *(ptr + 2) = %d\n", 
           (void*)(ptr + 2), *(ptr + 2));
    
    /* 指针自增 */
    printf("\n指针自增:\n");
    ptr++;
    printf("ptr++后: *ptr = %d\n", *ptr);
    ptr++;
    printf("ptr++后: *ptr = %d\n", *ptr);
    
    /* 指针减法 */
    printf("\n指针减法:\n");
    ptr--;
    printf("ptr--后: *ptr = %d\n", *ptr);
    
    /* 指针差值 */
    int *p1 = arr;
    int *p2 = arr + 3;
    printf("\np2 - p1 = %ld (元素个数)\n", (long)(p2 - p1));
    
    /* 使用指针遍历数组 */
    printf("\n使用指针遍历数组:\n");
    ptr = arr;
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d (地址: %p)\n", 
               i, *(ptr + i), (void*)(ptr + i));
    }
    
    /* 指针和下标 */
    printf("\n指针使用下标:\n");
    ptr = arr;
    for (int i = 0; i < 5; i++) {
        printf("ptr[%d] = %d\n", i, ptr[i]);
    }
    
    return 0;
}

指针与数组

c
/*
 * 指针与数组的关系
 */

#include <stdio.h>

int main(void)
{
    int arr[5] = {1, 2, 3, 4, 5};
    
    /* 数组名是数组首元素的地址 */
    int *ptr1 = arr;        /* 等价于 &arr[0] */
    int *ptr2 = &arr[0];
    
    printf("arr = %p\n", (void*)arr);
    printf("&arr[0] = %p\n", (void*)&arr[0]);
    printf("ptr1 = %p\n", (void*)ptr1);
    printf("ptr2 = %p\n", (void*)ptr2);
    
    /* 数组名和指针的区别 */
    printf("\nsizeof(arr) = %zu\n", sizeof(arr));      /* 整个数组的大小 */
    printf("sizeof(ptr1) = %zu\n", sizeof(ptr1));     /* 指针的大小 */
    
    /* arr是常量,不能修改 */
    /* arr++; */  /* 错误! */
    ptr1++;       /* 正确 */
    
    /* 使用指针访问数组 */
    printf("\n使用指针访问数组:\n");
    int *p = arr;
    for (int i = 0; i < 5; i++) {
        printf("%d ", *(p + i));  /* 或 p[i] */
    }
    printf("\n");
    
    /* 指向数组的指针 vs 指针数组 */
    /* 指向数组的指针 */
    int (*ptr_to_arr)[5] = &arr;  /* 指向整个数组的指针 */
    printf("\n(*ptr_to_arr)[0] = %d\n", (*ptr_to_arr)[0]);
    
    /* 指针数组 */
    int a = 1, b = 2, c = 3;
    int *ptr_array[3] = {&a, &b, &c};  /* 存储指针的数组 */
    printf("ptr_array[0]指向的值: %d\n", *ptr_array[0]);
    
    return 0;
}

指针与函数

指针作为函数参数

c
/*
 * 指针作为函数参数
 */

#include <stdio.h>

/* 交换两个数(使用指针) */
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

/* 通过指针返回多个值 */
void minmax(int arr[], int len, int *min, int *max)
{
    *min = arr[0];
    *max = arr[0];
    
    for (int i = 1; i < len; i++) {
        if (arr[i] < *min) *min = arr[i];
        if (arr[i] > *max) *max = arr[i];
    }
}

/* 修改数组元素 */
void double_elements(int *arr, int len)
{
    for (int i = 0; i < len; i++) {
        arr[i] *= 2;  /* 或 *(arr + i) *= 2; */
    }
}

int main(void)
{
    /* 交换两个数 */
    int x = 5, y = 10;
    printf("交换前: x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("交换后: x = %d, y = %d\n", x, y);
    
    /* 返回多个值 */
    int arr[] = {3, 7, 2, 9, 5};
    int min, max;
    minmax(arr, 5, &min, &max);
    printf("\n最小值: %d, 最大值: %d\n", min, max);
    
    /* 修改数组 */
    int nums[] = {1, 2, 3, 4, 5};
    printf("\n原数组: ");
    for (int i = 0; i < 5; i++) printf("%d ", nums[i]);
    
    double_elements(nums, 5);
    printf("\n翻倍后: ");
    for (int i = 0; i < 5; i++) printf("%d ", nums[i]);
    printf("\n");
    
    return 0;
}

指针作为返回值

c
/*
 * 指针作为返回值
 */

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

/* 返回较大数的地址 */
int *max(int *a, int *b)
{
    return (*a > *b) ? a : b;
}

/* 在数组中查找元素 */
int *find(int arr[], int len, int target)
{
    for (int i = 0; i < len; i++) {
        if (arr[i] == target) {
            return &arr[i];  /* 返回元素的地址 */
        }
    }
    return NULL;  /* 未找到 */
}

/* 动态分配内存并返回指针 */
int *create_array(int len)
{
    int *arr = (int*)malloc(len * sizeof(int));
    if (arr != NULL) {
        for (int i = 0; i < len; i++) {
            arr[i] = i + 1;
        }
    }
    return arr;
}

/* 错误示例:返回局部变量的地址 */
int *wrong_function(void)
{
    int local = 10;
    /* return &local; */  /* 错误!local在函数返回后销毁 */
    
    /* 正确做法:使用static或动态分配 */
    static int static_local = 10;
    return &static_local;
}

int main(void)
{
    /* 返回较大数的地址 */
    int a = 10, b = 20;
    int *p = max(&a, &b);
    printf("较大的数: %d\n", *p);
    
    /* 查找元素 */
    int arr[] = {3, 7, 2, 9, 5};
    int *found = find(arr, 5, 9);
    if (found != NULL) {
        printf("找到元素: %d (地址: %p)\n", *found, (void*)found);
    }
    
    /* 动态创建数组 */
    int *dynamic_arr = create_array(5);
    if (dynamic_arr != NULL) {
        printf("动态数组: ");
        for (int i = 0; i < 5; i++) {
            printf("%d ", dynamic_arr[i]);
        }
        printf("\n");
        free(dynamic_arr);  /* 释放内存 */
    }
    
    return 0;
}

指针与const

c
/*
 * 指针与const
 */

#include <stdio.h>

int main(void)
{
    int a = 10;
    int b = 20;
    
    /* 1. 指向常量的指针(指针指向的内容不可修改) */
    const int *p1 = &a;
    /* *p1 = 100; */  /* 错误!不能修改指向的值 */
    p1 = &b;           /* 正确!可以修改指针本身 */
    printf("const int *p1: *p1 = %d\n", *p1);
    
    /* 2. 常量指针(指针本身不可修改) */
    int * const p2 = &a;
    *p2 = 100;         /* 正确!可以修改指向的值 */
    /* p2 = &b; */     /* 错误!不能修改指针本身 */
    printf("int * const p2: *p2 = %d\n", *p2);
    
    /* 3. 指向常量的常量指针 */
    const int * const p3 = &a;
    /* *p3 = 200; */   /* 错误!不能修改指向的值 */
    /* p3 = &b; */     /* 错误!不能修改指针本身 */
    printf("const int * const p3: *p3 = %d\n", *p3);
    
    /* 记忆技巧:从右往左读 */
    /* const int *p1;    -> p1 is a pointer to const int */
    /* int * const p2;   -> p2 is a const pointer to int */
    /* const int * const p3; -> p3 is a const pointer to const int */
    
    return 0;
}

指针数组

c
/*
 * 指针数组
 */

#include <stdio.h>

int main(void)
{
    /* 指针数组:存储指针的数组 */
    int a = 10, b = 20, c = 30;
    int *ptrs[3] = {&a, &b, &c};
    
    printf("指针数组:\n");
    for (int i = 0; i < 3; i++) {
        printf("ptrs[%d] = %p, *ptrs[%d] = %d\n", 
               i, (void*)ptrs[i], i, *ptrs[i]);
    }
    
    /* 字符串数组(常用) */
    char *names[] = {"张三", "李四", "王五"};
    int count = sizeof(names) / sizeof(names[0]);
    
    printf("\n字符串数组:\n");
    for (int i = 0; i < count; i++) {
        printf("names[%d] = %s\n", i, names[i]);
    }
    
    /* 数组指针 vs 指针数组 */
    /* int *p[3];    指针数组:3个int指针 */
    /* int (*p)[3];  数组指针:指向包含3个int的数组 */
    
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*p_arr)[3] = arr;  /* 指向二维数组的行 */
    
    printf("\n使用数组指针遍历二维数组:\n");
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", p_arr[i][j]);
        }
        printf("\n");
    }
    
    return 0;
}

小结

本章学习了:

  • 指针基础:指针的概念、声明、初始化
  • 指针运算:加减运算、指针与数组
  • 指针与函数:指针参数、指针返回值
  • const与指针:指向常量的指针、常量指针
  • 指针数组:存储指针的数组

下一章,我们将学习字符串,了解C语言字符串的处理方法。