问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C语言指针系列:数组指针、函数指针、typedef与函数数组指针详解

创作时间:
作者:
@小白创作中心

C语言指针系列:数组指针、函数指针、typedef与函数数组指针详解

引用
CSDN
1.
https://blog.csdn.net/wheeldown/article/details/142255600

本文详细讲解了C语言中的指针相关知识,包括数组指针、函数指针、typedef以及函数数组指针等概念。通过详细的解释和代码示例,帮助读者理解这些复杂的概念。

一、指针数组

思考:我们已经知道什么是数组指针了,那么指针数组又是什么?

答案是:指针数组是一个数组,储存的是地址(首元素),数组指针是数组还是指针?答案是——指针变量。

我们已经熟悉:

  • 整形指针变量:int *pi; 存放的是整形变量的地址,能够指向整形数据的指针。
  • 浮点型指针变量:float *pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
  • 数组指针:存放的是数组的地址,能够指向数组的指针变量。

二、指针数组和数组指针的区别

数组指针和指针数组的写法

int *parr[10] // 指针数组
// 解释:数组名先和[]结合,说明这是一个数组,数组中有10个元素,元素的类型是(int*)型,所以p2是一个数组,数组元素指针,叫做指针数组。

int (*p2)[10] // 数组指针
// 解释:p2先和(*)结合说明p2是一个指针变量,然后指针指向的是一个大小为10个整形的数组,所以p2是一个指针,叫做数组指针。
// []的优先级要高于*号,所以必须要用()来提高优先级,确保p2与*结合

数组指针的初始化

数组指针是用来存放数组地址的,那怎么才能获得数组地址?这里就要用到&的符号,如果要存放数组地址,就得存放数组指针变量中

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

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

我们调试可以看到,&arr 和 p 的类型是完全一致的。

三、二维数组传参的本质

我们将之前,先来回忆一下一维数组传参,一维数组传参为了避免开辟额外的空间,所以只需要传数组的首地址即可

我们再来看我们曾经写过的一段二维数组传参的代码:

void test(int a[3][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 ", a[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;
}

这里实参是二维数组,形参也写成二维数组的形式,那还有什么其他的写法吗?

重点

  • 二维数组在内存中是连续存储的
  • 二维数组可以理解为一维数组的数组,二维数组的每一行都可以看成一个一维数组
  • 二维数组名也是元素的首地址,这里的首地址是指第一行首元素,传过去的是第一行这个一维数组的地址,也就是arr[0]的地址。
  • 第一行的一维数组的类型就是int[5],所以第一行的地址的类型就是int(*) [5]

二维数组传参,形参的部分可以写成数组形式,也可以写成指针形式,如下:

#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));//等价于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;
}
  • p:数组首元素的地址,也就是一维数组arr[0]的地址。
  • p+i:跳过 i 个 int[5] 这样的数组(p的类型是数组指针),指向arr[i],p+i 就是一维数组 arr[i] 的地址。
  • *(p+i):访问一维数组arr[i],等价于一维数组arr[i],而 arr[i] 是数组名,又是数组首元素的地址,也就是arr[i][0] 的地址。
  • (p + i) + j:由于(p+i)是 arr[i][0] 的地址,所以 +j 跳过 j 个整形(指向整形),也就是arr[i][j] 的地址。
  • *( (p + i) + j):由于(p + i) + j是 arr[i][j] 的地址,进行解引用操作,就是找到arr[i][j]。
  • 最终:*( *(p + i) + j) 等价于 arr[i][j]。

二、函数指针

什么是函数指针变量呢?

根据前面学习整型指针,数组指针的时候,类比一下,我们不难得出结论:

函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。那么函数是否有地址呢?我们做个测试:

#include <stdio.h>

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

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

函数的地址确实是有的,同时也验证了函数的地址也是可以用&调取出来的。同时我们观察发现,函数名和取出来的地址是一样的。

函数指针变量的使用

如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针非常类似。如下:

