专栏:C语言


本文为 C 语言指针系列第四篇,深度精讲 字符指针数组指针二维数组传参本质函数指针函数指针数组转移表六大核心考点,我会以严谨底层逻辑帮助大家尽可能的吸收、消化并化为己用

前言
在前面的指针学习中,我们已经掌握了指针基础、指针与数组、二级指针等核心内容,完成了指针入门阶段的积累。而从本篇开始,我们将正式进入指针高阶应用学习 —— 字符指针的底层存储、数组指针与指针数组的本质区分、二维数组传参的真实逻辑、函数指针与转移表的实战用法。这些内容既是理解 C 语言内存模型的关键,也是笔试面试与工程开发的高频考点,跟着本篇思路稳步学习,就能顺利打通指针进阶的全链路。

一.字符指针变量

字符指针 char* 是C语言中使用最频繁的指针类型之一,既可以指向单个字符,也可以指向字符串。

1.1基本用法

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
}
  1. pc 存放字符变量 ch 的地址
  2. 通过 *pc 可修改指向的字符

1.2指向常量字符串

int main()
{
    const char* pstr = "hello bit.";
    printf("%s\n", pstr);
    return 0;
}

核心要点

  1. 并非把字符串存入指针,而是将字符串首字符地址存入 pstr
  2. 字符串常量存储在只读数据区,不允许修改,必须用 const 修饰。
  3. 相同常量字符串在内存中只存一份

1.3经典笔试题

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";

    if(str1 == str2)
        printf("str1 and str2 are same\n");
    else
        printf("str1 and str2 are not same\n");

    if(str3 == str4)
        printf("str3 and str4 are same\n");
    else
        printf("str3 and str4 are not same\n");

    return 0;
}

运行结果:
在这里插入图片描述
代码分析:

  1. str1str2 是数组,各自独立开辟内存,地址不同
  2. str3str4 指向只读区同一个常量字符串,地址相同

二.数组指针变量

数组指针是指向数组的指针,本质是指针,专门存放整个数组的地址&数组名)。

2.1定义格式

int (*p)[10];

逻辑分析:

  1. p 先与 * 结合,说明是指针。
  2. 指向一个包含 10 个 int 元素的数组。
  3. 必须加括号,因为 [ ] 优先级高于 *

2.2指针数组与数组指针

指针数组与数组指针的区分在上一章节我们已经讲过了,这里再重复说明一遍

int *p1[10];// 指针数组
int (*p2)[10];//数组指针

指针数组:是数组,里面每个元素是指针
数组指针: int(*p)[10] 是指针,指向整个数组
区分口诀:括号优先是指针,括号靠后是数组

2.3数组指针初始化

int arr[10] = {0};
int (*p)[10] = &arr;

·&arr· 是整个数组的地址,类型为 ·int(*)[10]·。
一维数组极少使用,二维数组传参是核心应用场景。

三.二维数组传参的本质

核心结论:

  1. 二维数组可以看作 “元素为一维数组” 的一维数组
  2. 二维数组名表示第一行一维数组的地址,类型为数组指针。

3.1形参两种等价写法

写法1:数组形式

void test(int a[3][5], int row, int col);

