指针数组:一群指针的集合

指针数组,说白了就是一个数组,里面装的不是普通整数,而是指针。

我刚开始学的时候,总觉得这玩意儿有点绕。数组就数组,指针就指针,干嘛非要搞个「指针数组」?后来在项目中处理命令行参数时,我才真正体会到它的威力。

指针数组的定义

定义方式很简单:

char *str_arr[5];  // 一个数组,有5个元素,每个元素都是 char* 类型

注意写法。我见过不少新手写成 char (*str_arr)[5],那是数组指针,完全两码事。

来,看个完整的例子:

#include <stdio.h>

int main() {
    char *colors[3] = {
        "红色",
        "绿色", 
        "蓝色"
    };
    
    for (int i = 0; i < 3; i++) {
        printf("颜色 %d: %s\n", i, colors[i]);
    }
    
    return 0;
}

输出结果:

颜色 0: 红色
颜色 1: 绿色
颜色 2: 蓝色

这里 colors 数组的每个元素,都指向一个字符串常量。你想想看,如果不用指针数组,你要怎么存这三个字符串?用二维数组?那得固定每行的长度,浪费空间。

核心区别:

  • char *arr[5] → 指针数组,5个指针
  • char (*arr)[5] → 数组指针,指向一个包含5个char的数组

指针数组的内存布局

我画了一张图,帮你理解指针数组在内存里长什么样:

指针数组内存布局示意图 colors[0] colors[1] colors[2] "红色" "绿色" "蓝色" 指针数组(栈区) 字符串常量(只读区) 每个指针指向一个字符串的首地址 数组本身占 3 × sizeof(char*) 字节

看到没?数组里存的是地址,不是字符串本身。每个指针指向内存中某处真正的字符串数据。

指针数组的应用场景

场景一:命令行参数

这是指针数组最经典的应用。你写的每个C程序,main函数其实都长这样:

int main(int argc, char *argv[])

这里的 argv 就是一个指针数组。每个元素指向一个命令行参数字符串。

举个例子:

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("参数个数: %d\n", argc);
    
    for (int i = 0; i < argc; i++) {
        printf("参数 %d: %s\n", i, argv[i]);
    }
    
    return 0;
}

假设编译成 test,然后运行:

./test hello world 123

输出:

参数个数: 4
参数 0: ./test
参数 1: hello
参数 2: world
参数 3: 123

个人经验:我在做嵌入式调试工具时,经常用命令行参数来控制日志级别。比如 ./monitor -v 3 -o log.txt,用指针数组解析起来特别顺手。

场景二:字符串查找表

我习惯用指针数组做命令映射表。比如:

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

const char *commands[] = {
    "help",
    "version",
    "status",
    "reset"
};

void handle_command(const char *cmd) {
    for (int i = 0; i < 4; i++) {
        if (strcmp(cmd, commands[i]) == 0) {
            printf("执行命令: %s (索引 %d)\n", cmd, i);
            return;
        }
    }
    printf("未知命令: %s\n", cmd);
}

int main() {
    handle_command("status");
    handle_command("reboot");
    return 0;
}

输出:

执行命令: status (索引 2)
未知命令: reboot

场景三:多级菜单

嵌入式设备上经常有菜单系统。指针数组用来存菜单项,再合适不过:

const char *main_menu[] = {
    "1. 系统设置",
    "2. 网络配置",
    "3. 诊断测试",
    "4. 关于设备"
};

每个菜单项就是一个字符串指针。想改菜单内容?改字符串就行,数组大小都不用动。

我曾经踩过的坑:用指针数组存字符串时,一定要注意字符串的生命周期。如果指向的是局部变量,函数返回后指针就悬空了。我早期一个项目因此出了诡异的内存错误,排查了两天才发现。

指针数组 vs 二维数组

很多人会纠结:存多个字符串,到底用指针数组还是二维数组?

特性 指针数组 char *arr[] 二维数组 char arr[][N]
内存占用 灵活,每个字符串长度可变 固定,每行长度相同,浪费空间
修改字符串 可以重新指向新字符串 只能修改内容,不能改变长度
访问速度 多一次间接寻址 直接访问,稍快
适用场景 字符串长度不固定,需要动态管理 字符串长度固定,追求极致性能

我个人习惯:除非你明确知道每个字符串长度都一样,否则优先用指针数组。它更灵活,也更符合C语言「信任程序员」的设计哲学。

指针数组的初始化技巧

初始化时,可以用复合字面量(C99起支持):

// 传统方式
const char *days[] = {"Mon", "Tue", "Wed"};

// 动态分配后赋值
char **names = malloc(3 * sizeof(char *));
names[0] = "Alice";
names[1] = "Bob";
names[2] = "Charlie";

动态分配时,记得每个指针可以指向不同长度的字符串。这是二维数组做不到的。

避坑指南:

  • 指针数组不检查边界,越界访问是未定义行为
  • 如果指针指向动态分配的内存,记得逐个释放
  • const修饰指针数组时,注意语义:const char *arr[] 表示字符串内容不可改,但指针本身可改

小结

指针数组,说白了就是「数组里放指针」。它最牛的地方在于:每个元素可以指向不同类型、不同大小的数据。命令行参数、菜单系统、命令查找表……这些场景里,指针数组都是不二之选。

嗯,记住那个定义写法:类型 *数组名[大小]。别跟数组指针搞混了就行。


公众号:蓝海资料掘金营,微信deep3321