C语言绝对值得一看的常识讲解:(补充篇)柔性数组+转移表

今天给大家补充两个比较有意思的知识点:

①今天突然看到的一个比较特别的知识点——柔性数组。它是在C99中出现的一种特别的数组,具体是指结构体中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

②之前讲解数组时,没有过多介绍数组元素是特殊元素时的数组,今天补充一下函数指针数组。

目录

1.柔性数组

1.柔性数组的定义

2.柔性数组的特点

3.柔性数组的使用举例

4.柔性数组的优点

2.转移表

1.函数指针数组

2.转移表


1.柔性数组

1.柔性数组的定义

柔性数组定义的一般形式为:

typedef struct st_type

        int i;

        int a[0];//柔性数组成员

}type_a;//这里的typedef是重命名的意思,是把原来struct struct st_type命名为type_a,以方便使用

 有些编译器会报错,我们可以改成下面这种定义方式。

typedef struct st_type

        int i;

        int a[ ];//柔性数组成员

}type_a;

2.柔性数组的特点

①结构中的柔性数组成员前面必须至少有一个其他成员。 

②sizeof 返回的这种数据结构大小不包括柔性数组的内存。 

③包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小,即malloc申请的空间应该是sizeof(结构体体变量)+柔性数组大小(用多少开辟多少)。 

struct st_type
{
    int i;
    int *a;//柔性数组成员
 }

int main()
{
    printf("%d\n", sizeof(st_type));//输出的是4
    return 0;
}
3.柔性数组的使用举例
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int i = 0;
    type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));//在使用前一定要定义,这里图方便没有定义
    p->i = 100;
    for(i=0; i<100; i++)
    {
        p->a[i] = i;
    }        
    free(p);//动态开辟的内存,记得及时释放
    return 0;
}
4.柔性数组的优点

柔性数组是一个结构体的一个成员数组,在上面的例子中,整个结构体都是在堆上malloc出来的。此时,整个结构体都是存储在堆上一块连续的空间内,因为数组arr的大小是可以改变的,所以就叫柔性数组。但是实际上如果我们把柔性数组成员换成一个指针成员,然后通过malloc也是可以动态开辟空间的,如下图代码所示:

struct st_type
{
    int i;
    int *arr;//柔性数组成员
 }

int main()
{
    struct st_type* ps = (struct st_type*)malloc(sizeof(struct st_type));
    if(ps == NULL)
    {
        printf("malloc()->%s\n") , strerror(errno));
        return 1;
    }
    
    ps->arr = (int*)malloc(10 * sizeof(int));
    if(ps->arr == NULL)
    {
        printf("2:malloc()->%s\n") , strerror(errno));
        return 1;
    }
    return 0;
}

上面指针arr在malloc后,即可当作数组使用。那么我们为什么要引入柔性数组呢?原因有以下两点:

①方便内存释放

使用时,柔性数组malloc一次,free一次;不使用柔性数组要malloc两次,free两次。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返 回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

②有利于提高访问速度

连续的内存有益于提高访问速度,也有益于减少内存碎片。

2.转移表
1.函数指针数组

之前我们对于数组进行了较为详细的讲解,但是对于一些基本元素是特殊元素的,没有过多讲解,我们这里补充一些关于函数指针数组的相关知识点。

数组名代表一个指向数组首元素的指针,其基类型是所包含的数组元素的数据类型。类似地,函数名代表一段程序代码在内存单元中的首地址,也称为函数的入口地址。

调用函数时,必须先为被调用函数分配连续的内存空间。该内存单元的地址也可以赋给特定类型的指针变量。赋值后,指针变量的内容就是该函数的内存单元中的首地址。该指针变量称为指向函数的指针变量,简称函数指针。通过函数指针可以方便地实现函数的使用。

定义函数指针的一般形式为:

数据类型(*函数指针名)(参数列表);//细品一下是不是把函数名用(*函数指针名)代替,其他和定义函数没有区别

定义函数指针数组的一般形式为:

数据类型(*函数指针数组名 [元素个数] )(参数列表);

函数指针数组的每个元素就是一个对应的函数指针,换句话说多个相同类型的函数指针在一起就是函数指针数组。

2.转移表

函数指针数组的用途:转移表

给大家举一个简单的例子:计算器的一般实现。

代码实现如下:

#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;
    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);
        switch (input)
        {
            case 1:
                printf("输⼊操作数:");
                scanf("%d %d", &x, &y);
                ret = add(x, y);
                printf("ret = %d\n", ret);
                break;
            case 2:
                printf("输⼊操作数:");
                scanf("%d %d", &x, &y);
                ret = sub(x, y);
                printf("ret = %d\n", ret);
                break;
            case 3:
                printf("输⼊操作数:");
                scanf("%d %d", &x, &y);
                ret = mul(x, y);
                printf("ret = %d\n", ret);
                break;
            case 4:
                printf("输⼊操作数:");
                scanf("%d %d", &x, &y);
                ret = div(x, y);
                printf("ret = %d\n", ret);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误\n");
                break;
        }
    } while (input);
    return 0;
}

相信正常情况下,让大家实现计算器的一般功能都是这样实现的,我们也不难看出这样写代码有两个缺点:①代码冗长②无效重复,所以这里我们就要展示我们的关键先生“转移表”的身手喽。

代码实现如下:

#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 x, int y) = { 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 <= 4 && input >= 1))
        {
            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;
}

显然函数指针数组既是转移表,我们把相同类型的函数指针放在一起,形成函数指针数组,只需要通过改变下标即可实现对于不同函数的调用,简化了代码,减少了无用功。

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值