在C语言里面,我们学过的数据基础类型有char、short、int、long等,并且还学习了通过数组来构造数据的集合。但是在不同的情况下,我们往往需要一些不同的数据类型组合成一种新的数据结构。
例如:编写一个学生信息管理程序时,就需要存储学生的信息,学生的信息包括学生名称、学号、性别、年龄等数据。学生信息都是不同类型的数据,描述一个学生的信息需要定义(声明)多个不同数据类型的变量,同时还需要一个数组来描述多名学生的信息。
结构体的定义
C语言提供了一种可以自定义的数据类型,用来描述类似学生信息这样的结构化数据。定义结构体的语法为:
struct 结构体名{
类型名1 成员名1;
类型名2 成员名2;
……
类型名n 成员名n;
};
其中,struct是定义结构体的关键字,结构体名是结构体的名称,是合法的C语言标识符。结构体主体使用一对大括号封闭起来,在结构体主体内,可以定义多个结构体成员,每个结构体成员定义方式和变量的声明方式相同,成员之间用分隔符“;”隔开。
例如:学生信息结构体。
struct STUDENT{
// 学生姓名
char name[30];
// 年龄
int age;
};
上面定义了一个名称为STUDENT的结构体,定义结构体名称时,一般全部采用大写,当然也可以按照自己的喜好来命名,全部采用大写可以和变量名称区分开,结构体有name、age两个成员。
定义的结构体只是一种自定义的数据类型,结构体的使用和其它数据类型的使用完全相同,需要在程序中定义一个结构体变量。
例如:下面定义了结构体STUDEN的结构体变量student,STUDEN是结构体,student是结构体变量。
STUDEN student;
也可以在定义结构体时,直接定义一个结构体变量。
struct STUDENT{
// 学生姓名
char name[30];
// 年龄
int age;
} student;
为了访问结构体的成员,C语言提供了成员访问运算符“.”。成员访问运算符是结构体变量名称和要访问的结构成员之间的一个“.”符号,案例8-1演示了访问结构体成员的使用方法。
例1 使用结构体练习
程序清单 sample.c
#include <stdio.h>
#include <string.h>
// 定义学生信息结构体
struct STUDENT{
// 学生姓名
char name[30];
// 年龄
int age;
} student;
// 程序主函数
void main()
{
// 初始化结构体变量student
// 访问结构体成员使用“.”运算符
student.age = 21;
strcpy(student.name,"马汉");
// 输出结构体变量student
printf("姓名:%s\n年龄:%d\n",student.name,student.age);
}
例8-1定义了结构体STUDENT,并声明了结构体变量student,在main()函数内初始化结构体变量student,并输出结构体变量成员信息。
结构体数组
数组的元素也可以是结构类型,因此可以声明结构体数组,结构体数组的每一个元素都是具有相同结构类型的变量。在实际应用中,经常用结构数组来表示具有相同数据结构的数据集合。例如:学习信息管理程序的学生信息、图书管理系统的图书信息等。
声明结构体数组变量的语法和声明其它数据类数组的语法相同,声明结构体数组变量时,一般要指定数组的长度,然后在程序中对数组元素进行赋值。
例如,可以创建一个包含10个学生的结构体数组:
struct STUDENT students[10];
这个students数组包含了10个STUDENT结构体,可以通过数组索引来访问或修改数组内的结构体元素,
你可以通过数组索引来访问和修改这些结构体中的成员。下面的代码设置第一个学生的名字、年龄:
students[0].age = 20;
students[0].name = "张三";
例2 遍历结构体数组元素
程序清单
#include <stdio.h>
#include <string.h>
// 定义学生结构体
typedef struct {
char name[50];
int age;
float score;
} Student;
int main() {
int i;
// 初始化学生数组
struct STUDENT students[3] = {
{"张三", 20},
{"李四", 21},
{"王五", 19}
};
// 遍历学生数组并打印信息
printf("学生信息:\n");
for(i = 0; i < 3; i++) {
printf("姓名:%s,年龄:%d", students[i].name, students[i].age);
}
return 0;
}
案例代码定义了一个名称为STUDENT的结构体,其中包含学生的姓名(一个字符数组)、年龄(一个整数)。然后,在main函数中,创建一个包含3个学生的STUDENT结构体数组,并初始化每个学生的信息。使用一个for循环遍历这个数组,并使用printf函数打印出每个学生的姓名和年龄。
结构体指针
结构体指针是指向结构体变量的指针,通过结构体指针可以间接地访问和修改结构体中的数据成员。
定义一个结构体指针的基本语法如下:
struct 结构体名 *指针名;
例如前面定义的STUDENT结构体,可以这样定义一个指向STUDENT结构体变量的指针:
struct STUDENT *studentPtr;
上面定义的结构体指针变量studentPtr指向一个随机的内存地址,需要对studentPtr进行初始化后,才能使用该结构体指针变量。初始化代码如下:
struct STUDENT *studentPtr;
struct STUDENT student = {"Tom", 20};
studentPtr = &student;
printf("姓名:%s\n年龄:%d\n",studentPtr->name,studentPtr->age);
上面的初始化代码先定义一个STUDENT结构体变量student,然后将student的内存地址赋值给结构体指针变量studentPtr。
结构体指针变量还有一种初始化方法,就是采用内存申请的方法为结构体指针变量直接分配内存,在《内存管理》章节会使用这种方法。案例【8-3】使用了这种方法。
例3 结构体指针应用示例
程序清单
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义学生结构体
typedef struct {
char name[50];
int age;
float score;
} Student;
// 创建学生
Student* createStudent(const char* name, int age, float score) {
Student* student = (Student*)malloc(sizeof(Student));
if (student == NULL) {
printf("内存分配失败!\n");
exit(1);
}
strcpy(student->name, name);
student->age = age;
student->score = score;
return student;
}
// 打印学生信息
void printStudent(Student* student) {
printf("姓名:%s,年龄:%d,成绩:%.2f\n", student->name, student->age, student->score);
}
// 释放学生内存
void freeStudent(Student* student) {
if (student != NULL) {
free(student);
student = NULL;
}
}
int main() {
// 创建学生结构体指针
Student* student1 = createStudent("张三", 20, 85.5);
Student* student2 = createStudent("李四", 21, 92.0);
// 打印学生信息
printStudent(student1);
printStudent(student2);
// 释放学生内存
freeStudent(student1);
freeStudent(student2);
return 0;
}
案例代码定义了三个函数:
createStudent:该函数接受学生的姓名、年龄和成绩作为参数,并动态地分配内存来创建一个新的学生结构体。然后设置结构体的字段并返回指向该结构体的指针。
printStudent:该函数接受一个指向学生结构体的指针作为参数,并打印出学生的信息。
freeStudent:该函数接受一个指向学生结构体的指针作为参数,并释放该结构体所占用的内存。
在main函数内创建了两个学生结构体指针student1和student2,并使用createStudent函数来动态地创建两个学生对象。然后调用printStudent函数来打印这两个学生的信息。最后使用freeStudent函数来释放这两个学生对象所占用的内存。
例3展示了如何使用结构体指针来动态地管理学生对象的创建和销毁。在实际应用中,该方法可以更加灵活地处理大量数据,并且可以在需要时动态地分配和释放内存。
结构体作为函数参数
结构体作为函数参数时,实际上传递的是结构体的一个副本,这意味着函数内对结构体的任何修改都不会影响到原始的结构体。若需要函数对原始结构体进行修改,可以传递结构体指针。
结构体作为函数参数时,存在值传递和指针传递两种方式。
按值传递:在这种情况下,函数接收结构体的一个副本。因此,函数内对结构体的修改不会影响原始的结构体。
指针传递:在这种情况下,函数接收结构体的指针。因此,函数内对结构体的修改会直接影响到原始的结构体。
例4 结构体作为值传递
#include <stdio.h>
typedef struct {
int id;
char name[50];
} Student;
void printStudent(Student s) {
printf("ID: %d, Name: %s\n", s.id, s.name);
}
int main() {
Student stu = {1, "Alice"};
printStudent(stu);
return 0;
}
typedef struct {
int id;
char name[50];
} Student;
void printStudent(Student s) {
printf("ID: %d, Name: %s\n", s.id, s.name);
}
int main() {
Student stu = {1, "Alice"};
printStudent(stu);
return 0;
}
当结构体很大时,按值传递可能会导致大量的数据复制,从而影响性能。
例5 结构体作为指针传递
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
char name[50];
} Student;
void updateStudent(Student *s) {
s->id = 2;
strcpy(s->name, "Bob");
}
int main() {
Student stu = {1, "Alice"};
printf("Before update: ID: %d, Name: %s\n", stu.id, stu.name);
updateStudent(&stu);
printf("After update: ID: %d, Name: %s\n", stu.id, stu.name);
return 0;
}
指针传递效率更高,因为它只传递一个地址,而不是整个结构体。但也带来了额外的风险,因为函数可以修改原始的结构体。