【C语言回顾】结构体

在这里插入图片描述
在这里插入图片描述

#include<GUIQU.h>
int main {
上期回顾: 【C语言回顾】数据在内存中的存储
个人主页:C_GUIQU
专栏:【C语言学习】
return 一键三连;
}

在这里插入图片描述

前言

各位小伙伴大家好!上期小编给大家讲解了C语言中数据在内存中的存储,接下来我们讲解一下结构体!

1. 结构体类型的声明

在C语言中,结构体(struct)是一种用户自定义的数据类型,允许将不同类型的数据项组合成一个单一的复合类型。
结构体类型的声明通常使用 struct 关键字,并定义一个结构体标签,然后是一对大括号,括号内包含一个或多个成员变量。

下面是一个结构体声明的例子:

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

在这个例子中,struct Student 是一个包含三个成员的结构体:一个字符数组 name 用于存储学生的名字,一个整数 age 用于存储学生的年龄,以及一个浮点数 score 用于存储学生的分数。

一旦声明了结构体类型,就可以使用 struct 关键字来声明该类型的变量:

struct Student student1;

此外,C语言允许在声明结构体类型的同时声明变量:

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

在这种情况下,我们没有为结构体类型提供标签,这样的结构体称为匿名结构体,student2 变量就是这种匿名结构体类型的一个实例。由于没有标签,这种形式的结构体声明通常只能使用一次,即在声明的同时创建变量。

结构体成员的访问是通过点操作符(.)来完成的,例如:

strcpy(student1.name, "张三");
student1.age = 20;
student1.score = 92.5;

这些语句分别设置 student1 结构体的 nameagescore 成员。

结构体类型声明:

  1. 结构体标签:在声明结构体时,可以为其指定一个标签,例如 struct Student。这个标签在后续声明结构体变量时使用。
  2. 成员变量:结构体内部可以包含一个或多个成员变量,这些成员可以是基本数据类型(如 int、float、char 等),也可以是其他复合数据类型(如数组、指针、其他结构体等)。
  3. 匿名结构体:如果省略结构体标签,则称为匿名结构体。这种结构体只能在声明时直接创建变量,不能在其他地方使用。
  4. 结构体变量的声明:使用 struct 关键字和结构体标签来声明结构体变量。例如,struct Student student1; 声明了一个名为 student1struct Student 类型的变量。
  5. 结构体成员的访问:使用点操作符(.)来访问结构体成员。例如,student1.age = 20;student1 结构体的 age 成员设置为 20。
  6. 结构体数组:可以声明结构体数组来存储多个结构体变量。例如,struct Student students[100]; 声明了一个包含 100 个 struct Student 类型元素的数组。
  7. 结构体指针:可以使用指针来存储结构体变量的地址。例如,struct Student *ptr = &student1; 声明了一个指向 student1 结构体变量的指针。结构体成员的访问可以使用箭头操作符(->):ptr->age = 20;
  8. 结构体初始化:可以在声明结构体变量时进行初始化。例如:
struct Student student1 = {"张三", 20, 92.5};

或者使用 C99 标准的指定初始化器:

struct Student student1 = {.name = "张三", .age = 20, .score = 92.5};

2. 结构体变量的创建和初始化

在C语言中,结构体变量可以通过多种方式进行创建和初始化。

创建结构体变量

  1. 声明结构体类型后创建变量
    首先,你需要声明一个结构体类型,然后使用该类型来创建变量。
    struct Student {
        char name[50];
        int age;
        float score;
    };
    struct Student student1;
    
  2. 在声明结构体类型的同时创建变量
    你可以在声明结构体类型的同时直接创建变量,无需指定结构体标签。
    struct {
        char name[50];
        int age;
        float score;
    } student2;
    
    这种方式创建的变量 student2 是一个匿名结构体变量,你不能在其他地方使用结构体类型来创建更多变量。
  3. 使用指针创建结构体变量
    你可以使用 malloc 或其他内存分配函数来动态分配结构体变量的内存。
    struct Student *student3 = malloc(sizeof(struct Student));
    
    注意,使用动态分配的内存后,需要手动释放内存以避免内存泄漏。

