C 语言各种指针详解

指针是 C 语言的核心特性之一,它通过直接操作内存地址赋予程序高效的内存访问能力,但也因其灵活性成为初学者的"拦路虎"。本文将系统解析六种核心指针类型,结合代码示例与原理分析,帮助开发者更好地掌握指针的正确用法。

一、普通指针:内存访问的基础工具

普通指针指向具体的数据类型,核心功能是通过地址间接访问和修改数据,突破函数传值调用的限制。

1. 定义与初始化

语法:数据类型 *指针变量名 = 地址;

指针变量存储的是目标变量的内存起始地址,需要通过取地址符&获取。

2. 核心操作示例

c 复制代码
#include <stdio.h>

// 1. 交换两个变量的值(突破传值限制)
void swap(int *a, int *b) {
    int temp = *a;  // 解引用获取a指向的内存值
    *a = *b;        // 修改a指向的内存数据
    *b = temp;      // 修改b指向的内存数据
}

// 2. 返回多个计算结果
void calc(int a, int b, int *sum, int *diff) {
    *sum = a + b;   // 结果写入sum指向的内存
    *diff = a - b;  // 结果写入diff指向的内存
}

int main() {
    int x = 10, y = 20;
    swap(&x, &y);   // 传入变量地址
    printf("交换后:x=%d, y=%d\n", x, y);  // 输出:交换后:x=20, y=10

    int s, d;
    calc(30, 12, &s, &d);
    printf("和:%d, 差:%d\n", s, d);  // 输出:和:42, 差:18

    return 0;
}

3. 关键特性

  • 解引用运算符*:通过指针访问目标内存数据。
  • 步长特性:指针加减运算的步长等于指向的数据类型的大小(例如int*的步长为4字节)。
  • 必须初始化:未初始化的野指针会访问未知内存,导致程序崩溃。

二、函数指针:指向代码的指针

函数指针存储函数的入口地址,能够实现函数的动态调用与回调机制,是模块化编程的核心工具。

1. 定义与初始化

语法:返回值类型 (*指针名)(参数类型列表) = 函数名;

括号不可省略,否则会被解析为"返回指针的函数"。

2. 核心操作示例

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 ascending(int a, int b) { return a - b; }  // 升序
int descending(int a, int b) { return b - a; } // 降序

// 通用排序函数(通过函数指针实现动态排序规则)
void sort(int arr[], int len, int (*cmp)(int, int)) {
    for (int i = 0; i < len - 1; i++) {
        for (int j = 0; j < len - i - 1; j++) {
            if (cmp(arr[j], arr[j + 1]) > 0) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    // 1. 基础函数调用
    int (*funcPtr)(int, int) = add;
    printf("2+3=%d\n", funcPtr(2, 3));  // 输出:2+3=5

    // 2. 回调函数应用
    int arr[] = {5, 2, 8, 1};
    int len = sizeof(arr) / sizeof(arr[0]);

    sort(arr, len, ascending);
    printf("升序:");
    for (int i = 0; i < len; i++) printf("%d ", arr[i]);  // 输出:升序:1 2 5 8

    sort(arr, len, descending);
    printf("\n降序:");
    for (int i = 0; i < len; i++) printf("%d ", arr[i]);  // 输出:降序:8 5 2 1

    return 0;
}

3. 典型应用

  • 回调函数:如排序算法中的比较器、事件响应机制。
  • 函数表:实现状态机、插件系统等动态功能。
  • 跨模块调用:减少代码耦合度。

三、二级指针:指向指针的指针

二级指针存储指针变量的地址,常用于修改一级指针的指向或传递动态多维数组。

1. 定义与初始化

语法:数据类型 **二级指针名 = &一级指针名;

可以理解为"指针的指针",需要两次解引用才能访问目标数据。

2. 核心操作示例

c 复制代码
#include <stdio.h>
#include <stdlib.h>

// 1. 修改一级指针的指向
void resetPointer(int **p) {
    int *newPtr = (int *)malloc(sizeof(int));
    *newPtr = 100;
    *p = newPtr;  // 修改一级指针的指向
}

// 2. 动态创建二维数组
int **create2DArray(int rows, int cols) {
    int **arr = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        arr[i] = (int *)calloc(cols, sizeof(int));  // 初始化为0
    }
    return arr;
}

int main() {
    // 示例1:修改指针指向
    int *ptr = NULL;
    resetPointer(&ptr);
    printf("动态值:%d\n", *ptr);  // 输出:动态值:100
    free(ptr);
    ptr = NULL;

    // 示例2:动态二维数组
    int rows = 2, cols = 3;
    int **matrix = create2DArray(rows, cols);
    matrix[0][1] = 5;
    matrix[1][2] = 8;

    printf("二维数组:\n");
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }

    // 释放内存
    for (int i = 0; i < rows; i++) free(matrix[i]);
    free(matrix);
    matrix = NULL;

    return 0;
}

