C语言3——结构体、联合体、枚举、宏定义

时间:2022-06-01 21:29:44

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;
}