初始化结构体变量

  1. 在创建时初始化
    你可以在创建结构体变量时直接初始化其成员。
    struct Student student1 = {"张三", 20, 92.5};
    
    这种初始化方式适用于静态分配的结构体变量。
  2. 使用指定初始化器(C99及以后版本)
    C99标准引入了指定初始化器,允许你初始化结构体的特定成员。
    struct Student student1 = {.name = "张三", .age = 20, .score = 92.5};
    
    这种方式可以只初始化结构体中的部分成员,未指定的成员将被自动初始化为0或空字符串。
  3. 动态分配时初始化
    如果你使用 malloc 分配结构体变量,可以在分配后手动初始化成员。
    struct Student *student3 = malloc(sizeof(struct Student));
    strcpy(student3->name, "张三");
    student3->age = 20;
    student3->score = 92.5;
    
  4. 使用函数初始化
    你可以编写一个函数来初始化结构体变量,这在处理复杂结构体时特别有用。
    void initializeStudent(struct Student *student) {
        strcpy(student->name, "张三");
        student->age = 20;
        student->score = 92.5;
    }
    struct Student student1;
    initializeStudent(&student1);
    

注意事项

  • 初始化结构体数组时,每个元素都必须单独初始化。
  • 对于动态分配的结构体,记得在使用完毕后释放内存,以避免内存泄漏。
  • 如果结构体中有指针成员,确保正确地管理指向的内存,避免悬挂指针和内存泄漏。

3. 结构成员访问操作符

在C语言中,结构体成员访问操作符用于访问结构体变量的成员。有两种主要的成员访问操作符:点操作符(.)和箭头操作符(->)。

