C语言3——结构体、联合体、枚举、宏定义
1、初始化
方式一
#include <stdio.h> #include <string.h> #pragma warning(disable:4996) struct student{ char name[100]; int age; int sex; };//说明了一个结构体的数据成员类型 int main(){ struct student st;//定义一个结构体变量st st.age=20; st.sex=0; strcpy(st.name,"myj"); printf(); return 0; }
方式二
//定义结构体的时候就可以初始化 struct student st={"myj",24,0}; struct student st={0};//将所有成员值初始化为0,字符串将会初始化为空串
方式三
scanf("%s",st.name); scanf("%d",&st.age); scanf("%d",&st.sex);
方式四
struct student st={.age=20,.name="myj3",.sex=0}; struct student st={.name="myj3",.sex=0};//没有给age初始化,默认为0
方式五
struct student st; memset(&st,0,sizeof(st));
2、结构体内存对齐模式
struct A{ int a; char b; }; int main(){ struct A s; printf("%d",sizeof(s));//8 计算长度的时候会以所有成员里面最长的那个为基准 }
struct A{ int a; char b; char c; char d; char e; }; int main(){ struct A s; printf("%d",sizeof(s));//还是8 }
struct A{ int a; char b; char c; char d; char e; char f; }; int main(){ struct A s; printf("%d",sizeof(s));//12 }
struct A{ char a[10]; char b; char c; char d; char e; char f; char g; }; int main(){ struct A s; printf("%d",sizeof(s));//16 只会把数组中的一个元素当成一个成员 }
struct A{ char int[10]; char b; char c; char d; char e; char f; char g; }; int main(){ struct A s; printf("%d",sizeof(s));//48 只会把数组中的一个元素当成一个成员 }
struct A{ char c; int a; char b; } int main(){ struct A s; printf("%d",sizeof(s));//12 由于第一个成员是char类型 char会扩展为和第2个int成员相同的空间 }
struct A{ char a; short b; int c; short d; char e; } int main(){ struct A a; a.a=1; a.b=2; a.c=3; a.d=4; a.e=5; } //实例化之后的结构体a在内存中结构为 //01 xx 00 02 //00 00 00 03 //00 04 05 xx //其中xx表示没有被利用的空间 所以sizeof(a)是12 //结构体变量在占内存时总是以2的倍数为开始索引来占据的,再例如: struct A{ char a; short b; int c; char f; short d; char e; } int main(){ struct A a; a.a=1; a.b=2; a.c=3; a.d=4; a.e=5; a.f=6; } //结构体变量a在内存中的结构为 //01 xx 00 02 //00 00 00 03 //06 xx xx xx //00 04 05 xx //------------------ //所以sizeof(a)是16 struct A{ char a; short b; int c; short d; char e; char f; } int main(){ struct A a; a.a=1; a.b=2; a.c=3; a.d=4; a.e=5; a.f=6; } ----------------------- //01 xx 00 02 //00 00 00 03 //00 04 05 06 //sizeof(a)是12
指定结构体成员的位字段
struct D{ char a:2;//定义一个成员类型为char,但这个成员只是用2个bit } int main(){ struct D d; d.a=3; printf("%d\n",d.a);//-1 printf("%d\n",sizeof(d));//1 return 0; }
struct D{ char a:2; char b:2; } int main(){ struct D d; printf("%d\n",sizeof(d));//还是1,也就是说我们可以通过字节下的比字节更小的单位bit来控制 return 0; }
struct D{ char a:2; unsigned char b:4; } int main(){ struct D d; d.a=3; d.b=16; printf("%d %d\n",d.a,d.b);//-1 0 printf("%d\n",sizeof(d));// return 0; }
//在嵌入式的场合通常使用: struct D{ char a:1;//只有两种状态,开或者关 } //一个LED灯的话用这样的八个成员就可以了(不要忘了小数点)
struct F{ int i:2; char c:2; } int main(){ printf("%d\n",sizeof(struct F));//8 }
struct G{ char a; char b; char c; } int main(){ struct G g; char *p=&g; p[0]=3; p[1]=4; p[2]=0; printf("%d %d %d",g.a,g.b,g.c);//3 4 0 }
struct H{ char a; int b; } int main(){ struct H h={1,2}; char *p1=&h; p1++; *p1=4; p1++; *p1=5; printf("%p\n",&h);//01 04 05 xx 02 00 00 00 从结果中可以看出我们已经把第一行char类型浪费的字节利用起来了 return 0; }
3、结构体数组
struct student{ char name[16]; unsigned char age; unsigned char score; char classes[100]; }; int main(){ struct student st[3]={{"aaa",20,50,"c"},{"bbb",21,60,"c++"},{"ccc",22,70,"java"}}; int i; for(i=0;i<5;i++){ printf("姓名=%s 年龄=%d 成绩=%d 班级=%s\n",st[i].name,st[i].age,st[i].score,st[i].classes); } return 0; }
struct A{ char array[10]; } int main(){ struct A a1={"hello"};//如果结构体的成员是数组,通过结构可以变相的实现数组的赋值,而数组之间是不可以直接赋值的 struct A a2={0}; a2=a1;//这样写是合法的 printf("%s",a2.array); }
void swap(struct student *a,struct student *b){ struct student tmp=*a; *a=*b; *b=tmp; } int main(){ int i; int j; for(i=0;i<5;i++){ for(j=1;j<5-i;j++){ if(st[j].age<st[j-1].age){ swap(&st[j],&st[j-1]); }else if(st[j].age==st[j-1].age){ //如果年龄一样就比较分数 if(st[j].score<st[j-1].score){ swap(&st[j],&st[j-1]); } } } } }
//如果通过班级来排序,即给字符串排序 int main(){ int i; int j; for(i=0;i<5;i++){ for(j=1;j<5-i;j++){ if(strcmp(st[j].classes,st[j-1].classes)>0){ swap(&st[j],&st[j-1]); } } } return 0; }
4、结构体嵌套
struct A{ int a; char b; }; struct B{ struct A a; char b; } int main(){ printf("%d\n",sizeof(struct B));//12 return 0; }
struct A{ int a; int b; }; struct B{ int c; struct A a; int b; }; struct C{ struct B b;//仍然以int对齐 char d; }; int main(){ printf("%d",sizeof(struct C));//20 }
struct A1{ char a; } struct A2{ struct A1 a; char b; } struct A3{ struct A2 a; char b; } int main(){ struct A3 a3; //a3.a.a.a=100; 栈中的结构体通过点访问 struct A3 *ap=malloc(sizeof(struct A3)); ap->a.a.a=20;//堆中的结构体通过->访问 }
5、结构体拷贝
struct A a1,a2; a1.a=1; a1.b=2; a2=a1;//结构体之间内存的拷贝 相当于memcpy(&a2,&a1,sizeof(a1));
6、指向结构体的指针
struct A a; struct A *p=&a; p->a=10;//相当于(*p).a=10; p->b=20;//相当于(*p).b=10;
7、堆内存结构体
int main(){ struct A *p=malloc(sizeof(struct A)*10); memset(p,0,sizeof(struct A)*10); struct A *array=p; p->a=1; p->b=2; p++; p->a=3; p->b=4; int i; for(i=0;i<10;i++){ printf("%d %d",array[i].a,array[i].b); } free(array);//不能free掉p,因为p已经赋给array了,free知道要去释放10个字节的内存,如果要去free p的话,从第2个字节的内存开始free,free到第10个字节的内存时还没有free完,就再去free没有通过malloc分配的内存,这样一定会出问题 return 0; }
8、结构体中的数组成员和指针成员
struct student{ char name[100]; int age; } struct student_a{ char *name; int age; } int main(){ struct student_a st={NULL,0}; st.age=30; st.name=malloc(100); strcpy(st.name,"myj"); printf("%d %s",st.age,st.name); free(st.name); }
struct student{ char name[100]; int age; } struct student_a{ char *name; int age; } int main(){ struct student_a st={NULL,0}; st.age=30; st.name=malloc(100); strcpy(st.name,"myj"); struct student_a st1=st; printf("%d %s",st1.age,st1.name);//30 乱码 free(st.name); }
struct man{ char *name; int age; } int main(){ struct man m={"tom",20}; printf("name=%s age=%d\n",m.name,m.age);//看起来结果正常 } //但是假如用strcpy改一下name: int main(){ struct man m={"tom",20}; strcpy(m.name,"mike");//程序会挂掉 printf("name=%s age=%d\n",m.name,m.age); } //如果结构体中name的定义改成了char name[100] 就没问题 //name是指针类型时,struct man m={"tom",20}; 执行完了之后m.name就指向了常量"tom"的地址 //strcpy(m.name,"mike");这种操作是去修改一个常量,而常量是不可以修改的,所以会挂掉 //结构体中的成员是指针类型的时候通常通过以下方式赋值 struct man m; m.name=malloc(sizeof(char)*100);//在堆中给name分配100字节 strcpy(m.name,"mike"); free(m.name);//要记得释放
struct student{ char name[100]; int age; } int main(){ //struct student st; 在栈里面 struct student *p=malloc(sizeof(struct student)); //在堆里面 free(p); return 0; }
struct man{ char *name; int age; }; int main(){ struct man *p=malloc(sizeof(struct man));//结构体变量p在堆中 p->name=malloc(sizeof(char)*100);//结构体变量p的成员name也在堆中 strcpy(name,"tom"); p->age=30; printf("name=%s age=%d\n",p->name,p->age); free(p->name);//注意释放的顺序一定得是先释放p的成员name,再释放整个结构体 free(p); }
9、将结构体作为函数参数
struct student{ char name[100]; int age; }; void print_student(struct student s){//一般不把结构类型变量作为函数参数,因为结构类型可能很大,函数调用式,实参和形参在栈内存中同时存在,就会极大浪费栈内存 printf("name=%s age=%d",s.name,s.age); } void set_student(struct student s,const char *name,int age){ strcpy(s.name,name); s.age=age; } int main(){ struct student st={"tom",20}; set_student(st,"mike",100); print_student(st);//打印出来的是tom 就是普通类型的变量 }
//想要改的话只能传递地址: struct student{ char name[100]; int age; }; void print_student(struct student s){ printf("name=%s age=%d",s.name,s.age); } void set_student(struct student *s,const char *name,int age){ strcpy(s->name,name); s->age=age; } int main(){ struct student st={"tom",20}; set_student(&st,"mike",100); print_student(st);//打印出来的是mike }
10、联合体
union A{ int a; int b; }; int main(){ union A a; a.a=10; a.b=20; printf("%d",sizeof(union A));//4 printf("%p %p",&a.a,&a.b);//内存一样 printf("a=%d",a.a);//20 因为共用一块内存 }
联合体union是一个能在同一个存储空间存储不同类型数据的类型
联合体所占的内存长度等于其最长成员的长度,也有叫做共用体
联合体虽然可以有多个成员,但同一时间只能存放其中一种
在上面的例子中联合体内成员a和b是共用一块地址的
11、联合体长度
union A{ unsigned char a; char b; }; int main(){ union A a; a.a=128; //a.b=20; printf("%d",sizeof(union A));//1 以联合体中长度最大的那个成员为准 printf("%p %p",&a.a,&a.b); printf("a=%d",a.a);//128 printf("b=%d",a.b);//-128 }
12、联合体中的指针成员
union A{ char a; char *b; }; int main(){ union A a; a.b=malloc(100);//b指向了一个堆的地址 a.a=10;//但是给a赋值之后b的值成了10,所以下面就free不了了 free(a.b); return 0; }
如果联合体中有指针成员,那么一定要使用完这个指针,并且free指针之后才能使用其他成员
union A{ char a; char *b; }; int main(){ union A a; a.b=malloc(100);//b指向了一个堆的地址 free(a.b); a.a=10;//但是给a赋值之后b的值成了10,所以下面就free不了了 return 0; }
13、枚举类型
enum A{ red,green,black,yellow //相当于定义了4个常量,都是int类型的 值分别为0 1 2 相当于用#define定义的常量 }; int main(){ int color=black;//但是这里不可以给black赋值 枚举是整数常量 printf("%d",red);//0 printf("%d",green);//1 printf("%d",black);//2 printf("%d",yellow);//3 printf("%d",color);//2 }
enum A{ red=5,black,yellow,green }; //现在black变成了6 yellow是7 ...
enum A{ red=5,black=2,yellow,green }; //yellow是3
14、typeof
typedef是一种高级数据特性,它能使某一类型创建自己的名字
typedef char BYTE;//定义了新的数据类型,名字叫BYTE,类型是char
#define BYTE1 char//define只是做了一个语法替换,但是typedef就是定义了一种类型,并不单单是替换 int main(){ BYTE a; BYTE1 a1; a=10; printf("%d",a); return 0; }
#define MAX 10不可以替换成typedef 10 AAA;
struct abc{ int a; char b; }; typedef struct abc A; int main(){ A a;//就不用一直写struct关键字了 }
//================上面的typedef定义的类型还有一种简化写法,而这种写法是define做不到的 typedef struct{ int a; } A2;
char *mystrcat(char *s1,char *s2){ strcat(s1,s2); return s1; } char *test( char *(*p)(char *,char *),char *s1,char *s2 ){ return p(s1,s2); } int main(){ char s1[100]="hello"; char s2[100]="world"; char *s=test(mystrcat,s1,s2); printf("%s",s);//helloworld return 0; }
上面的代码中,test函数的第一个参数是函数指针 但是这个函数的定义太复杂 所以我们一般避免这样写
char *mystrcat(char *s1,char *s2){ strcat(s1,s2); return s1; } typedef char *(*STRCAT)(char *,char *)//STRCAT可以指向两个参数是char*类型,返回值也是char*类型的函数 char *test( STRCAT p, char *s1, char *s2 ){ return p(s1,s2); } int main(){ STRCAT array[10];//定义了一个每个成员的类型都是STRCAT的数组,该数组长度是10 //如果用原始的方法定义的话可读性极差:char *(*p[10])(char *s1,char *s2); 而且在这里typedef是无法用#define替换的 char s1[100]="hello"; char s2[100]="world"; char *s=test(mystrcat,s1,s2); printf("%s",s);//helloworld return 0; }