3. 注意事项

  • 解引用层级:**p访问目标数据,*p访问一级指针地址。
  • 内存管理:动态多维数组需逐层释放,避免内存泄漏。
  • 常见场景:函数传递指针地址、实现链表等复杂数据结构。

四、数组指针:指向数组的指针

数组指针指向整个数组而非单个元素,常用于二维数组的高效访问与传递。

1. 定义与初始化

语法:数据类型 (*指针名)[数组长度] = &数组名;

与"指针数组"(数据类型 *指针名[数组长度])需严格区分。

2. 核心操作示例

c 复制代码
#include <stdio.h>

// 数组指针作为函数参数(传递二维数组)
void printMatrix(int (*matrix)[3], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++) {
            // 两种访问方式等效
            printf("%d ", matrix[i][j]);
            // printf("%d ", *( *(matrix+i)+j));
        }
        printf("\n");
    }
}

int main() {
    // 初始化数组指针
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*pArr)[3] = arr;  // 数组名自动转换为数组指针

    // 指针运算特性(步长为数组总大小:3*4=12字节)
    printf("首行地址:%p\n", (void *)pArr);
    printf("第二行地址:%p\n", (void *)(pArr + 1));  // 地址差值为12

    // 函数传递与访问
    printf("二维数组内容:\n");
    printMatrix(pArr, 2);

    return 0;
}

3. 关键区别:数组指针 vs 指针数组

类型 定义示例 指向对象 步长特性
数组指针 int (*p)[3] 整个 int [3] 数组 数组总大小(12B)
指针数组 int *p[3] 3 个 int * 指针 单个指针大小(通常是8B)

五、字符串指针:指向字符序列的指针

字符串指针指向字符串常量或字符数组的首地址,是 C 语言处理字符串的核心方式。

1. 定义与初始化

语法 1(字符串常量):const char *str = "hello";(不可修改内容)

语法 2(字符数组):char str[] = "hello";(可修改内容)

2. 核心操作示例

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    // 1. 字符串指针(指向常量,不可修改)
    const char *str1 = "C language";
    printf("字符串1:%s\n", str1);
    printf("首字符:%c\n", *str1);  // 输出:首字符:C

    // *str1 = 'c';  // 错误:字符串常量不可修改

    // 2. 字符数组(可修改)
    char str2[] = "pointer";
    str2[0] = 'P';
    printf("修改后字符串2:%s\n", str2);  // 输出:修改后字符串2:Pointer

    // 3. 字符串长度计算
    printf("str1长度:%zu\n", strlen(str1));  // 输出:str1长度:10(不含'\0')
    printf("str2占用内存:%zu\n", sizeof(str2));  // 输出:str2占用内存:8(含'\0')

    // 4. 指针遍历字符串
    const char *p = str1;
    printf("遍历字符串1:");
    while (*p != '\0') {  // 遇'\0'结束
        printf("%c", *p);
        p++;
    }
    printf("\n");

    return 0;
}

3. 关键特性

  • 结束标志:字符串以'\0'结尾,缺失会导致遍历越界。
  • 存储差异:字符串常量存放在只读区,字符数组存放在栈区。
  • 效率优势:字符串指针操作比数组下标访问更高效。