点操作符(.
点操作符用于直接访问结构体变量的成员。当你有一个结构体变量的实例时,你可以使用点操作符来访问其成员。

struct Student {
    char name[50];
    int age;
    float score;
};
struct Student student1;
// 使用点操作符来访问和赋值成员
strcpy(student1.name, "张三");
student1.age = 20;
student1.score = 92.5;

在上面的例子中,student1 是一个 struct Student 类型的结构体变量。我们使用点操作符来设置其 nameagescore 成员的值。

箭头操作符(->
箭头操作符用于通过指向结构体的指针来访问结构体的成员。当有一个指向结构体的指针时,可以使用箭头操作符来访问其成员。

struct Student {
    char name[50];
    int age;
    float score;
};
struct Student *ptr = &student1;
// 使用箭头操作符来访问和赋值成员
strcpy(ptr->name, "张三");
ptr->age = 20;
ptr->score = 92.5;

在上面的例子中,ptr 是一个指向 struct Student 类型变量的指针。我们使用箭头操作符来设置其所指向的结构体变量的 nameagescore 成员的值。

注意事项

  • 点操作符用于结构体变量本身,而箭头操作符用于结构体指针。
  • 当你使用点操作符时,你不需要解引用指针,因为点操作符会自动处理。
  • 当你使用箭头操作符时,你不需要使用箭头左侧的指针的地址,因为箭头操作符会自动解引用指针。

示例
下面是一个完整的示例,展示了如何使用点操作符和箭头操作符来访问结构体成员:

#include <stdio.h>
#include <string.h>
struct Student {
    char name[50];
    int age;
    float score;
};
int main() {
    // 创建结构体变量
    struct Student student1;
    // 使用点操作符访问和赋值成员
    strcpy(student1.name, "张三");
    student1.age = 20;
    student1.score = 92.5;
    // 创建指向结构体的指针
    struct Student *ptr = &student1;
    // 使用箭头操作符访问和打印成员
    printf("Name: %s\n", ptr->name);
    printf("Age: %d\n", ptr->age);
    printf("Score: %.2f\n", ptr->score);
    return 0;
}

在这个示例中,我们首先使用点操作符来设置 student1 的成员,然后使用箭头操作符通过 ptr 指针来访问和打印这些成员的值。

4. 结构体内存对齐

在C语言中,结构体的内存对齐是指结构体成员在内存中的排列方式,这种排列方式受到编译器和目标平台的对齐要求的影响。理解结构体的内存对齐对于优化内存使用和访问速度非常重要。

内存对齐的原因
内存对齐的主要原因是提高内存访问效率。许多计算机系统的内存访问是按照字(word)边界对齐的,这意味着访问一个对齐的字比访问未对齐的数据快得多。字的大小通常是处理器架构的一个特性,例如,32位架构上的字大小通常是4字节。

对齐规则
结构体的内存对齐通常遵循以下规则:

  1. 成员对齐:结构体的每个成员按照其类型的自然对齐要求进行对齐。例如,int类型通常在4字节边界对齐,而char类型可以在任何字节边界对齐。
  2. 结构体对齐:整个结构体的大小是其最宽成员的宽度的倍数,最宽成员是指占用最大空间的成员。
  3. 填充(Padding):为了满足对齐要求,编译器可能会在结构体的成员之间插入额外的未使用空间,这称为填充。
  4. 数组对齐:数组元素的内存对齐取决于元素类型。整个数组的起始地址必须满足元素类型的对齐要求。

示例
下面是一个结构体的例子,以及它如何在内存中对齐的说明:

struct Example {
    char a;    // 1 byte
    int b;     // 4 bytes
    char c;    // 1 byte
};

在32位系统上,这个结构体的内存布局可能如下:

+------+------+------+------+------+
|  a   | pad  | pad  | pad  |  b   |
+------+------+------+------+------+
|  b   |  b   |  b   |  c   | pad  |
+------+------+------+------+------+
| pad  | pad  | pad  | pad  | pad  |
+------+------+------+------+------+

这里,a 后面有3字节的填充,以确保 b 在4字节边界上对齐。c 后面也有3字节的填充,以确保整个结构体的大小是最大成员 int b 的宽度的倍数,即4字节。

对齐属性
C语言提供了offsetof宏,用于获取结构体成员的偏移量,以及sizeof运算符,用于获取结构体的大小。这些可以帮助理解结构体的内存布局。

printf("Offset of 'a' = %zu\n", offsetof(struct Example, a));
printf("Offset of 'b' = %zu\n", offsetof(struct Example, b));
printf("Offset of 'c' = %zu\n", offsetof(struct Example, c));
printf("Size of struct = %zu\n", sizeof(struct Example));

对齐控制
在C语言中,可以使用pragma pack指令来控制结构体的对齐方式。这可以改变默认的对齐规则,但应该谨慎使用,因为它可能会影响性能。

#pragma pack(push, 1) // 设置最大对齐为1字节
struct Example {
    char a;
    int b;
    char c;
};
#pragma pack(pop) // 恢复之前的对齐设置

在这个例子中,pragma pack指令使得结构体成员之间没有填充,结构体的大小将是所有成员大小的总和。

总结
结构体的内存对齐是由编译器和目标平台决定的,它对程序的性能和内存使用有重要影响。理解对齐规则可以帮助程序员优化数据结构和内存访问模式。

5. 结构体传参

在C语言中,结构体可以作为函数的参数传递。传递结构体参数有几种不同的方式,每种方式都有其特点和用途。

传递结构体值
当你将结构体作为函数参数传递时,默认情况下是按值传递的。这意味着函数接收的是结构体参数的一个副本,对参数的任何修改都不会影响原始结构体。

struct Point {
    int x;
    int y;
};
void printPoint(struct Point p) {
    printf("Point: (%d, %d)\n", p.x, p.y);
}
int main() {
    struct Point myPoint = {3, 4};
    printPoint(myPoint); // 传递结构体值的副本
    return 0;
}

在上面的例子中,printPoint 函数接收 struct Point 的一个副本。在函数内部对 p 的任何修改都不会影响 myPoint

传递结构体指针
另一种方式是传递指向结构体的指针。这样,函数接收的是结构体地址,因此函数内部对结构体的任何修改都会影响原始结构体。

void modifyPoint(struct Point *p) {
    p->x = 10;
    p->y = 20;
}
int main() {
    struct Point myPoint = {3, 4};
    modifyPoint(&myPoint); // 传递结构体的地址
    printf("Modified Point: (%d, %d)\n", myPoint.x, myPoint.y);
    return 0;
}

在上面的例子中,modifyPoint 函数接收一个指向 struct Point 的指针。函数内部对 p 的修改会改变 myPoint 的值。

传递结构体引用
C语言本身不支持引用传递,但可以通过指针实现类似的效果。传递结构体指针实际上就是传递结构体的引用。

void modifyPoint(struct Point *p) {
    p->x = 10;
    p->y = 20;
}
int main() {
    struct Point myPoint = {3, 4};
    modifyPoint(&myPoint); // 传递结构体的引用
    printf("Modified Point: (%d, %d)\n", myPoint.x, myPoint.y);
    return 0;
}

在上面的例子中,modifyPoint 函数通过指针 p 操作原始的 myPoint 结构体。

注意事项

  • 当结构体较大时,按值传递可能会导致性能问题,因为需要复制整个结构体。
  • 传递指针可以避免复制结构体,但需要小心处理内存管理,避免悬挂指针和内存泄漏。
  • 如果函数不需要修改结构体,可以将参数声明为 const 指针,以确保结构体不被意外修改。

示例
下面是一个完整的示例,展示了如何通过值和指针传递结构体:

#include <stdio.h>
struct Point {
    int x;
    int y;
};
void printPoint(struct Point p) {
    printf("Point: (%d, %d)\n", p.x, p.y);
}
void modifyPoint(struct Point *p) {
    p->x = 10;
    p->y = 20;
}
int main() {
    struct Point myPoint = {3, 4};
    printPoint(myPoint); // 按值传递
    modifyPoint(&myPoint); // 按指针传递
    printf("Modified Point: (%d, %d)\n", myPoint.x, myPoint.y);
    return 0;
}

在这个示例中,我们首先通过值传递打印 myPoint,然后通过指针传递修改 myPoint 的值,并再次打印修改后的值。

6. 结构体实现位段

在C语言中,位段(bit field)是一种特殊的结构体成员,它允许你以位为单位来分配内存空间。位段特别有用,当你需要节省内存或者在数据结构中处理二进制数据时。

位段的基本语法
位段的语法是在结构体定义中,使用 : 来指定成员的位数。例如:

struct BitField {
    unsigned int a : 3; // 占用3位
    unsigned int b : 2; // 占用2位
    unsigned int c : 1; // 占用1位
};

在上面的例子中,struct BitField 包含三个位段成员 abc,它们分别占用3位、2位和1位。

位段的存储和对齐
位段通常按照成员声明的顺序存储,从一个字节的开头开始,如果当前字节的剩余空间不足以存储下一个位段,则从下一个字节继续。位段的存储顺序和字节对齐取决于具体的编译器和目标平台。

位段的注意事项

  1. 位段必须是 int 类型的,可以是 signedunsigned
  2. 位段的大小不能超过 int 的大小。例如,在一个32位系统中,位段的大小不能超过32位。
  3. 位段的地址是不可见的,也就是说,你不能取位段的地址。
  4. 位段通常用于存储标志或者枚举值,而不适合用于大量数据的存储。

位段的应用
位段常用于以下场景:

  • 当你需要表示一组布尔标志时,可以使用位段来节省空间。
  • 当你需要处理硬件寄存器时,位段可以帮助你映射寄存器的位域。

示例
下面是一个使用位段的示例:

#include <stdio.h>
structBitFields {
    unsigned int a : 3; // 占用3位
    unsigned int b : 2; // 占用2位
    unsigned int c : 1; // 占用1位
    // 3 + 2 + 1 = 6位,占用1个字节
};

int main() {
    structBitFields bf;
    bf.a = 5; // 101
    bf.b = 3; // 11
    bf.c = 1; // 1
    printf("Size of struct: %zu bytes\n", sizeof(structBitFields));
    printf("BitFields: a = %u, b = %u, c = %u\n", bf.a, bf.b, bf.c);
    return 0;
}

在这个示例中,我们定义了一个包含三个位段的结构体 BitFields。我们设置了这些位段的值,并打印了结构体的大小和位段的值。由于位段的总和是6位,所以结构体占用1个字节的内存。
位段是一种强大的特性,可以让我们以非常精细的方式控制数据的存储和布局。然而,由于位段的实现依赖于具体的编译器和平台,因此在跨平台编程时需要特别小心。

结语

以上就是小编对结构体的详细讲解。
如果觉得小编讲的还可以,还请一键三连。互三必回!
持续更新中~!

在这里插入图片描述

在这里插入图片描述

  • 42
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值