#include<stdio.h>

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;//parr 是数组指针变量
    //arr:数组首元素的地址   &arr:数组的地址

    //&函数名和函数名都是函数的地址,没有区别
    //printf("%p\n", &Add);
    //printf("%p\n", Add);
    //int(*pf3)(int, int) = Add;
    //int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的 

    //int (*pf)(int,int) = &Add;//pf 函数指针变量,()不能省略
    int (*pf)(int, int) = Add;//pf 函数指针变量
    int ret1 = (*pf)(4, 5);
    int ret2 = pf(4, 5);//pf等价于Add
    printf("%d\n", ret1);
    printf("%d\n", ret2);
    int ret = Add(4, 5);
    printf("%d\n", ret);
    //int (*pf)(int x, int y) = &Add;//pf 函数指针变量
    //int (*)(int,int) 函数指针类型
    return 0;
}
  1. int (*pf)(int, int) = Add,*pf外的 () 不能省略。
  2. pf == (*pf) == Add == &Add。

两端有趣的代码:

  1. ( *( void ( * )( ) )0 ) ( );
  2. void( *signle(int, void( * )(int) ) ) (int)

typedef重命名函数

typedef是用来重命名的,可以将复杂的名字简单化,规范化

比如我们命名了一个结构体叫做jinfsjajngijiasogjoiasjda(随便打的),我们每次调用都要写很长一段复杂的东西,但是有了typedef这个东西,我们可以将它重命名为js,对!就这两个字符,就可以表达这个结构体

typedef unsigned int uint;
//将unsigned int 重命名为uint

如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:

typedef int* ptr_t;

但是对于数组指针和函数指针稍微有点区别:

比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5];

函数指针类型的重命名也是⼀样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:

typedef void(pfun_t)(int);//新的类型名必须在的右边

那么要简化代码2,可以这样写:

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

重点
给出typedef命名一个数组的例子:

#include<stdio.h>

int main()
{
    typedef int IntArray[5]//定义一个包含5个int类型元素的数组类型IntArray
    IntArray arr={1,2,3,4,5};// 使用 IntArray 声明一个数组
    for(int i=0;i<5;i++)
    {
        printf("%d",arr[i]);
    }
    return 0;
}

四、函数指针数组

定义:
int(*parr[3])();

解释:parr先和[]结合表明这是一个数组,数组的内容是什么,是int(*)()类型的函数指针

函数指针数组的实际应用:转移表

计算器的一般实现路径代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

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 = 0;
    int y = 0;
    int input = 0;
    int ret = 0;
    do
    {
        menu();
        printf("请输入:");
        scanf("%d", &input);
        switch (input)
        {
        case 0:
            break;
        case 1:
            printf("请输入两个数:");
            scanf("%d %d", &x, &y);
            ret = Add(x, y);
            printf("%d+%d=%d\n", x, y, ret);
            break;
        case 2:
            printf("请输入两个数:");
            scanf("%d %d", &x, &y);
            ret = Sub(x, y);
            printf("%d-%d=%d\n", x, y, ret);
            break;
        case 3:
            printf("请输入两个数:");
            scanf("%d %d", &x, &y);
            ret = Mul(x, y);
            printf("%d*%d=%d\n", x, y, ret);
            break;
        case 4:
            printf("请输入两个数:");
            scanf("%d %d", &x, &y);
            ret = Div(x, y);
            printf("%d/%d=%d\n", x, y, ret);
            break;
        default:
            printf("输入错误,请重新输入\n");
            break;
        }
    } while (input);
    return 0;
}

用函数指针实现的:

#include<stdio.h>

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 = 0;
    int y = 0;
    int input = 0;
    int ret = 0;
    do
    {
        menu();
        printf("请输入:");
        scanf("%d", &input);
        int (*arr[5])(int, int) = { 0,Add,Sub,Mul,Div };//转移表
        if (input == 0)
        {
            break;
        }
        else if (input >= 1 && input <= 4)
        {
            printf("请输入两个数:");
            scanf("%d %d", &x, &y);
            ret = arr[input](x, y);
            printf("ret=%d\n", ret);
        }
        else
        {
            printf("输入错误,请重新输入\n");
        }
    } while (input);
    return 0;
}

不仅仅可以实现加减乘除,还能实现按位与,或,异或,左移,右移等操作,只需在数组中追加函数地址即可,当然前提是将函数敲出来,这种就叫作转移表。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号