C语言struct结构体的定义和使用(非常详细)
结构体(struct)是 C语言中的一种自定义复合数据类型,它允许将多个不同类型的数据元素(称为成员)组织在一起。
结构体可用于存储具有多个属性的实体,例如,一个人员可能具有姓名、薪水等属性。这些相关属性可以通过结构体在一个数据结构中进行组织和管理,从而提高代码的可读性和维护性。
尽管结构体类型的定义较长,但其实它与 int 类型类似。正如在 int 后填写变量名可以声明一个整型变量一样,在结构体类型后添加变量名也可以声明一个结构体变量。
要访问结构体的各个成员,需要使用成员运算符
接下来,我们为 timmy 变量的各个成员赋值:
有些读者可能会疑惑,为什么需要使用 strcpy() 函数为 timmy.name 赋值,而不是直接将其写成 timmy.name = "Timmy" 呢?
在这段代码中,timmy.name = "Timmy"; 的写法是错误的,因为 timmy.name 是一个字符数组,而不是一个字符指针。字符数组的内容不能直接使用赋值运算符进行赋值。要将字符串常量分配给字符数组,需要使用 strcpy() 函数或其他适当的字符串复制方法。
当然可以,我们只需要给结构体类型定义一个别名,例如:
事实上,我们还可以在最开始时进行结构体类型声明。这样,所有的结构体变量都可以使用该别名进行声明。这相当于先定义一个模板,然后使用该模板生成各个变量:
需要注意的是,如果结构体类型是在某个函数中声明的,那么其别名只能在该函数内部使用,例如:
如果需要在多个函数中使用结构体别名,可以将结构体声明放到函数外面,例如:
例如:
对于第三点,person 结构体成员声明的顺序应依次为 name、gender、height、weight,对应的初始化列表中的初始化值顺序应为 "timmy"、1、170.00、60.00。这点需要严格执行。
以下是正确和错误的结构体变量初始化方式:
因此,我们需要严格按照成员声明的顺序,对初始化列表中的初始化值进行排列。
结构体数组与基本类型数组类似,通过在方括号内填写数组元素的数量来声明。初始化列表也可用于初始化结构体数组,初始化列表中依次填每个结构体的初始化列表,每个结构体的初始化列表之间用逗号分隔。
我们可以通过在方括号内填写下标来访问结构体数组中的元素。同样地,下标也是从 0 开始的。
例如,我们可以声明一个用于存储通信方式的结构体:
现在,我们需要记录每个人员的通信方式。我们可以将上述的结构体添加到人员结构体中,作为其一个成员:
在人员信息中,通信方式结构体为其第五个成员。因此,在人员信息初始化列表的第五个位置处,填写通信方式结构体的初始化列表,即可正确地对结构体进行初始化。
使用 . 加上字段名可以访问通信方式结构体的成员。如果想要访问其内部的成员,可以再次使用 . 加上字段名,例如:
取出结构体指针值的操作,也和之前的操作类似。由于取地址&与取值 * 具有可逆关系,我们可以把指针先转为结构体再使用。
另外,C语言提供了更加方便的写法,即成员间接运算符 ->。(*pTimmy).name 等价于 pTimmy->name,例如:
传值方式会将整个结构体的副本传递给函数,而传指针方式只会传递结构体的地址。
下面程序展示了一个通过传值方式将结构体传递给函数的示例。
在函数中传递结构体时,使用传值方式有以下几个潜在问题:
1) 性能消耗:当结构体较大时,传值方式会将整个结构体的副本传递给函数。这会导致更多的内存和CPU时间被消耗在复制结构体数据上。相比之下,传指针方式只需传递结构体的地址,无论结构体有多大,都只需传递一个指针大小的数据。
2) 原始结构体不会被修改:由于传值方式传递的是结构体的副本,函数内对结构体的修改不会影响到原始结构体。这在某些情况下可能不是你想要的结果,尤其是当你需要在函数内修改原始结构体时。传指针方式可以解决这个问题,因为它传递的是原始结构体的地址。
综上所述,尽管传值方式在结构体较小时是可行的,但在许多情况下,传指针方式更为有效和安全。传指针方式可以减少内存和 CPU 时间消耗,允许在函数内修改原始结构体,并避免潜在的错误。
下面程序展示了一个通过传指针方式将结构体传递给函数的示例。
结构体可用于存储具有多个属性的实体,例如,一个人员可能具有姓名、薪水等属性。这些相关属性可以通过结构体在一个数据结构中进行组织和管理,从而提高代码的可读性和维护性。
尽管结构体类型的定义较长,但其实它与 int 类型类似。正如在 int 后填写变量名可以声明一个整型变量一样,在结构体类型后添加变量名也可以声明一个结构体变量。
struct { char name[20]; int gender; double height; double weight; }timmy;timmy 是由该结构体声明的变量,它包含 4 个成员。
要访问结构体的各个成员,需要使用成员运算符
.
与成员名:
timmy.name; timmy.gender; timmy.height; timmy.weight;
接下来,我们为 timmy 变量的各个成员赋值:
strcpy(timmy.name, "Timmy"); timmy.gender = 1; timmy.height = 170.00; timmy.weight = 60;
有些读者可能会疑惑,为什么需要使用 strcpy() 函数为 timmy.name 赋值,而不是直接将其写成 timmy.name = "Timmy" 呢?
在这段代码中,timmy.name = "Timmy"; 的写法是错误的,因为 timmy.name 是一个字符数组,而不是一个字符指针。字符数组的内容不能直接使用赋值运算符进行赋值。要将字符串常量分配给字符数组,需要使用 strcpy() 函数或其他适当的字符串复制方法。
C语言结构体别名
现在,我们想要定义多个人员信息的结构体变量,例如:struct { char name[20]; int gender; double height; double weight; } timmy; struct { char name[20]; int gender; double height; double weight; } david; struct { char name[20]; int gender; double height; double weight; } jane;上述代码使用结构体定义了 timmy、david、jane 三个变量。由于这三个结构体变量的内部成员都是一致的,每次声明都要写一段很长的代码,这是非常烦琐的,因此我们是否可以只声明一次结构体类型,然后重复使用它呢?
当然可以,我们只需要给结构体类型定义一个别名,例如:
struct person { char name[20]; int gender; double height; double weight; } timmy; struct person david; struct person jane;在这段代码中,第一次声明结构体变量时,我们在 struct 和 { 之间填写了一个结构体别名。如果以后需要使用这种结构,则只需使用 struct 加上该别名即可声明该结构体的变量。
事实上,我们还可以在最开始时进行结构体类型声明。这样,所有的结构体变量都可以使用该别名进行声明。这相当于先定义一个模板,然后使用该模板生成各个变量:
struct person { char name[20]; int gender; double height; double weight; }; struct person timmy; struct person david; struct person jane;
需要注意的是,如果结构体类型是在某个函数中声明的,那么其别名只能在该函数内部使用,例如:
void func1() { struct person{ char name[20]; int gender; double height; double weight; }; struct person timmy; } void func2() { // 别名 person 无法在 func2 中使用 struct person david; }在上述代码中,函数 func1() 声明了一个结构体类型,它的别名为 person。同时,函数 func1() 使用该别名声明了一个结构体变量 timmy。函数 func2() 使用别名 person 声明了另一个结构体变量 david,但是别名 person 无法在函数 func2() 中使用,因此代码会编译报错。
如果需要在多个函数中使用结构体别名,可以将结构体声明放到函数外面,例如:
// 将结构体声明放到函数外 struct person { char name[20]; int gender; double height; double weight; }; void func1() { struct person timmy; } void func2() { struct person david; }
C语言结构体的初始化
初始化结构体是为结构体中的成员分配初始值的过程。在定义结构体变量时,我们可以使用花括号({})为成员分配初始值。成员的初始化顺序应与结构体定义中的成员顺序一致。例如:
struct person timmy = {"timmy", 1, 170.00, 60.00};结构体变量初始化的形式与数组初始化的形式类似。在声明时,结构体变量后跟等号 = 和初始化列表。结构体的初始化列表需要注意以下四点:
- 初始化列表由 {} 包括;
- {} 内是结构体成员需要被初始化的值;
- 初始化值应按照声明结构体成员的顺序依次排列;
- 每个初始化值之间应用逗号(,)分隔。
对于第三点,person 结构体成员声明的顺序应依次为 name、gender、height、weight,对应的初始化列表中的初始化值顺序应为 "timmy"、1、170.00、60.00。这点需要严格执行。
以下是正确和错误的结构体变量初始化方式:
// 正确的初始化方式 struct person timmy = {"timmy", 1, 170.00, 60.00}; // 错误的初始化方式 struct person timmy = {1, "timmy", 170.00, 60.00}; // 类型不一致无法编译通过 struct person timmy = {"timmy", 1, 60.00, 170.00}; // 编译可以通过,但是身高和体重数据被颠倒了在上述代码中:
- 第一个结构体变量的初始化列表顺序正确;
- 第二个结构体变量的初始化列表顺序错误,因为第一个初始化值是一个整数,而不是字符数组,这将导致编译错误;
- 第三个结构体变量的初始化列表可以编译通过,但是身高和体重数据被颠倒了。
因此,我们需要严格按照成员声明的顺序,对初始化列表中的初始化值进行排列。
C语言结构体数组
结构体数组由多个结构体类型的元素组成。在 C语言中,我们可以像处理其他数据类型的数组一样处理结构体数组。例如:struct person { char name[20]; int gender; double height; double weight; }; struct person people[3] = { {"timmy", 1, 170.00, 60.00}, {"david", 1, 175.00, 65.00}, {"jane", 2, 165.00, 55.00} }; for(int i = 0; i < 3; i++) { struct person per = people[i]; printf("%s ", per.name); printf("%d ", per.gender); printf("%.2f ", per.height); printf("%.2f\n", per.weight); }这段代码定义并初始化了一个大小为 3 的结构体数组 people,之后使用 for 循环输出了数组内的值。
结构体数组与基本类型数组类似,通过在方括号内填写数组元素的数量来声明。初始化列表也可用于初始化结构体数组,初始化列表中依次填每个结构体的初始化列表,每个结构体的初始化列表之间用逗号分隔。
我们可以通过在方括号内填写下标来访问结构体数组中的元素。同样地,下标也是从 0 开始的。
C语言结构体嵌套
在 C语言中,结构体可以嵌套,也就是说,一个结构体可以包含另一个结构体作为其成员。这样做可以更好地表示复杂的数据结构。例如,我们可以声明一个用于存储通信方式的结构体:
struct contact { char phone[20]; char email[20]; };
现在,我们需要记录每个人员的通信方式。我们可以将上述的结构体添加到人员结构体中,作为其一个成员:
struct person{ char name[20]; int gender; double height; double weight; struct contact c; };
在人员信息中,通信方式结构体为其第五个成员。因此,在人员信息初始化列表的第五个位置处,填写通信方式结构体的初始化列表,即可正确地对结构体进行初始化。
struct person timmy = { "timmy", 1, 170.00, 60.00, {"130123456678", "timmy@xxx.com"} };
使用 . 加上字段名可以访问通信方式结构体的成员。如果想要访问其内部的成员,可以再次使用 . 加上字段名,例如:
struct person timmy = { "timmy", 1, 170.00, 60.00, {"130123456678", "timmy@xxx.com"} }; printf("%s\n", timmy.c.phone); printf("%s\n", timmy.c.email);这样就可以分别输出timmy的电话号码和电子邮件地址。
C语言结构体指针
在 C语言中,我们可以使用指针指向结构体。结构体指针可以用于间接访问结构体成员,以及将结构体作为函数参数或返回值进行传递,例如:struct person timmy = {"timmy", 1, 170.00, 60.00}; struct person *pTimmy = &timmy;和往常一样,加上星号(*)用于声明一个指针。我们可以使用取地址运算符 & 获取指针。
取出结构体指针值的操作,也和之前的操作类似。由于取地址&与取值 * 具有可逆关系,我们可以把指针先转为结构体再使用。
printf("%s\n", (*pTimmy).name); printf("%d\n", (*pTimmy).gender); printf("%.2f\n", (*pTimmy).height); printf("%.2f\n", (*pTimmy).weight);由于成员运算符
.
的优先级高于取值 *
,为了让取值 *
先运算,必须使用括号将 *pTimmy 包括起来。另外,C语言提供了更加方便的写法,即成员间接运算符 ->。(*pTimmy).name 等价于 pTimmy->name,例如:
printf("%s\n", pTimmy->name); printf("%d\n", pTimmy->gender); printf("%.2f\n", pTimmy->height); printf("%.2f\n", pTimmy->weight);使用成员间接运算符 -> 可以更加简洁地访问结构体指针的成员。
C语言结构体作为函数参数
在 C语言中,我们可以将结构体作为函数参数或返回值进行传递。通常有两种方式:传值和传指针。传值方式会将整个结构体的副本传递给函数,而传指针方式只会传递结构体的地址。
下面程序展示了一个通过传值方式将结构体传递给函数的示例。
#include <stdio.h> #include <string.h> struct person { char name[20]; int gender; double height; double weight; }; void change(struct person per) { strcpy(per.name, "david"); per.gender = 1; per.height = 175.00; per.weight = 65.00; } int main() { struct person timmy = { "timmy", 1, 170.00, 60.00 }; change(timmy); printf("%s\n", timmy.name); printf("%d\n", timmy.gender); printf("%.2f\n", timmy.height); printf("%.2f\n", timmy.weight); return 0; }在上述代码中,函数 change() 被调用时,参数 per 是通过传值方式进行传递的。由于函数内的修改只会影响传入的副本而不是原始结构体,因此函数 change() 对结构体 timmy 所做的修改不会被保留。因此,程序的输出结果如下所示,仍然是原始的 timmy 的数据。
timmy
1
170.00
60.00
在函数中传递结构体时,使用传值方式有以下几个潜在问题:
1) 性能消耗:当结构体较大时,传值方式会将整个结构体的副本传递给函数。这会导致更多的内存和CPU时间被消耗在复制结构体数据上。相比之下,传指针方式只需传递结构体的地址,无论结构体有多大,都只需传递一个指针大小的数据。
2) 原始结构体不会被修改:由于传值方式传递的是结构体的副本,函数内对结构体的修改不会影响到原始结构体。这在某些情况下可能不是你想要的结果,尤其是当你需要在函数内修改原始结构体时。传指针方式可以解决这个问题,因为它传递的是原始结构体的地址。
综上所述,尽管传值方式在结构体较小时是可行的,但在许多情况下,传指针方式更为有效和安全。传指针方式可以减少内存和 CPU 时间消耗,允许在函数内修改原始结构体,并避免潜在的错误。
下面程序展示了一个通过传指针方式将结构体传递给函数的示例。
#include <stdio.h> #include <string.h> struct person{ char name[20]; int gender; double height; double weight; }; void change(struct person *per) { strcpy(per->name, "david"); per->gender = 1; per->height = 175.00; per->weight = 65.00; } int main() { struct person timmy = {"timmy", 1, 170.00, 60.00}; change(&timmy); printf("%s\n", timmy.name); printf("%d\n", timmy.gender); printf("%.2f\n", timmy.height); printf("%.2f\n", timmy.weight); return 0; }在上述代码中,函数 change() 被调用时,参数 per 是通过传指针方式进行传递的。由于函数内修改的是原始结构体,因此函数 change() 对结构体 timmy 所做的修改会得到保留。程序的输出结果如下:
timmy
1
170.00
60.00
C语言结构体实例
编写一个程序,实现一个简单的学生信息管理系统,用于存储和输出学生的姓名、年龄和成绩。要求使用结构体和结构体数组来实现。#include <stdio.h> // 定义学生信息结构体 struct Student { char name[30]; int age; float score; }; // 定义一个函数,用于输出学生信息 void printStudentInfo(const struct Student* student) { printf("姓名:%s\n", student->name); printf("年龄:%d\n", student->age); printf("成绩:%.2f\n", student->score); } int main() { // 创建一个结构体数组,用于存储学生信息 struct Student students[] = { {"Alice", 20, 89.5}, {"Bob", 21, 78.0}, {"Charlie", 22, 95.5}, }; // 获取数组的长度 int numberOfStudents = sizeof(students) / sizeof(students[0]); // 遍历结构体数组,输出学生信息 for (int i = 0; i < numberOfStudents; i++) { printf("学生 #%d\n", i + 1); printStudentInfo(&students[i]); printf("\n"); } return 0; }