六、void * 指针:无类型通用指针

void * 指针又称空类型指针,可以指向任意类型的数据,是实现通用接口的关键工具。

1. 核心规则

  1. 可接收任意类型指针的赋值,无需强制转换。
  2. 赋值给其他类型指针时,必须强制转换。
  3. ANSI C 中不可直接解引用或进行算术运算(GNU 编译器视为 char*)。

2. 核心操作示例

c 复制代码
#include <stdio.h>
#include <string.h>

// 通用内存拷贝函数(模拟memcpy)
void my_memcpy(void *dest, const void *src, size_t size) {
    // 转换为char*进行字节级拷贝
    char *d = (char *)dest;
    const char *s = (const char *)src;
    for (size_t i = 0; i < size; i++) {
        d[i] = s[i];
    }
}

int main() {
    // 1. 多类型指针赋值
    int num = 100;
    char ch = 'A';
    void *p = &num;  // 指向int
    printf("int值:%d\n", *(int *)p);  // 需强制转换,输出:int值:100

    p = &ch;  // 指向char,无需转换
    printf("char值:%c\n", *(char *)p);  // 输出:char值:A

    // 2. 通用函数应用
    int arr1[] = {1, 2, 3};
    int arr2[3];
    my_memcpy(arr2, arr1, sizeof(arr1));  // 拷贝int数组
    printf("拷贝结果:%d %d %d\n", arr2[0], arr2[1], arr2[2]);  // 输出:1 2 3

    // 3. 算术运算限制(ANSI C报错,GNU视为char*)
    // p++;  // ANSI C错误:void*无法确定步长

    return 0;
}

3. 典型应用

  • 内存操作函数:如 mallocmemcpymemset
  • 通用数据结构:如链表、栈可存储任意类型数据。
  • 跨类型接口:减少函数重载,实现多态效果。

七、指针避坑指南

  1. 野指针防治
  • 定义时初始化为NULLint *p = NULL;
  • 动态内存释放后立即置空:free(p); p = NULL;
  • 使用前检查有效性:if (p != NULL) { ... }
  1. 越界访问
  • 数组遍历严格控制边界,避免p >= arr+len
  • 字符串操作确保'\0'存在
  1. 类型匹配
  • 避免不同类型指针直接赋值(void * 除外)
  • 函数指针参数类型必须与函数原型一致
  1. 内存泄漏
  • 动态分配的内存(malloc/calloc)必须free
  • 多维动态数组需逐层释放

结语

指针是 C 语言的核心竞争力,从普通指针的内存访问到函数指针的回调机制,从二级指针的多维数组到 void * 的通用接口,每种指针都承载着特定的功能价值。掌握指针的关键在于理解"地址"本质与"类型"约束,结合实践规避野指针、越界等常见错误,才能真正发挥 C 语言的高效与灵活特性。

相关推荐
Rewloc4 小时前
Trae CN配置Maven环境
java·maven
彭于晏Yan4 小时前
MyBatis-Plus使用动态表名分表查询
java·开发语言·mybatis
秋月的私语6 小时前
如何快速将当前的c#工程发布成单文件
android·java·c#
天***88966 小时前
使用python写一个应用程序要求实现微软常用vc++功能排查与安装功能
java
代码充电宝7 小时前
LeetCode 算法题【简单】283. 移动零
java·算法·leetcode·职场和发展
迎風吹頭髮8 小时前
UNIX下C语言编程与实践9-UNIX 动态库创建实战:gcc 参数 -fpic、-shared 的作用与动态库生成步骤
c语言·数据库·unix
MediaTea9 小时前
Python IDE:Spyder
开发语言·ide·python
不枯石9 小时前
Matlab通过GUI实现点云的均值滤波(附最简版)
开发语言·图像处理·算法·计算机视觉·matlab·均值算法
不枯石10 小时前
Matlab通过GUI实现点云的双边(Bilateral)滤波(附最简版)
开发语言·图像处理·算法·计算机视觉·matlab