C语言结构体如何定义与使用?

99ANYc3cd6
预计阅读时长 23 分钟
位置: 首页 C语言 正文

struct 是 C 语言中一种非常重要的用户自定义数据类型(User-Defined Data Type, UDT),它允许你将不同类型的数据项组合成一个单一的类型,这就像创建一个新模板,然后用这个模板来声明变量,这些变量可以包含多个不同类型的成员。

c 语言 structure
(图片来源网络,侵删)

为什么需要结构体?

假设你要描述一个学生,学生有姓名、年龄、学号和成绩等信息。

  • name 是一个字符串(字符数组)。
  • age 是一个整数。
  • id 是一个整数。
  • score 是一个浮点数。

如果你不使用结构体,你可能需要定义多个独立的变量:

char name[50];
int age;
int id;
float score;

这样做的缺点很明显:

  1. 数据分散:所有关于一个学生的信息都散落在不同的变量中,难以管理。
  2. 传递不便:如果你想将一个学生的所有信息传递给一个函数,你必须传递所有这些变量,非常麻烦。
  3. 逻辑不清晰:代码无法直观地表现出这些变量是属于同一个“学生”实体的。

结构体就是为了解决这些问题而生的,它可以将这些相关的数据项打包成一个逻辑单元。

c 语言 structure
(图片来源网络,侵删)

如何定义和声明结构体?

1 定义结构体类型

你需要定义一个结构体的“蓝图”或“模板”,这通常使用 struct 关键字完成。

语法:

struct 结构体名 {
    数据类型 成员1;
    数据类型 成员2;
    ...
};

示例:定义一个 Student 结构体

struct Student {
    char name[50];
    int age;
    int id;
    float score;
};

这里的 struct Student 就是一个新的数据类型,就像 intfloat 一样。

2 声明结构体变量

定义好结构体类型后,你就可以用它来声明变量了。

语法:

struct 结构体名 变量名;

示例:

struct Student student1; // 声明一个名为 student1 的 Student 结构体变量
struct Student student2, student3; // 声明两个变量

你也可以在定义结构体类型的同时声明变量:

struct Student {
    char name[50];
    int age;
    int id;
    float score;
} student1, student2; // 在这里直接声明了两个变量

如何访问和修改结构体成员?

要访问结构体变量中的成员,我们使用成员访问运算符,也称为点运算符 ()。

语法:

结构体变量名.成员名

示例:

// 给 student1 的成员赋值
strcpy(student1.name, "张三"); // 字符串不能直接用 = 赋值,需要 strcpy
student1.age = 20;
student1.id = 1001;
student1.score = 95.5;
// 访问并打印 student1 的成员
printf("姓名: %s\n", student1.name);
printf("年龄: %d\n", student1.age);
printf("学号: %d\n", student1.id);
printf("成绩: %.2f\n", student1.score);
// 修改成员的值
student1.score = 98.0;
printf("修改后的成绩: %.2f\n", student1.score);

结构体的初始化

你可以在声明结构体变量的同时对其进行初始化,就像初始化数组一样。

示例:

struct Student student4 = {"李四", 21, 1002, 88.5};
// 如果只初始化部分成员,其余成员会被自动初始化为 0 (对于数值类型) 或 '\0' (对于字符)
struct Student student5 = {"王五", 22}; // age=22, id=0, score=0.0

结构体数组

你可以创建一个结构体数组,其中每个元素都是一个结构体变量。

示例:

struct Student class[3]; // 定义一个包含3个学生的数组
// 初始化数组
struct Student class[3] = {
    {"赵一", 19, 1011, 92.0},
    {"钱二", 20, 1012, 85.5},
    {"孙三", 21, 1013, 91.0}
};
// 访问数组中的结构体成员
printf("第一个学生的姓名: %s\n", class[0].name);
printf("第二个学生的成绩: %.2f\n", class[1].score);
// 遍历结构体数组
for (int i = 0; i < 3; i++) {
    printf("学生 %d: %s, 成绩: %.2f\n", i+1, class[i].name, class[i].score);
}

结构体指针

你可以声明一个指向结构体的指针,这在进行函数传参和动态内存分配时非常有用。

语法:

struct Student *ptr;

通过结构体指针访问成员,有两种方式:

  1. 使用 -> 运算符(推荐) ->(*ptr). 的缩写,专门用于通过指针访问结构体成员,更简洁。

  2. 使用 运算符 需要先对指针进行解引用 (*ptr)

示例:

struct Student student6 = {"周六", 23, 1021, 76.5};
struct Student *ptr_student = &student6; // 指针指向 student6 的地址
// 使用 -> 访问成员
printf("通过指针访问姓名: %s\n", ptr_student->name);
printf("通过指针访问年龄: %d\n", ptr_student->age);
// 使用 . 访问成员 (需要解引用)
printf("通过解引用指针访问成绩: %.2f\n", (*ptr_student).score);
// 通过指针修改成员
ptr_student->score = 80.0;
printf("修改后的成绩: %.2f\n", student6.score); // 原变量也被修改了

结构体作为函数参数

结构体可以作为函数的参数传递,主要有两种方式:

a) 传值调用

