深入指针4 - 学习笔记整理

文章目录

深入指针4 - 学习笔记整理

一、数组指针和指针数组回顾

验证 *& 的抵消关系

c 复制代码
#include<stdio.h>
int main()
{
    int a = 10;
    int* p = &a;
    printf("%d\n", *p);  // *p = *(&a) = a
    
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int(*parr)[10] = &arr;  // 数组指针,存放整个数组的地址
    
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", (*parr)[i]);  // (*parr)[i] = *(&arr)[i] = arr[i] = *(arr+i)
    }
    return 0;
}

关键点

  • [i]:通过下标访问数组元素
  • arr[i][j]:访问二维数组第i行第j列元素
  • *&互为逆运算:*(&a) = a

二、二维数组传参的本质

二维数组内存布局

二维数组在内存中是连续存储的,按行优先排列。

二维数组名含义

二维数组的数组名是首元素地址 ,而首元素是第一行的一维数组,所以二维数组名就是第一行一维数组的地址。

传参打印实现

c 复制代码
// 方法1:使用数组接收
void print1(int arr[3][5], int r, int c)
{
    for (int i = 0; i < r; i++)
    {
        for (int j = 0; j < c; j++)
        {
            printf("%d ", *(*(arr + i) + j));  // 三种写法均可
            // printf("%d ", arr[i][j]);
            // printf("%d ", (*(arr + i))[j]);
        }
        printf("\n");
    }
}

