今天给大家补充两个比较有意思的知识点:
①今天突然看到的一个比较特别的知识点——柔性数组。它是在C99中出现的一种特别的数组,具体是指结构体中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
②之前讲解数组时,没有过多介绍数组元素是特殊元素时的数组,今天补充一下函数指针数组。
目录
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;
}
显然函数指针数组既是转移表,我们把相同类型的函数指针放在一起,形成函数指针数组,只需要通过改变下标即可实现对于不同函数的调用,简化了代码,减少了无用功。