将整个结构体变量的副本传递给函数,函数内部对成员的修改不会影响原始变量,这种方式对于大型结构体来说效率较低,因为需要复制大量数据。

示例:

// 函数:打印学生信息
void printStudent(struct Student s) {
    printf("--- 学生信息 ---\n");
    printf("姓名: %s\n", s.name);
    printf("年龄: %d\n", s.age);
    printf("成绩: %.2f\n", s.score);
    printf("-----------------\n");
}
int main() {
    struct Student student7 = {"吴七", 24, 1022, 99.9};
    printStudent(student7); // 传递的是 student7 的副本
    return 0;
}

b) 传址调用(指针传递)

将指向结构体的指针传递给函数,函数通过指针可以直接访问和修改原始结构体,这种方式更高效,特别是对于大型结构体。

示例:

// 函数:修改学生成绩
void updateScore(struct Student *s, float new_score) {
    s->score = new_score; // 通过指针修改原始数据
}
int main() {
    struct Student student8 = {"郑八", 25, 1023, 65.0};
    printf("修改前的成绩: %.2f\n", student8.score);
    updateScore(&student8, 75.5); // 传递地址
    printf("修改后的成绩: %.2f\n", student8.score);
    return 0;
}

结构体的大小与内存对齐

sizeof 运算符可以返回一个结构体变量所占用的字节数。

printf("Student 结构体的大小: %zu 字节\n", sizeof(struct Student));

你可能发现,sizeof(struct Student) 的结果不一定等于 sizeof(name) + sizeof(age) + sizeof(id) + sizeof(score),这是因为编译器会进行内存对齐

内存对齐的目的:

  • 提高性能:CPU 读取内存时,通常是按字或双字等特定大小的块进行的,对齐后的数据可以被一次性读取,避免了多次访问,从而提高效率。
  • 硬件要求:某些体系结构的 CPU 要求特定类型的数据必须对齐到特定的地址上,否则会引发硬件异常。

对齐规则(简化的通用规则):

  1. 结构体的第一个成员从偏移量 0 的地址开始存放。
  2. 其他成员变量存放的地址,必须是该成员大小的整数倍(对齐数)。
  3. 结构体的总大小,必须是其中最大成员大小的整数倍。
  4. 如果嵌套了结构体,嵌套的结构体对齐到自己的成员大小的整数倍处,结构体的整体大小是所有成员(包括嵌套结构体的成员)中最大大小的整数倍。

示例:

struct MyStruct {
    char c;      // 1 字节
    int i;       // 4 字节
    short s;     // 2 字节
};
// 内存布局 (假设 int 对齐数为 4):
// [c] (偏移 0)
// [  填充3字节  ] (偏移 1-3)
// [i] (偏移 4)
// [s] (偏移 8)
// [ 填充2字节 ] (为了让总大小是4的倍数)
// 总大小 = 12 字节
// sizeof(c) + sizeof(i) + sizeof(s) = 1 + 4 + 2 = 7,但实际是12。
// 为了节省内存,可以调整成员顺序:
struct OptimizedStruct {
    char c;      // 1
    short s;     // 2
    int i;       // 4
};
// 内存布局:
// [c] (偏移 0)
// [s] (偏移 2)
// [i] (偏移 4)
// 总大小 = 8 字节 (4的倍数)

实践建议:将占用空间小的成员放在一起,大的成员放在一起,可以减少内存填充,节省空间。


结构体与 typedef

使用 typedef 可以为结构体类型定义一个更简短、更易读的别名,避免每次声明变量时都写 struct

语法:

typedef struct 结构体名 {
    // 成员列表
} 结构体别名;

示例:

// 定义 Student 类型,并起别名为 Stu
typedef struct Student {
    char name[50];
    int age;
    float score;
} Stu;
// 现在可以直接使用 Stu 来声明变量,而无需 struct
Stu student9 = {"陈九", 26, 88.0};
Stu *ptr_stu = &student9;
printf("学生姓名: %s\n", student9.name);
printf("学生成绩: %.2f\n", ptr_stu->score);

特性 描述 示例
定义 创建一个包含多种数据类型的复合数据类型。 struct Point { int x; int y; };
声明 根据结构体类型创建变量。 struct Point p1;
成员访问 使用点运算符 () 访问成员。 p1.x = 10;
指针访问 使用箭头运算符 (->) 通过指针访问成员。 struct Point *ptr = &p1; ptr->y = 20;
初始化 在声明时为成员赋初值。 struct Point p2 = {0, 0};
数组 创建结构体数组。 struct Point points[10];
函数参数 可以传值(副本)或传址(指针)。 void func(struct Point p); / void func(struct Point *p);
typedef 为结构体类型创建别名,简化代码。 typedef struct Point Pt; Pt p1;
内存对齐 编译器为了性能和硬件要求进行的内存布局优化。 sizeof(struct Point) 可能大于成员大小之和。

结构体是 C 语言进行数据组织和建模的基石,掌握它对于编写复杂、结构化的程序至关重要。

-- 展开阅读全文 --
头像
C语言setpencolor函数如何使用?
« 上一篇 01-30
dede limit 0 1是什么?如何使用?
下一篇 » 01-30
取消
微信二维码
支付宝二维码

目录[+]