写法2:指针形式(本质

void test(int (*p)[5], int row, int col);

3.2举例

#include <stdio.h>
void test(int (*p)[5], int r, int c)
{
    int i = 0;
    int j = 0;
    for(i = 0; i < r; i++)
    {
        for(j = 0; j < c; j++)
        {
            printf("%d ", *(*(p + i) + j));
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
    test(arr, 3, 5);
    return 0;
}

分析:
1. p + i:指向第 i 行。
2. *(p + i):第 i 行首元素地址。
3. *(*(p + i) + j):第 i 行第 j 列元素。

运行结果:
在这里插入图片描述

四.函数指针变量

函数指针是指向函数的指针,用于存放函数地址,并通过地址调用函数。

4.1函数地址

函数名 == 函数地址
&函数名 == 函数名

例如:

#include <stdio.h>

void test()
{
    printf("hehe\n");
}

int main()
{
    printf("test:   %p\n", test);
    printf("&test:  %p\n", &test);
    
    return 0;
}

4.2函数指针定义

举例:

int Add(int x, int y)
{
    return x + y;
}

//定义函数指针
int (*pf)(int, int) = Add;

其中:

  1. int:函数返回值类型
  2. (*pf)pf 是函数指针
  3. (int, int):函数参数列表

4.3函数指针调用

举个例子:

int main()
{
    int (*pf)(int, int) = Add;
    printf("%d\n", (*pf)(2, 3));//<---标准调用
    printf("%d\n", pf(3, 5));//<---简化调用(*可省略)
    return 0;
}

4.4经典代码(C陷阱与缺陷)

4.4.1经典1

(*(void (*)())0)();

分析:

  1. 0 强制转为 void(*)() 类型函数地址
  2. 调用 0 地址处的无参无返回值函数

4.4.2经典2

void (*signal(int, void(*)(int)))(int);

分析:

  1. 声明 signal 函数
  2. 参数:int + 函数指针 void(*)(int)
  3. 返回值:函数指针 void(*)(int)

typedef简化

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

五.函数指针数组

多个同类型函数地址存入数组,即为函数指针数组。

5.1定义格式

举个例子:

int (*pArr[5])(int, int);

分析:

  1. pArr 是数组
  2. 每个元素为 int(*)(int, int) 类型函数指针

但是有一个要求,就是所有函数的返回值、参数个数、参数类型必须完全一致

六.转移表

我们一般写简单计算器的实现的代码时就会想到使用 switch 语句来实现,但是写完之后代码很长很麻烦,效率低;那我们如何解决这个问题呢?那就是利用转移表了,转移表用函数指针数组替代大 switch / case,代码更简洁、扩展性更强。

接下来请看代码如何利用转移表来实现的

计算器的实现(利用转移表)

#include <stdio.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }

int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    // 转移表
    int(*p[5])(int, int) = { 0, add, sub, mul, div };

    do
    {
        printf("*************************\n");
        printf(" 1:add              2:sub\n");
        printf(" 3:mul              4:div\n");
        printf(" 0:exit                  \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);

        if (input >= 1 && input <= 4)
        {
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = (*p[input])(x, y);
            printf("ret = %d\n", ret);
        }
        else if (input == 0)
        {
            printf("退出计算器\n");
        }
        else
        {
            printf("输入错误\n");
        }
    } while (input);

    return 0;
}

运行结果:
在这里插入图片描述
优点:

  1. 代码简洁优雅
  2. 新增功能只需添加函数与数组元素
  3. 工程与面试高频用法

总结

  1. char* 指向字符串时存储首字符地址,常量字符串必须用 const 修饰,相同字符串只存一份。
  2. 数组指针 int(*p)[10] 是指向数组的指针,主要用于二维数组传参
  3. 二维数组名是第一行地址,类型为数组指针,传参时可用数组或指针形式接收。
  4. 函数名即函数地址,函数指针可存储并调用函数,*可省略。
  5. 函数指针数组可实现转移表,替代冗长 switch / case
  6. typedef 可大幅简化复杂指针类型的书写。

易错点

  1. 混淆指针数组数组指针,导致定义错误。
  2. 二维数组传参误用 int**,造成程序崩溃。
  3. 忽略字符串常量只读属性,试图修改导致程序异常。
  4. 函数指针参数 / 返回值不匹配,编译失败。
  5. 函数指针数组内存放不同类型函数,引发未定义行为。

如果这篇文章对友友们有帮助,期待友友们的支持!!!
点赞 ❤️ 干货收藏,助力指针吃透
收藏 📂 反复回看,攻克难点盲区
关注 🔔 持续更新,C 语言不迷路
留言 💬 交流探讨,学习共同进步

友友们的每一次互动,都是我持续更新C语言硬核知识的动力!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