C语言结构、联合、枚举
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
void func1(void);
void func2(void);
void manybook(void);
char* s_gets(char* s, int limit);
void generate1(void);
void get_ins_info(struct insurance* sp);
void get_ins_info2(struct insurance s);
void booksave(void);
void seat_reserve(void);
void show_empty_num(struct seat* sp, int size);
void show_empty_list(struct seat* sp, int size);
void show_alphabetical_list(struct seat* sp, int size);
void assign_seat(struct seat* sp, int size);
void delete_seat(struct seat* sp, int size);
void other1(void);
void func1(void)
{
struct month { char name[20]; char abbre[4]; int days; int num; }; //structure结构,可将多种类型组成一个整体。
// 格式:struct 结构标识符 {字段1类型(field字段/member成员,结构中包含的数据的类型) 字段1标识符;字段2类型 字段2标识符...} 变量名;
//不写变量名只写标识符则为只声明结构类型,不创建变量,声明结构类型不会产生内存块。而写变量名会创建该结构,不需要多次创建该结构可以省略标识符
struct month January = { "January","jan",31,1 }; // 创建结构变量时格式: struct 结构标识符 变量名 ,定义变量时可以使用{value1,value2...}初始化结构,需按照结构声明的字段顺序,如果不初始化则该内存块仍然保留之前的垃圾值
printf("%s has %d days", January.name, January.days); //结构变量通过 . 成员运算符访问结构的成员
struct month year[12] = { January,[1] = {"February",.num = 2} }; //声明结构数组,和普通类型的数组方法一样,只写了1月和2月,剩下的部分会初始化为0,结构变量的初始化中通过.num的形式(初始化器)给2月的num成员初始化为2,没有显式初始化的abbre和days会初始化为0
strcpy(year[1].abbre, "feb"); //给结构的成员单独赋值,字符数组需要strcpy/strncpy完成赋值,而对整个结构赋值时不需要对每个字符数组字段使用strcpy
year[1].days = 28;
year[2] = (struct month){ "March","mar",31,3 }; //复合字面量,格式(struct 结构标识符){每个成员/字段的值}
// 省略数组剩余月份赋值过程
char user_input[20] = "";
int total = 0;
puts("输入月份名:");
scanf("%19s", user_input);
for (int i = 11; i >= 0; i--)
{
if (strcmp(user_input,year[i].name)==0)
{
for (int j = 0; j <= i; j++)
{
total += year[j].days;
}
printf("当年直到该月一共有%d天\n", total);
break;
}
}
if (total==0)
{
puts("月份名错误");
}
}
void func2(void)
{
extern struct month
{
char name[20];
char abbre[4];
int days;
int num;
} months[]; //引用定义好的结构数组,数组大小应省略,但结构类型的字段内容不能省略,C将结构类型的所有字段完全相同的结构视为同一个结构类型,不论结构类型标识符
printf("输入年:");
int y, d;
char m[20]="";
int m_index;
if (scanf("%d", &y)==1)
{
if (y%4==0)
{
months[1].days = 29; //处理闰年
}
printf("输入月:");
if (scanf("%19s", m)==1) //可以输入英文全称、缩写、数字
{
if ((m_index = (int)strtol(m, NULL, 10))!=0) //strtol()转换失败/字符串开头没有读取到数字返回0
{
if (m_index<1||m_index>12) //检测数字有效性
{
m_index = 12; //设置为无用值
}
else
{
m_index--; //月份号比数组索引多1
}
}
else //m_index 在if中判断为0
{
for (; m_index < 12; m_index++)
{
if (strcmp(months[m_index].name, m) == 0 || strcmp(months[m_index].abbre, m) == 0) //比较英文,如果数组元素都不匹配m_index的值变为12和上面的无用值一致
break;
}
}
if (m_index<12) //小于12则一定为0-11中的一个有效数
{
printf("输入日:");
if (scanf("%d", &d)==1&&d>0&&d<=months[m_index].days) //检测日的有效性
{
int total = d;
for ( int i = 0; i < m_index; i++)
{
total += months[i].days;
}
printf("%d年%s月%d日是这一年的第%d天\n", y, m, d, total);
return;
}
}
}
}
puts("输入有误");
}
void manybook(void)
{
struct book { //在函数中声明的结构类型具有块作用域,在函数外声明具有文件作用域
char title[40];
char author[40];
float value;
} library[100];
int count = 0;
int index;
printf("Please enter the book title.\nPress [enter] at the start of a line to stop.\n");
while (s_gets(library[count].title, 40)!=NULL&&library[count].title[0]!='\0') //得到标题
{
puts("Now enter the author.");
s_gets(library[count].author, 40);
puts("Now enter the value.");
scanf("%f", &library[count++].value); //完成一个结构的赋值,count++
while (getchar()!='\n')
continue; //清空行
if (count < 100)
puts("Enter the next title.");
else
break;
}
if (count>0)
{
struct book** parr = (struct book**)malloc(count * sizeof(struct book*)); //动态分配包含count个指向book结构的指针元素的数组,*parr[]转换为**parr
//函数返回void*,需要强转成parr的类型,malloc的参数是字节的数量,与类型无关,返回的是内存块的首地址,需要转换成什么类型就转换成什么类型,应转换成和parr同类型,而不是struct book*
puts("Here is the list of your books:");
for ( index = 0; index < count; index++)
{
printf("%s by %s: $%.2f\n", library[index].title, library[index].author, library[index].value);
parr[index] = library + index; //指针数组的指针逐一指向结构数组的元素
}
struct book* temp;
int complete;
for ( index = 0; index < count; index++)
{
complete = 1;
for (int j = count-1; j >index; j--)
{
if (strcmp((*parr[j - 1]).title,parr[j]->title)>0) //通过指针访问结构变量的两种写法,library[j]是结构,parr[j]=&library[j]是指针/地址,*parr[j]是结构,(*parr[j]).title结构调用字段,解引用*优先级低于. 成员运算符需要括起来,另一种常用写法parr[j]->title和(*parr[j]).title等价,结构地址->结构字段,(*结构地址).结构字段,->间接成员运算符
{
temp = parr[j];
parr[j] = parr[j - 1];
parr[j - 1] = temp;
complete = 0;
}
}
if (complete)
break;
}
for (index = 0; index < count; index++)
{
printf("%s by %s: $%.2f\n", parr[index]->title, parr[index]->author, parr[index]->value);
}
for ( index = 0; index < count; index++)
{
complete = 1;
for (int j = count-1; j >index; j--)
{
if (parr[j-1]->value>parr[j]->value)
{
temp = parr[j];
parr[j] = parr[j - 1];
parr[j - 1] = temp;
complete = 0;
}
}
if (complete)
break;
}
for (index = 0; index < count; index++)
{
printf("%s by %s: $%.2f\n", parr[index]->title, parr[index]->author, parr[index]->value);
}
free(parr); //动态生成的内存最好在创建时就附上释放的代码避免遗忘
}
else
{
puts("No books? Too bad.");
}
}
char* s_gets(char* s, int limit)
{
char* p1 = fgets(s, limit, stdin);
if (p1)
{
char* p2 = strchr(s, '\n');
if (p2)
*p2 = '\0';
else
while (getchar() != '\n')
continue;
}
return p1;
}
struct insurance { //结构储存保险的编号和人名
unsigned long num;
struct fullname { // 结构中使用结构,fullname结构类型字段包含名、中间名和姓,字段的标识符为name,如果之前已经声明了fullname结构,则简写为struct fullname name; 如果该结构只在这里使用,可不写fullname结构类型标识符
char first[20];
char mid[20];
char last[20];
} name;
};
void generate1(void)
{
struct insurance customers[5] = {
{302038923,{"Dribble","Mac","Flossie"}},
{302038543,{"Abel","Ken","Smith"}},
{302345922,{"Caleb","","Jones"}},
{302876924,{"Ford",.last = "williams"}}, // 跳过mid给last赋值
{305678926,{"Eddie","Que","Brown"}},
}; //结构数组,每个元素内又有一个结构需要再加一层大括号
get_ins_info(customers); //第一个元素的地址
get_ins_info2(customers[1]); //第二个元素的值
}
void get_ins_info(struct insurance* sp)
{
printf("%s , %s ", sp->name.first,sp->name.last); // 指针->结构字段标识符.char字段标识符,如果声明insurance结构时省略了内部结构字段的标识符name,则insurance类型变量在使用时直接通过.即可访问内部结构的字段,写作sp->first
if (sp->name.mid[0])
{
printf("%c. ", sp->name.mid[0]);
}
printf("--%lu\n", sp->num);
}
void get_ins_info2(struct insurance s) //将结构作为值接收,形参s初始化为函数调用时实参/值的备份
{
struct insurance s2 = s; //结构可以赋值,结构名不是地址(不同于数组)
printf("%s , %s ", s2.name.first, s2.name.last);
if (s2.name.mid[0])
{
printf("%c. ", s2.name.mid[0]);
}
printf("--%lu\n", s2.num);
// 这也意味着,更改s或s2的值不会影响主调函数的变量
}
typedef struct { struct { char first[20]; char last[20]; }; double score[3]; double average; } STUDENT; //使用typedef取别名,将整个嵌套结构取别名为student,好处是定义结构变量时不需要写struct,坏处是不能显式表明该类型为结构,所以建议用大写来表示该类型为复杂的类型
//由于使用typedef,结构的标识符就没用了省略,内部结构没有声明字段名,所以直接.调用内部的字段,如果函数的形参使用了typedef声明的别名,需要将typedef声明放在函数原型之前才能生效
void get_student_info(STUDENT* sp) //STUDENT是某个结构的类型,所以sp是结构的指针
{
printf("%s %s : %.2f %.2f %.2f avg:%.2f\n", sp->first, sp->last, sp->score[0], sp->score[1], sp->score[2], sp->average); //匿名内部结构的first/last通过外部结构直接.first调用
}
#define MAXTITL 40 //define预处理只对该行以下的MAXTITL生效
#define MAXAUTL 40
#define MAXBKS 10
struct book
{
char title[MAXTITL];
char author[MAXAUTL];
float value;
};
void booksave(void)
{
struct book library[MAXBKS]; // 自动存储类别的对象被储存在栈中,如果结构数组大小很大,会造成栈溢出,可以使用编译器选项设置栈大小为10000或创建静态数组(包括静态块作用域),编译器不会将静态数组放在栈中
struct book *ptra[MAXBKS], *temp;
int count = 0;
int index, filecount;
char s[2];
FILE* pbooks;
int size = sizeof(struct book); //将book结构的尺寸记录
if ((pbooks=fopen("data01.txt","rb"))==NULL)
{
fputs("Can't open data01.txt file\n", stderr);
exit(1);
}
while (count<MAXBKS&&fread(library+count,size,1,pbooks)==1) //将结构在内存中的值原样保存到二进制文件中,读取时按结构的尺寸逐个结构读取,fread()返回读取到的数据块
{
if (count==0)
{
puts("Current contents of data01.txt:");
}
printf("No.%d %s by %s: $%.2f\n", count, library[count].title, library[count].author, library[count].value);
count++; //count记录图书列表中图书的总数
}
for (int i = 0; i < MAXBKS; i++)
{
ptra[i] = library + i; // 将指针数组和结构数组逐一对应,指针数组的元素是指向结构的指针,结构数组的元素是结构
}
while (puts("a)add new book\tu)update exist book\td)delete exist book\tq)quit"),s_gets(s,2)!=NULL&&s[0]!='q') //增删改查
{
switch (s[0])
{
case 'a':
if (count>=MAXBKS)
{
puts("The data01.txt file is full. You can delete some books or update.");
}
else
{
puts("Please add new book titles.\nPress [enter] at the start of a line to stop.");
while (s_gets(ptra[count]->title,MAXTITL)!=NULL&& ptra[count]->title[0]!='\0') //使用指针数组添加book
{
puts("Now enter the author.");
s_gets(ptra[count]->author, MAXAUTL);
puts("Now enter the value.");
scanf("%f", &ptra[count++]->value); //当前元素所有信息录完,递增count
while (getchar()!='\n')
{
continue;
}
if (count<MAXBKS)
{
puts("Enter the next title.");
}
else
{
break;
}
}
}
break;
case 'u':
if (count<=0)
{
puts("There are not books in the file.");
continue;
}
else
{
printf("Which book do you want to update(0-%d,other to quit)?", count-1); //0到count-1
while (s_gets(s,2)!=NULL)
{
index = s[0] - '0'; //ASCII字符集中,数字字符减'0'字符等于数字的值
if (index>=0&&index<count)
{
printf("No.%d %s by %s: $%.2f\nt)title\ta)author\tv)value\n", index, ptra[index]->title, ptra[index]->author, ptra[index]->value);
if (s_gets(s, 2) != NULL && s[0] != '\0' && strchr("tav", s[0]) != NULL) //strchr函数参数2是\0时会返回参数1字符串的末尾\0的位置,不为null
{
printf("new content:");
switch (s[0])
{
case 't':
s_gets(ptra[index]->title, MAXTITL);
break;
case 'a':
s_gets(ptra[index]->author, MAXAUTL);
break;
case 'v':
scanf("%f", &ptra[index]->value);
while (getchar() != '\n')
continue;
}
printf("No.%d %s by %s: $%.2f\nt)title\ta)author\tv)value\n", index, ptra[index]->title, ptra[index]->author, ptra[index]->value);
}
}
else
{
break;
}
printf("Which book do you want to update(0-%d)?", count - 1);
}
}
break; //每个case的break
case 'd':
if (count <= 0)
{
puts("There are not books in the file.");
continue;
}
else
{
printf("Which book do you want to delete(0-%d)?", count - 1);
if (s_gets(s,2)!=NULL&&(index=s[0]-'0')>=0&&index<count)
{
printf("No.%d %s by %s: $%.2f\n", index, ptra[index]->title, ptra[index]->author, ptra[index]->value);
printf("Delete(y/n)?");
if (s_gets(s, 2) != NULL && tolower(s[0]) == 'y')
{
if (index<MAXBKS-1)
{
temp = ptra[index];
memmove(ptra + index, ptra + index + 1, (MAXBKS - 1 - index) * sizeof(struct book*)); //操作指针数组,将指向要删除的book的指针元素后面的元素前移,使用memmove复制整块数据对象,将指向要删除的book的地址(结构数组中该book元素的地址)交换到指针数组的最后一位元素上,这一步操作并没有清除数据
ptra[MAXBKS - 1] = temp;
}
count--; //记录总数的count递减,因为移动了指针数组的部分元素,前count个元素依然是当前的图书,而要删除的book的地址被移动到count范围外,不能读取相当于删除,这也完成了删除book使后面的book前移的操作,而如果要删除的是索引9即指针数组的最后一个指针,则只需要递减count将该book排除,经过删除操作后,指针数组的元素顺序和结构数组的元素顺序不再相同,但依旧保持一对一的关系
puts("Delete success.");
}
}
}
break;
default:
continue;
}
if (count>0)
{
puts("Here is the list of your books:");
for (index = 0; index < count; index++)
{
printf("No.%d %s by %s: $%.2f\n", index, ptra[index]->title, ptra[index]->author, ptra[index]->value); //顺序以指针数组为准
}
}
else
{
puts("The list of your books is empty now.");
}
}
fclose(pbooks);
if (pbooks = fopen("data01.txt", "wb")) //清空文件
{
for ( index = 0; index < count; index++)
{
fwrite(ptra[index], size, 1, pbooks); //只保存指针数组中前count个元素指向的结构
}
fclose(pbooks);
}
if (count<=0)
{
puts("No books? Too bad.\n");
}
puts("Bye.\n");
}
struct seat {
int num;
_Bool reserved;
char first[20];
char last[20];
};
#define SEATSIZE 12
void seat_reserve(void)
{
struct seat seats[SEATSIZE] = {
{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12} //初始化座位编号,reserved字段预定状态默认初始化为0
};
puts("To choose a function, enter its letter label:\na)Show number of empty seats\tb)Show list of empty seats\nc)Show alphabetical list of seats\td)Assign a customer to a seat assignment\ne)Delete a seat assignment\tf)Quit");
char ch;
typedef void (*func_ptr)(struct seat*, int); //逐步分析,void func()为函数,无返回,void* func()为返回指向void指针的函数(因为括号优先级高于解引用,func和()结合成为函数,圆括号(2+3)和方括号[i]优先级相同), void (*func)()为函数指针,因为*先和func结合,使func成为指针,然后将*func看成一个整体和()结合成为函数,也就是说*func是函数,func是指针,然后void声明该函数为void类型,函数的类型+函数的形参列表合称函数签名,所以func是指向函数的指针,该函数为void类型有两个形参
//typedef 类型 别名, 对于函数,typedef 函数签名 别名,(*func_ptr)以外的部分为函数签名,*func_ptr为函数,func_ptr为函数指针类型的别名
//声明函数指针的方法为,先写出函数原型,void func(),再将函数名替换为 (*标识符) 的形式,标识符代表的就是函数指针
func_ptr func_ptrs[5] = { show_empty_num,show_empty_list ,show_alphabetical_list ,assign_seat ,delete_seat }; //声明包含五个函数指针元素的数组,函数名为函数的地址,加括号为调用
//如果不使用typedef,声明指针数组的格式为:void (*func[5])() ,func先和[5]结合成为数组,再和*结合成为存放指针的数组,再()存放函数指针,再void。如果写成void (*func)[5](),因为括号()和[]优先级相同,根据结合律从左到右,func和*成为指针,和[5]成为指向5个元素数组的指针,这时(*func)[5]整体为一个数组,数组外的部分都是数组元素的定义,所以和()结合使元素成为函数,再void,但C不允许函数数组,这样的声明时非法的,只能声明函数指针的数组
while ((ch=tolower(getchar()))!='f')
{
if (ch != '\n')
while (getchar() != '\n')
continue;
if (strchr("abcde", ch) && ch != '\0')
(*func_ptrs[ch - 'a'])(seats, SEATSIZE); //func_ptrs为函数指针的数组,func_ptrs[0]为函数指针,(*func_ptrs[0])为函数,后面加(参数)来调用,因为*优先级低于()函数调用所以要括起来,C允许(*函数指针)(参数)的形式,也允许函数指针(参数)的形式(不推荐)
//a-a=0 e-a=4,对应指针数组的五个函数
puts("To choose a function, enter its letter label:\na)Show number of empty seats\tb)Show list of empty seats\nc)Show alphabetical list of seats\td)Assign a customer to a seat assignment\ne)Delete a seat assignment\tf)Quit");
}
}
void show_empty_num(struct seat* sp, int size)
{
int count = 0;
for (int i = 0; i < size; i++)
{
if (sp[i].reserved)
continue;
printf("%d\t", sp[i].num);
count++;
}
if (count)
putchar('\n');
else
puts("These are not empty seats now.");
}
void show_empty_list(struct seat* sp, int size)
{
for (int i = 0; i < size; i++)
{
if (sp[i].reserved)
continue;
printf("seat number:%d status:not reserved customer information:none\n", sp[i].num); //sp是指针,sp[i]是结构,*sp是结构,sp+i是地址,所以也可以写成(sp+i)->num
}
}
void show_alphabetical_list(struct seat* sp, int size)
{
struct seat** temparr = (struct seat**)malloc(size*sizeof(struct seat*)); //动态分配内存
for (int i = 0; i < size; i++)
{
temparr[i] = sp + i;
}
int complete;
for (int i = size-1; i >0; i--)
{
complete = 1;
for (int j = 0; j < i; j++)
{
if (temparr[j]->first[0])
{
if (temparr[j + 1]->first[0]&& strcmp(temparr[j]->first, temparr[j + 1]->first) > 0)
{
sp = temparr[j]; //sp传参给temparr之后就没用了,当做交换用临时变量
temparr[j] = temparr[j + 1];
temparr[j + 1] = sp;
complete = 0;
}
}
else if (temparr[j + 1]->first[0])
{
sp = temparr[j];
temparr[j] = temparr[j + 1];
temparr[j + 1] = sp;
complete = 0;
}
}
if (complete)
break;
}
for (int i = 0; i < size; i++)
{
printf("seat number:%d status:%s customer information:%s %s\n", temparr[i]->num, temparr[i]->reserved?"reserved" : "not reserved", temparr[i]->first, temparr[i]->last); //temparr是指向指针的指针,temparr[i]是指针,(*temparr[i])是结构,等价于temparr[i][0]
}
free(temparr);
}
void assign_seat(struct seat* sp, int size)
{
printf("Enter the number of seat to assign(1-%d other to quit):", size);
int a;
char ch;
while (scanf("%d",&a)==1&&a>0&&a<=size)
{
while (getchar() != '\n')
continue;
if (sp[a-1].reserved)
{
puts("The seat have been reserved. Try another:");
continue;
}
printf("Enter the first name and last name:");
scanf("%19s%19s", sp[a - 1].first, sp[a - 1].last);
while (getchar() != '\n')
continue;
printf("Sure(y/n)?");
if ((ch=tolower(getchar()))=='y')
{
sp[a - 1].reserved = 1;
puts("Success.");
}
if(ch!='\n')
while (getchar() != '\n')
continue;
printf("Enter the number of seat to assign(1-%d other to quit):", size);
}
while (getchar() != '\n')
continue;
}
void delete_seat(struct seat* sp, int size)
{
printf("Enter the number of seat to delete(1-%d other to quit):", size);
int a;
char ch;
while (scanf("%d", &a) == 1 && a > 0 && a <= size)
{
while (getchar() != '\n')
continue;
if (sp[a - 1].reserved)
{
printf("seat number:%d status:%s customer information:%s %s\n", sp[a - 1].num, sp[a - 1].reserved ? "reserved" : "not reserved", sp[a - 1].first, sp[a - 1].last);
printf("Sure(y/n)?");
if ((ch = tolower(getchar())) == 'y')
{
sp[a - 1].reserved = 0;
sp[a - 1].first[0] = '\0';
sp[a - 1].last[0] = '\0';
puts("Success.");
}
if (ch != '\n')
while (getchar() != '\n')
continue;
printf("Enter the number of seat to delete(1-%d other to quit):", size);
}
else
{
puts("The seat is empty. Try another:");
continue;
}
}
while (getchar() != '\n')
continue;
}
void other1(void)
{
union MyUnion // 联合union,是一种数据类型,创建一个空间储存字段,大小为字段中最大的字段的大小,同一时间只能存储其中一个字段
{
int i;
float f;
char ch;
double d;
struct { short s1; short s2; };
} u = {1.2}; //联合初始化默认初始化为第一个字段的类型,1.2发生截断,如果要指定初始化为d字段,使用{.d=1.2}
u.s1 = 20; //匿名结构直接调用,对联合的其他字段赋值会覆盖之前的字段值,同时间只能存储一个字段,即便该字段默认大小远小于联合的大小
struct { int uniontype; union MyUnion value} su = { 1,{20} }; //用一个标识记录当前联合中存储的类型,如type=1说明value.i有效,这样做使得数据块的大小一致,存储和读取时通过type访问联合正确的成员
struct ss {
int status;
union {
char c;
short s;
}; // 匿名联合,可通过结构变量.c直接访问联合成员
};
struct flexible //在结构中使用伸缩型数组成员
{
int size;
double arr[]; //最后一个成员声明为一个不写大小的数组
}; //这个结构类型的大小为伸缩型数组成员以外所有字段大小的和,这里是1个int的大小,也就是说这时结构的.arr并不存在
struct flexible* sp = malloc(sizeof(struct flexible) + 5*sizeof(double)); //这种结构需要配合动态分配内存,在结构的大小之外增加数组所需的大小,这里设定数组有5个double元素
sp->size = 5; //在结构中记录数组的尺寸
for (int i = 0; i < sp->size; i++)
{
sp->arr[i] = i * 1.0; //对每个元素赋值
}
free(sp); //因为结构实际上不包含伸缩型数组成员,所以使用结构赋值时只会拷贝除数组以外的部分
enum MyEnum //枚举类型,enumerated type,声明符号名称来表示整形常量,enum实际类型为int
{
ZERO,ONE,THREE=3,FOUR,EIGHT=8,NINE //这里声明的ZERO的值为0,接着的ONE的值为1,枚举默认从0开始,通过符号名称=来对特定符号赋值,之后的符号顺着上一个的值递增
};
enum MyEnum i; //声明枚举变量,实际类型为int,可以是任意int的值
char st[NINE]; //可以使用枚举常量定义数组大小,和#define NINE 9作用类似
//补充: *解引用&取址运算符优先级低于.成员运算符->间接成员运算符,所以ptr->xx改为(*ptr).xx,而*ptr->ptr2->struct3.memberptr会解引用memberptr,&ptr->member会取址member,三种括号(无论用法)的优先级和.->同级(同为最高级),所以ptr->func()根据结合律从左往右先找到ptr指向的结构的func成员再作为函数调用,funcs[23]()同样从左往右
}