51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

结构体

  1. 结构体的定义和使用 {#1-结构体的定义和使用} ===========================

在前面的章节中学习了数组,它描述的是一组具有相同类型数据的有序集合,用于处理大量相同类型的数据运算。

有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理。

C语言中给出了另一种构造数据类型------结构体。

1.1 定义和初始化 {#1-1-定义和初始化}

声明一个结构体类型的语法是:

|---------------------|----------------------------------------------------| | 1 2 3 4 5 6 | struct 结构体名 { 成员类型 成员名1; 成员类型 成员名2; ... }; |

结构体类型是一种复合类型,说得更直白一些就是自定义类型,通过上面的语法将结构体类型定义出来之后,就可以基于这种类型定义变量了。

定义结构体变量的语法为:

|-----------|--------------------------| | 1 | struct 结构体名 变量名; |

在一个结构体变量内部有若干个成员,访问结构体成员的语法为:

|-----------|--------------------| | 1 | 结构体变量名.成员名 |

其实,定义结构体变量有三种方式,如下图:

  • 先声明结构体类型,再定义相应的变量

  • 在声明类型的同时定义变量

  • 直接定义结构体类型变量(无类型名)

结构体类型和结构体变量关系:

  • 结构体类型:指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元

  • 结构体变量系统根据结构体类型(内部成员状况)为之分配空间

|---------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 结构体类型的定义 struct Student { char name[50]; int age; }; // 先定义类型,再定义变量(常用) struct Student s1 = { "tom", 18 }; // 定义类型同时定义变量 struct Student2 { char name[50]; int age; }s2 = { "lily", 22 }; struct { char name[50]; int age; }s3 = { "jack", 25 }; |

上面的示例代码中,基于三种方式定义了三个结构体变量s1、s2、s3,并通过{}对结构体变量中的各个成员依次进行了初始化。

1.2 成员访问 {#1-2-成员访问}

在一个结构体变量内部有若干个成员,访问结构体成员的语法为:

|-----------|--------------------| | 1 | 结构体变量名.成员名 |

通过上面的方式我们就可以读出结构体变量中各个成员的值,或者给结构体变量的成员进行赋值操作。

|------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <stdio.h> #include <string.h> //结构体类型的定义 struct Student { char name[50]; int age; }; int main() { struct Student s1; // 如果是普通变量,通过点运算符操作结构体成员 strcpy(s1.name, "luffy"); s1.age = 18; printf("s1.name = %s, s1.age = %d\n", s1.name, s1.age); // 结构体变量赋值 struct Student s2 = s1; printf("s2.name = %s, s2.age = %d\n", s2.name, s2.age); return 0; } |

相同类型的两个结构体变量,可以相互赋值,上面代码中把s1变量各个成员的值拷贝到了s2变量的各个成员对应的内存中。s1s2只是成员变量的值一样而已,它们对应的是不同的两块内存。

1.3 嵌套使用 {#1-3-嵌套使用}

由于结构体是一种复合类型,有时候需要通过一个结构体来描述相对复杂的数据,此时可能就需要让多个结构体之间进行嵌套。

|------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 | #include <stdio.h> #include <string.h> struct Person { char name[20]; char sex; // M-Man, W-Woman }; struct Student { int id; struct Person info; }; int main() { struct Student s; s.id = 1; s.info.sex = 'W'; strcpy(s.info.name, "Lucy"); struct Student s1 = { 2, "Tom", 'M'}; printf("s.id = %d, s.info.name=%s, s.info.sex=%c\n", s.id, s.info.name, s.info.sex); printf("s1.id = %d, s1.info.name=%s, s1.info.sex=%c\n", s1.id, s1.info.name, s1.info.sex); return 0; } |

在上面的示例代码中,每个Student变量中都包含一个Person变量,二者之间是父子节点的关系。

  1. 结构体数组和指针 {#2-结构体数组和指针} =========================

2.1 结构体数组 {#2-1-结构体数组}

如果想要记录某个学生的信息可以使用结构体,如果想要记录班级或者小组的学生信息就需要定义多个结构体变量,此时就可以使用数组。

下面的示例程序中使用结构体数组对小组中的学生成绩进行了统计:

|------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 | // 统计学生成绩 struct Student { int num; char name[20]; char sex; float score; }; int main() { //定义一个含有5个元素的结构体数组并将其初始化 struct Student stu[5] = { { 101, "Li ping", 'M', 45 }, { 102, "Zhang ping", 'M', 62.5 }, { 103, "He fang", 'W', 93 }, { 104, "Cheng ling", 'W', 87 }, { 105, "Wang ming", 'M', 58 } }; int loser = 0; float avg, total = 0; for (int i = 0; i < 5; i++) { total += stu[i].score; // 计算总分 if (stu[i].score < 60) { loser += 1; // 统计不及格人的分数 } } printf("total = %.2f\n", total);// 打印总分数 avg = total / 5; // 计算平均分数 printf("average = %.2f, loser count = %d\n", avg, loser); for (int i = 0; i < 5; i++) { printf(" name = %s, score = %.2f\n", stu[i].name, stu[i].score); } return 0; } |

结构体数组的使用方式和非结构体数组相同,都是通过[]对数组中的元素进行访问,只不过结构体数组元素更加复杂,还需要使用该元素再通过.的方式对结构体成员进行数据的读或写操作。

2.2 结构体指针 {#2-2-结构体指针}

2.2.1 普通结构体指针 {#2-2-1-普通结构体指针}

如果定义了一个结构体变量,我们可以再定义一个相同类型的指针,使用这个指针指向结构体变量的地址,示例代码如下:

|---------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include<stdio.h> //结构体类型的定义 struct Student { char name[50]; int age; }; int main() { struct Student s1 = { "lily", 18 }; // 如果是指针变量,通过->操作结构体成员 struct Student* p = &s1; printf("p->name = %s, p->age=%d\n", p->name, p->age); printf("(*p).name = %s, (*p).age=%d\n", (*p).name, (*p).age); return 0; } |

关于结构体内部成员的访问有两种情况:

  • 基于结构体变量进行访问,语法为:变量名.成员名
  • 基于结构体指针进行访问,语法为:指针名->成员名

如果对结构体指针通过*进行了解引用(比如代码中的 *p),那么它们的组合将不再是指针,在访问结构体成员的时候需要通过.进行操作(比如:(*p).name

2.2.2 堆区结构体指针 {#2-2-2-堆区结构体指针}

如果在函数体内部定义结构体变量,那么该变量对应的内存位于栈区,如果在函数体外部定义结构体变量,那么该变量对应的内存位于全局数据区,除此之外我们还可以在函数体内部使用malloc动态申请一块堆区内存,并将这块内存的地址保存到一个指针变量中。

下面的示例代码给大家演示了如何给结构体动态申请一块堆内存:

|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 | #include <stdio.h> #include <string.h> #include <stdlib.h> //结构体类型的定义 struct Student { char name[50]; int age; }; int main() { struct Student* p; p = (struct Student*)malloc(sizeof(struct Student)); // 如果是指针变量,通过->操作结构体成员 strcpy(p->name, "Monkey·D·Luffy"); p->age = 22; printf("p->name = %s, p->age = %d\n", p->name, p->age); printf("(*p).name = %s, (*p).age = %d\n", (*p).name, (*p).age); free(p); p = NULL; return 0; } |

在C语言中,通过malloc/calloc/realloc函数动态申请的堆内存是不会自动释放的,在使用完毕之后需要手动进行释放,释放函数是free

2.2.3 结构体嵌套指针 {#2-2-3-结构体嵌套指针}

在定义结构体的时候,结构体成员可以是基础数据类型,可以是结构体类型,也可以是指针。如果是指针,它自身占4字节或者8字节的存储空间并且指针指向的位置是随机的,在使用的时候需要让它指向一个正确的、合法的地址。

下面是一段结构体嵌套指针的示例代码:

|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 | #include <stdio.h> #include <string.h> #include <stdlib.h> //结构体类型的定义 struct Student { char* name; // 一级指针 int age; }; int main() { char* str = "Monkey·D·Luffy"; struct Student* p = (struct Student*)malloc(sizeof(struct Student)); p->name = (char*)malloc(strlen(str) + 1); strcpy(p->name, "Monkey·D·Luffy"); p->age = 22; printf("p->name = %s, p->age = %d\n", p->name, p->age); printf("(*p).name = %s, (*p).age = %d\n", (*p).name, (*p).age); if (p->name != NULL) { free(p->name); p->name = NULL; } if (p != NULL) { free(p); p = NULL; } return 0; } |

在上面的程序中,一共手动申请了两块堆内存:

  • 结构体指针 p,通过malloc操作,该指针指向的内存大小为4+4=8字节
    • age 整形变量,4字节
    • name 整形指针,Win32平台4字节(Win64平台8字节),不能存储数据
  • 结构体成员 p->name,如果想要通过指针来存储数据,那么指针必须要指向一块有效的存储空间,这块存储空间默认是没有的,所以需要再通过malloc分配出这块堆内存,并把得到的地址保存到p->name指针中。

所以在释放内存的时候,需要对这两个指针依次进行判断,然后再调用free函数,手动的将堆内存还给操作系统。

  1. 结构体做函数参数 {#3-结构体做函数参数} =========================

3.1 普通结构体变量 {#3-1-普通结构体变量}

在定义函数的时候,可以将形参指定为结构体类型,示例代码如下:

|------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 | #include <stdio.h> #include <string.h> // 结构体类型的定义 struct Student { char name[50]; int age; }; //函数参数为结构体普通变量 void initStudent(struct Student tmp) { strcpy(tmp.name, "robin"); tmp.age = 35; printf("tmp.name = %s, tmp.age = %d\n", tmp.name, tmp.age); } int main() { struct Student s = { "Luffy", 20 }; initStudent(s); printf("s.name = %s, s.age = %d\n", s.name, s.age); return 0; } |

程序输出的结果如下:

|-------------|-------------------------------------------------------------------| | 1 2 | tmp.name = robin, tmp.age = 35 s.name = Luffy, s.age = 20 |

上面程序中定义了一个函数initStudent(struct Student tmp),通过该函数来设置结构体变量中的初始值,但是通过输出的信息可以看出外部变量s中的值并没有被修改,原因是这样的:结构体变量的传递方式是值传递,值传递过程中会发生数据的拷贝,在 initStudent 函数内部修改是形参对应的内存中的值,对实参变量的内存数据没有任何影响,想要解决这个问题需要将参数修改为结构体指针类型。

3.2 结构体指针变量 {#3-2-结构体指针变量}

修改一下上面的代码,将参数的类型由结构体类型替换为结构体指针类型:

|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 | #include <stdio.h> #include <string.h> // 结构体类型的定义 struct Student { char name[50]; int age; }; //函数参数为结构体普通变量 void initStudent(struct Student* tmp) { strcpy(tmp->name, "robin"); tmp->age = 35; printf("tmp.name = %s, tmp.age = %d\n", tmp->name, tmp->age); } int main() { struct Student s = { "Luffy", 20 }; printf("s.name = %s, s.age = %d\n", s.name, s.age); initStudent(&s); printf("s.name = %s, s.age = %d\n", s.name, s.age); return 0; } |

程序输出的结果如下:

|---------------|----------------------------------------------------------------------------------------------| | 1 2 3 | s.name = Luffy, s.age = 20 tmp.name = robin, tmp.age = 35 s.name = robin, s.age = 35 |

可以看到,调用了initStudent函数之后,实参s中的数据确实被修改了,因为函数参数传递的是地址,在函数体内部形参指针指向的内存和实参变量对应的内存是同一块,通过这种方式就可以在函数体内部来修改函数体外部的变量数据了。

如果函数的形参是指针类型,我们还可以使用const关键字对其进行修饰,这样就可以限制使用者只能通过形参读内存数据,而不能修改内存数据。

|------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // 结构体类型的定义 struct Student { char name[50]; int age; }; void func1(struct Student* const p) { p->age = 10; // ok p = NULL; // error } void func2(const struct Student* p) { p = NULL; // ok p->age = 10; // error } void func3(const struct Student* const p) { p = NULL; // error p->age = 10; // error } |

在上面的示例代码中一共定义了三个函数,每个函数的参数类型都不同:

  1. void func1(struct Student* const p)
    • const 在 * 号右侧,修饰指针
    • 指针指向的地址不可变,指向的地址中的值可变
  2. void func2(const struct Student* p)
    • const 在 * 号左侧,修饰指针指向的内存地址中的值
    • 指针指向的地址可变,指向的地址中的值不可变
  3. void func3(const struct Student* const p)
    • const 在 * 号左右两侧,修饰指针和指针指向的内存地址中的值
    • 指针指向的地址不可变,指向的地址中的值也不可变

3.3 结构体数组 {#3-3-结构体数组}

除了使用结构体指针,结构体数组名也可以作为函数的形参,示例代码如下:

|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 | #include <stdio.h> #include <string.h> //结构体类型的定义 struct Student { char name[50]; int age; }; // void initStudent(struct Student tmp[100], int n) // void initStudent(struct Student* tmp, int n) void initStudent(struct Student tmp[], int n) { int i = 0; for (i = 0; i < n; i++) { sprintf(tmp->name, "name-%d", i); tmp->age = 20 + i; tmp++; } } int main() { struct Student s[3] = { 0 }; int i = 0; int size = sizeof(s) / sizeof(s[0]); initStudent(s, size); //数组名传递 for (i = 0; i < size; i++) { printf("%s, %d\n", s[i].name, s[i].age); } return 0; } |

在上面的代码中将initStudent函数的形参指定为了结构体数组类型,其实它有两种书写形式:

  1. void initStudent(struct Student tmp[], int n)
    • 不指定数组的容量,通过实参进行推导
  2. void initStudent(struct Student tmp[100], int n)
    • 指定实参的容量,数组大小需要和实参数组的大小一致

但是,不论将数组参数指定为何种形式,最终都将退化为指针,也就是这种形式:

|-----------|-------------------------------------------------------| | 1 | void initStudent(struct Student* tmp, int n); |

因此,在函数体内部是不能通过sizeof运算符计算出指针指向的内存所占用的内存地址的大小的,如果需要在函数体内部使用这个值,则需要额外添加参数来进行数据的传递,这就是参数n的由来。

赞(1)
未经允许不得转载:工具盒子 » 结构体