8. 指针数组:指针数组的定义与使用、指针数组与二维数组的对比、命令行参数argv

指针数组,这个名字听起来有点绕。说白了,就是一个数组,里面每个元素都是指针。嗯,就是这么简单。

我刚开始学C语言那会儿,总觉得指针数组和二维数组是一回事。后来在项目里踩了坑,才真正搞明白它们的区别。今天咱们就把这个知识点彻底讲透。

8.1 指针数组的定义与使用

先看定义。指针数组的语法是这样的:

类型名 *数组名[数组大小];

注意,是 *数组名[大小],不是 (*数组名)[大小]。前者是指针数组,后者是数组指针。这两个东西完全不一样,我见过不少同事在这上面翻车。

举个例子:

char *str_arr[3];  // 一个包含3个char*指针的数组

每个元素都能指向一个字符串。你可以这样初始化:

char *str_arr[3] = {
    "Hello",
    "World",
    "Pointer"
};

这时候,str_arr[0] 指向 "Hello" 的首地址,str_arr[1] 指向 "World" 的首地址,以此类推。

我在项目中经常用指针数组来处理菜单选项。比如一个嵌入式设备的LCD菜单:

char *menu_items[] = {
    "1. 系统设置",
    "2. 参数校准",
    "3. 数据查看",
    "4. 固件升级",
    "5. 退出"
};

这样写的好处是什么?每个字符串的长度可以不一样,内存利用率高。而且增删菜单项特别方便,改一下数组就行。

小技巧:指针数组的每个元素指向的是字符串常量,不能修改内容。如果你需要修改字符串,应该用二维字符数组,或者动态分配内存。

8.2 指针数组与二维数组的对比

这两个东西,初学者特别容易搞混。咱们来做个对比。

先看二维数组:

char arr2d[3][10] = {
    "Hello",
    "World",
    "Pointer"
};

再看指针数组:

char *arrptr[3] = {
    "Hello",
    "World",
    "Pointer"
};

表面上看,用法差不多。但内存布局完全不同。

二维数组在内存中是连续的一块空间。3行10列,总共30个字节,整整齐齐排在一起。每个字符串最多只能有9个字符(留一个给'\0')。

指针数组呢?数组本身是连续的,但每个指针指向的字符串可以分散在内存的不同位置。字符串长度也可以不一样。

我画个图帮你理解:

二维数组内存布局 H e l l o \0 W o r l d \0 P o i n t e r \0 指针数组内存布局 str_arr[0] str_arr[1] str_arr[2] "Hello" "World" "Pointer"

看到区别了吧?二维数组是"方方正正"的一块内存,指针数组是"散装"的。

咱们用表格对比一下:

对比项 二维数组 指针数组
内存布局 连续,固定大小 数组连续,数据可分散
字符串长度 每行固定长度 每个字符串可不同长度
内存利用率 可能有浪费(短字符串占满整行) 利用率高
修改字符串内容 可以直接修改 指向常量时不能修改
访问速度 稍快(连续内存,缓存友好) 稍慢(需要间接寻址)
注意:在嵌入式系统中,如果RAM资源紧张,指针数组通常比二维数组更省内存。但要注意,指针数组需要额外的空间来存储指针本身(每个指针4字节或8字节)。

8.3 命令行参数 argv

说到指针数组,最经典的应用就是 main 函数的参数 argv 了。

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

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

举个例子,假设你的程序叫 test,在命令行这样运行:

./test hello world 123

那么:

  • argc = 4
  • argv[0] = "./test"
  • argv[1] = "hello"
  • argv[2] = "world"
  • argv[3] = "123"
  • argv[4] = NULL(最后一个元素是空指针)

我曾经在一个嵌入式项目中,用 argv 来传递配置参数。比如:

./sensor_app --mode=fast --interval=100 --output=uart

解析起来也很简单:

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

int main(int argc, char *argv[])
{
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--mode=fast") == 0) {
            printf("设置快速模式\n");
        } else if (strcmp(argv[i], "--interval=100") == 0) {
            printf("采样间隔设为100ms\n");
        } else if (strcmp(argv[i], "--output=uart") == 0) {
            printf("输出到串口\n");
        } else {
            printf("未知参数: %s\n", argv[i]);
        }
    }
    return 0;
}

你想想看,如果没有指针数组,处理这种不定长的命令行参数得多麻烦?

核心要点:
  • 指针数组是"数组元素是指针"的数组
  • 与二维数组的区别在于内存布局和灵活性
  • argv 是指针数组的典型应用
  • 指针数组适合处理长度不等的字符串集合

嗯,指针数组这块内容就讲到这里。记住一点:指针数组和二维数组各有适用场景,选哪个要看你的具体需求。如果字符串长度固定,用二维数组;如果长度不固定,或者需要频繁增删,指针数组更合适。

我曾经在一个数据采集项目里,因为用了二维数组存储传感器名称,结果有个名称超长导致程序崩溃。后来改成指针数组,问题就解决了。所以,选对工具很重要。


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