// 方法2:使用数组指针接收(本质)
void print2(int(*p)[5], int r, int c)  // p指向含有5个int元素的数组
{
    for (int i = 0; i < r; i++)
    {
        for (int j = 0; j < c; j++)
        {
            printf("%d ", (*(p + i))[j]);  // 或 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}};
    print1(arr, 3, 5);
    print2(arr, 3, 5);
    return 0;
}

核心理解 :二维数组传参,传递的是第一行一维数组的地址,形参要用数组指针接收 int(*p)[5]


三、函数指针变量

基本概念

  1. 函数也有地址&函数名 = 函数名,都是函数的地址
  2. 通过函数地址调用函数:即使不知道函数名,只要知道地址就能调用
  3. 查看变量类型:去掉变量名,剩余部分就是类型
c 复制代码
int Add(int x, int y)
{
    return x + y;
}

int main()
{
    int a = 10;
    int* pa = &a;              // 整型指针
    
    int arr[5] = {0};
    int(*parr)[5] = &arr;      // 数组指针
    
    int (*pf)(int, int) = Add; // 函数指针,存放Add函数的地址
    // int (*pf)(int, int) = &Add;  // 等价
    
    // 三种调用方式,结果相同
    int ret1 = (*pf)(4, 5);     // 解引用函数指针
    int ret2 = pf(4, 5);        // 直接使用函数指针(推荐)
    int ret3 = Add(4, 5);       // 直接使用函数名
    
    printf("ret1 = %d\n", ret1);
    printf("ret2 = %d\n", ret2);
    printf("ret3 = %d\n", ret3);
    
    return 0;
}

四、两段有趣的代码(提高阅读能力)

1. 有趣的函数调用

c 复制代码
(*(void (*)())0)();

解析

  • (void (*)()):函数指针类型,指向返回void、无参数的函数
  • (void (*)())0:将0强制转换为函数指针类型,即0成为函数地址
  • *(void (*)())0:解引用,找到0地址处的函数
  • (*(void (*)())0)():调用0地址处的函数

含义:调用地址为0处的函数(实际不可调用,仅供理解)

2. 有趣的函数声明

c 复制代码
void (*signal(int, void(*)(int)))(int);

解析方法:从内向外逐步剖析

  1. signal是一个函数名
  2. 它有两个参数:intvoid(*)(int)(函数指针)
  3. signal的返回值类型是void(*)(int)(函数指针)

简化:使用typedef

c 复制代码
typedef void(*pf_t)(int);
pf_t signal(int, pf_t);

函数声明、定义、调用的区别

概念 说明 示例
函数声明 告诉编译器函数的存在,包含返回值类型、函数名、参数类型 int Add(int, int);
函数定义 完整实现函数功能,包含函数体 int Add(int x, int y) { return x + y; }
函数调用 使用函数完成特定功能 int sum = Add(3, 5);

五、typedef关键字

作用:类型重命名,简化复杂类型

基本用法

c 复制代码
// 基本类型重命名
typedef unsigned int uint;        // unsigned int → uint
typedef int* ptr_t;                // int* → ptr_t

// 数组指针重命名
typedef int(*parr_t)[6];           // int(*)[6] → parr_t
// 注意:新类型名必须在*右边

// 函数指针重命名
typedef int(*pf_t)(int, int);      // int(*)(int,int) → pf_t

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

int main()
{
    uint n = 10;                    // 等价于 unsigned int n = 10
    ptr_t p = &n;                    // 等价于 int* p = &n
    
    int arr[6] = {0};
    parr_t parr = &arr;              // 等价于 int(*parr)[6] = &arr
    
    pf_t padd = Add;                 // 等价于 int(*padd)(int,int) = Add
    
    return 0;
}

复杂声明简化

c 复制代码
// 原始复杂声明
void (*signal(int, void(*)(int)))(int);

// 使用typedef简化
typedef void(*pf_t)(int);
pf_t signal(int, pf_t);

六、函数指针数组

概念:存放相同类型函数指针的数组

运算符优先级:* < []

c 复制代码
int *parr[5];      // 指针数组:parr先与[5]结合,有5个int*元素
int (*parr)[5];    // 数组指针:parr先与*结合,指向有5个int元素的数组

函数指针数组实现

c 复制代码
int Add(int x, int y) { return x + y; }
int Sub(int x, int y) { return x - y; }
int Mul(int x, int y) { return x * y; }
int Div(int x, int y) { return x / y; }

int main()
{
    // 函数指针数组:存放4个函数指针
    int (*pf[4])(int, int) = {Add, Sub, Mul, Div};
    //          ↑ pf是数组,有4个元素,每个元素类型是int(*)(int,int)
    
    // 遍历调用
    for (int i = 0; i < 4; i++)
    {
        printf("%d ", pf[i](4, 6));  // 直接通过数组下标调用
        // printf("%d ", (*pf)[i](4, 6));  // 错误!*不能解引用数组
    }
    
    return 0;
}

注意*只能解引用指针,不能解引用数组


七、计算器实现

1. 普通计算器(switch语句)

c 复制代码
int Add(int x, int y) { return x + y; }
int Sub(int x, int y) { return x - y; }
int Mul(int x, int y) { return x * y; }
int Div(int x, int y) { return x / y; }

void menu()
{
    printf("*********************************\n");
    printf("**********1.Add  2.Sub***********\n");
    printf("**********3.Mul  4.Div***********\n");
    printf("**********0.exit      ***********\n");
    printf("*********************************\n");
}

int main()
{
    int x, y, input, ret;
    
    do {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        
        switch (input)
        {
        case 1:
            printf("请输入两个整数:");
            scanf("%d %d", &x, &y);
            ret = Add(x, y);
            printf("结果是:%d", ret);
            break;
        case 2:
            printf("请输入两个整数:");
            scanf("%d %d", &x, &y);
            ret = Sub(x, y);
            printf("结果是:%d", ret);
            break;
        case 3:
            printf("请输入两个整数:");
            scanf("%d %d", &x, &y);
            ret = Mul(x, y);
            printf("结果是:%d", ret);
            break;
        case 4:
            printf("请输入两个整数:");
            scanf("%d %d", &x, &y);
            ret = Div(x, y);
            printf("结果是:%d", ret);
            break;
        case 0:
            printf("正在退出计算器...");
            break;
        default:
            printf("请重新选择:");
            break;
        }
        printf("\n");
    } while (input);
    
    return 0;
}

缺点:添加新功能需要增加case,代码冗长

2. 转移表实现(函数指针数组)

转移表:通过函数指针数组实现函数调用转移

c 复制代码
int Add(int x, int y) { return x + y; }
int Sub(int x, int y) { return x - y; }
int Mul(int x, int y) { return x * y; }
int Div(int x, int y) { return x / y; }
int And(int x, int y) { return x & y; }
int Or(int x, int y)  { return x | y; }
int Nor(int x, int y) { return x ^ y; }
int contray(int x)    { return ~x; }  // 单目运算

void menu()
{
    printf("*********************************\n");
    printf("**********1.Add  2.Sub***********\n");
    printf("**********3.Mul  4.Div***********\n");
    printf("**********5.And  6.Or ***********\n");
    printf("**********7.Nor  8.contray*******\n");
    printf("**********0.exit      ***********\n");
    printf("*********************************\n");
}

int main()
{
    int input, x, y;
    
    // 函数指针数组(转移表)
    int (*parr[8])(int, int) = {0, Add, Sub, Mul, Div, And, Or, Nor};
    // 下标: 0   1    2    3    4    5    6    7
    
    do {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        
        if (input >= 1 && input <= 7)  // 双目运算
        {
            printf("请输入两个数:");
            scanf("%d %d", &x, &y);
            int ret = parr[input](x, y);  // 通过数组下标调用对应函数
            printf("结果是:%d\n", ret);
        }
        else if (input == 8)  // 单目运算(特殊处理)
        {
            printf("请输入一个数:");
            scanf("%d", &x);
            int ret = contray(x);
            printf("结果是:%d\n", ret);
        }
        else if (input == 0)
        {
            printf("退出计算器\n");
        }
        else
        {
            printf("请重新输入:\n");
        }
    } while (input);
    
    return 0;
}

优点

  • 添加新功能只需在数组中增加函数指针
  • 代码简洁,易于扩展
  • 消除了大量重复的switch-case代码

相关推荐
菜鸡儿齐2 小时前
leetcode-最大子数组和
数据结构·算法·leetcode
小妖6662 小时前
js 实现插入排序算法(希尔排序算法)
java·算法·排序算法
星火开发设计2 小时前
标准模板库 STL:C++ 的利器 —— 容器、算法、迭代器
java·开发语言·数据结构·c++·算法·html
blackicexs2 小时前
第五周第一天
算法
MIngYaaa5202 小时前
2026寒假牛客 2.13
算法
大梦南柯2 小时前
第一次作业
算法
iAkuya2 小时前
(leetcode)力扣100 71字符串解码(栈(两种)||递归)
windows·算法·leetcode
重生之后端学习2 小时前
105. 从前序与中序遍历序列构造二叉树
java·数据结构·后端·算法·深度优先
日更嵌入式的打工仔2 小时前
LAN9253中文注释第八章
笔记·原文翻译