• C语言结构体详解


    结构体

    ps:在文章末尾有用实验所用到的所有代码,不同部分在讲解的时候只是代码的节选,一般而言是不能够单独运行的,望周知;

    为什么要使用结构体?

    便于同一个类型的数据传输,运算;在单片机中,运行复杂的程序的时候,一个对数据有很好的储存;

    如何声明结构体:

    struct关键字

    struct Student{
       int id;     //学号
       char *name; //名字
       int age;    //年龄
       float score;//成绩
       //,,,,,  
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    结构体成员

    id,name,age,score

    空结构体
    struct Student{
     
    };
    
    • 1
    • 2
    • 3

    匿名结构体

    struct{
       int id;     //学号
       char *name; //名字
       int age;    //年龄
       float score;//成绩
       //,,,,,  
    }stu1 stu2;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意这里的stu1,stu2都是变量名字,不是类型名

    使用typedef

    struct Student{
       int id;     //学号
       char *name; //名字
       int age;    //年龄
       float score;//成绩
       //,,,,,  
    };
    
    int main(){
    //数据类型 + 变量名 : int a; float b; char c;
       Student stu;
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注意这里用这个

    Student stu;来定义的话,会报错的,原因是什么呢;

    Student不是数据类型,注意一点Student为结构体名;而struct Student才是结构体类型名,所以应该这样定义:

    int main(){
    //数据类型 + 变量名 : int a; float b; char c;
       struct Student stu;
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样一来,每次都要多写一个 struct ,很复杂,所以我们考虑引入 typedef;

    typedef 的作用就如他的名字一样,用于定义新的数据类型 type --> name

    typedef type name
    
    • 1

    如下:

    type struct Student Student;
    
    int main(){
    //数据类型 + 变量名 : int a; float b; char c;
       Student stu;
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    也可以直接在定义结构体的时候,定义:

    typedef struct Student{//这里的Student可以省略
       int id;     //学号
       char *name; //名字
       int age;    //年龄
       float score;//成绩
       //,,,,,  
    }Student;
    
    int main(){
    //数据类型 + 变量名 : int a; float b; char c;
       Student stu;
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结构体的嵌套定义

    比如每个学生都有生日,但是生日又是由年,月,日组成的:

    typedef struct Birthday{
       int year;
       int month;
       int day;
    }Birthday;
    
    typedef struct Student{
       int id;     //学号
       char *name; //名字
       int age;    //年龄
       float score;//成绩
       Birthday birthday;//生日
        
    }Student;
    
    int main(){
    //数据类型 + 变量名 : int a; float b; char c;
       Student stu;
       stu.birthday.year;
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    结构体变量

    声明结构体变量

    就以上面的例子,我们定义的 Student 还有 Birthday 结构体都是没有占用内存的,就好比是 int double float一样,没有定义明确的变量的时候,就相当于是一个蓝图;

    Student stu1,stu2;
    Birthday bir1,bir2;
    
    • 1
    • 2

    结构体变量的赋值

    以上面的 stu1 和 stu2为例:

    Student stu1 = {1001,"xsw",27,100,{2003,10,2}};
    Student stu2 = {1002,"zzz",27,80,{2001,1,24}};
    
    • 1
    • 2

    根据结构体定义的前后顺序进行赋值,如果遇到像 “Birthday” 这样的嵌套,你需要多加一层括号即可;

    访问成员结构体成员

    printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",stu1.id,stu1.name,stu1.age,stu1.score,stu1.birthday.year,stu1.birthday.month,stu1.birthday.day);
    	printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",stu2.id,stu2.name,stu2.age,stu2.score,stu2.birthday.year,stu2.birthday.month,stu2.birthday.day);
    
    • 1
    • 2

    一旦输入stu1. 后面就会跳出相关的选项,包括Birthday这样的嵌套的;需要注意的一点就是,前面的数据类型需要和结构体定义的时候保持一致,避免出错;

    结构体作为函数参数:

    void printStudentinfo(Student stu1)//定义传入的数据类型
    {
    	
       printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",stu1.id,stu1.name,stu1.age,stu1.score,stu1.birthday.year,stu1.birthday.month,stu1.birthday.day);
    
    }
    
    printStudentinfo(stu1);//printStudentinfo(Student stu1)这样传的话会报错哦
    printStudentinfo(stu2);
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    结构体 指针

    声明结构体指针

    为什么需要用到指针?

    刚刚在用函数解决问题的时候,计算机实际上是将stu1复制了一份,然后才传入函数的,如果定义的结构体比较的复杂,那么这样拷贝,然后传入的话,工作量很大(如果需要进行多个结构体的传入哈),所以用到指针,直接传入定义的时候的地址,加快代码的效率;

    定义:

      Student *pStu1 = &stu1;
      Student *pStu2 = &stu2;
    
    • 1
    • 2

    结构体指针访问结构体成员

    结构体指针作为函数参数

    这里直接用调用函数的方式,来解释上述的两个知识点:

    void printStudentinfo2(Student *pstu1)//传入指针类型的结构体
    {
    
       printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",pstu1->id,pstu1->name,pstu1->age,pstu1->score,pstu1->birthday.year,pstu1->birthday.month,pstu1->birthday.day);
    
    }
    
      printStudentinfo2(pStu1);
      printStudentinfo2(pStu2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    实际上和一般的引用方式一致,但是,在声明、调用一个结构体的指针时候,往往用到的是“->”;

    但是如果有嵌套的话,像这里的Birthday的话,Birthday和year,month,day之间还是用“.”连接的;注意体会他们之间的关系;

    如果用中文来解释的话:

    "."一般情况下读作"的”。

    “->”一般读作"指向的结构体的"。

    引用:

    这两个在结构体虽然常用,但有时候很容易让人用混淆了,程序编译不通过。

    1、一般情况下使用“.”,只需要声明一个结构体。这个一般用在结构体变量的使用。

    格式是,结构体类型名+结构体名。

    然后用结构体名加“.”加域名就可以引用域 了,因为自动分配了结构体的内存。

    2、用“->”,则要声明一个结构体的指针,还需要手动开辟一个该结构体的内存空间,

    然后把返回的指针给声明的结构体指针,才能用“->”正确引用。

    这个用在结构体指针变量。如果内存中只分配了指针的内存,没有分配结构体的内存,

    将会导致想要的结构体实际上是不存在。

    这时候用“->”引用自然出错了,因为没有结构体,自然没有结构体的域了。

    3、此外,(*a).b 等价于 a->b。

    4、总结

    "."一般情况下读作"的”。

    “->”一般读作"指向的结构体的"。

    结构体的内存计算

    计算方式

    用sizeof来计算,注意一点的是,如果是指针类型的话,需要查明占用空间以后再进行计算;

    还有就是如果,结构体都是一样的类型的数据,那么直接加和就行,但是如果类型不同的话,一般而言他们的储存空间也不一样,那么就需要按照内存对齐的方式进行计算;

    	printf("the size of char:%d\n",sizeof(char));
    	printf("the size of char*:%d\n",sizeof(char*));
    	printf("the size of int:%d\n",sizeof(int));
    	printf("the size of float:%d\n",sizeof(float));
    	printf("the size of double:%d\n",sizeof(double));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    	printf("the size of Birthday:%d\n",sizeof(Birthday));
    	printf("the size of Student:%d\n",sizeof(Student));
    
    • 1
    • 2

    内存对齐

    说白了就是必须按照储存大小的来倍数进行储存,什么意思呢,就比如说 int 我们都知道它占用4个字节,在储存的时候,就只能在第 4n 的位置开始储存;不能在第二个,第三个位置,,

    举个例子:
    struct S3 
    { 
    	double d; 
    	char c; 
    	int i; 
    };
    struct S4 
    { 
    	char c1; 
    	struct S3 s3; 
    	double d; 
    }; 
    int main()
    {
    	printf("%d\n", sizeof(S4));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    S4 = char + S3 + double;

    S3 = double + char + int

    double:8,char:1,int:4

    8+1=9,那么这个时候再加 int 的话,他需要在第三个位置开始储存的,(也就是前面说的n=4),所以最后就是 9+3+4=16 (12+4)

    S3 = 16

    S4 = 1 + 16 + 8;

    1是任何数的倍数,所以不需要留位置对齐,那么只需要留位置给 8;

    S4 = 1+16+7+8=32 (24+8)

    结构体数组:

    如果要表示很多学生的信息的话,我们不可能想之前一样,stu1,stu2,stu3,这样一直下去,于是结构体数组就体现出他的便携性了

    void printStudentInfo(Student *pStu, int len){
    
       for(int i = 0; i < len; ++i){
           printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",
    	   pStu[i].id,(pStu+i)->name,(pStu+i)->age,(pStu+i)->score,(pStu+i)->birthday.year,(pStu+i)->birthday.month,(pStu+i)->birthday.day);
         }
    
    }
    int main(){
    
    Student students[] = {
    	{1001,"xsw",27,100,{2003,10,2}},
    	{1002,"zzz",27,80,{2001,1,24}}
       };
       
        printStudentInfo(students ,sizeof(students)/sizeof(students[0]));
        printf("\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    很清晰的看到,在调用结构体的时候,你会发现有这样的两种方式可以进行表达;那么这是不是和我们之前说的有矛盾呢

    pStu[i].id, 相当于就是表示的是students[],是数组,所以用点号表示;

    (pStu+i)->name,中的 pStu 单独来写的话,表示的是地址,所以需要用到箭头表示;

    全部代码整理:

    #include 
    
    
    typedef struct Birthday{
       int year;
       int month;
       int day;
    }Birthday;
    
    
    typedef struct Student{
       int id;     //学号4
       char *name; //名字8
       int age;    //年龄4
       float score;//成绩4
       Birthday birthday;//生日12
    }Student;
    
    
    typedef struct A{
       int a;  //4个字节
       char c; //1个字节
       char d[3];//3个字节
    }A;
    
    
    void printStudentinfo1(Student pstu1)//定义传入的数据类型
    {
    	
       printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",pstu1.id,pstu1.name,pstu1.age,pstu1.score,pstu1.birthday.year,pstu1.birthday.month,pstu1.birthday.day);
    
    }
    
    void printStudentinfo2(Student *pstu1)//传入指针类型的结构体
    {
    
       printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",pstu1->id,pstu1->name,pstu1->age,pstu1->score,pstu1->birthday.year,pstu1->birthday.month,pstu1->birthday.day);
    
    }
    
    void printStudentInfo(Student *pStu, int len){
    
       for(int i = 0; i < len; ++i){
           printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",
    	   pStu[i].id,pStu->name,(pStu+i)->age,(pStu+i)->score,(pStu+i)->birthday.year,(pStu+i)->birthday.month,(pStu+i)->birthday.day);
    
    
       }
    
    
    }
    
    int main(){
    //数据类型 + 变量名 : int a; float b; char c;
       Student stu1 = {1001,"xsw",27,100,{2003,10,2}};
       Student stu2 = {1002,"zzz",27,80,{2001,1,24}};
       
       Student students[] = {
    	{1001,"xsw",27,100,{2003,10,2}},
    	{1002,"zzz",27,80,{2001,1,24}}
    
       };
       
    
        printStudentInfo(students ,sizeof(students)/sizeof(students[0]));
        printf("\n");
       
    
       
       Student *pStu1 = &stu1;
       Student *pStu2 = &stu2;
       
    //访问成员变量,选用点操作符号
    
    	printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",stu1.id,stu1.name,stu1.age,stu1.score,stu1.birthday.year,stu1.birthday.month,stu1.birthday.day);
        printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",stu2.id,stu2.name,stu2.age,stu2.score,stu2.birthday.year,stu2.birthday.month,stu2.birthday.day);
    
    //调用函数,来输出成员信息
    
    	printStudentinfo1(stu1);//printStudentinfo(Student stu1)这样传的话会报错哦
    	printStudentinfo1(stu2);
    
    //调用函数,传入指针,来输出成员信息
    
    	printStudentinfo2(pStu1);
    	printStudentinfo2(pStu2);
    	
    	printf("\n");
    	printf("the size of char:%d\n",sizeof(char));
    	printf("the size of char*:%d\n",sizeof(char*));
    	printf("the size of int:%d\n",sizeof(int));
    	printf("the size of float:%d\n",sizeof(float));
    	printf("the size of double:%d\n",sizeof(double));
    
    	printf("the size of Birthday:%d\n",sizeof(Birthday));
    	
    	printf("the size of Student:%d\n",sizeof(Student));
    
    
    	printf("the size of A:%d\n",sizeof(A));
    	
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    操作系统:Windows10

    软件:Devc++

    编译器:TDM-GCC 4.9.2 64-bit Release

    附习题详解:
    typedef的一些常见错误

  • 相关阅读:
    浅谈mysql索引
    会议剪影 | 思腾合力受邀出席第四届长三角文博会并作主题演讲
    c++调用windows vhd接口挂在vhd虚拟盘
    MySQL 的“回表”是什么
    springboot在线教育平台-计算机毕业设计源码68562
    KEITHLEY 2182A + 6220/6221 系列测试系统软件
    面向对象高级
    Java TreeSet类简介说明
    docker-compose多服务器部署kafka集群
    单链表基本练习-删除
  • 原文地址:https://blog.csdn.net/qq_60521516/article/details/126352294