C 结构体

结构体

什么是结构体

C 语言允许用户自己指定这样一种数据结构,它由不同类型的数据组合成一个整体,这些组合在一个整体中的数据是互相联系的,这样的数据结构称为结构体。

结构体格式

    struct 结构体名  //结构体名,用作结构体类型的标志,它又称 结构体标记,
    {
        数据类型 成员变量;  //也可以成员列表称为域表,第一个成员也称为结构体中的一个域。

    };

    
    // 1。结构体名可无,这样为无名结构体
    // 2.前面只是声明了一个结构体类型,不分配实际内存单元,在定义结构体变量时分配内存。

定义结构体变量

先声明后定义

//声明一个学生结构体
struct students
{
    char name[32];
    char sex;
    int age;
    int number;

};
//定义学生类型的结构体变量
//类型为`struct students`
struct students students_1;
struct students students_2;

声明的同时定义

struct students
{
    char name[32];
    char sex;
    int age;
    int number;

}students_1, students_2;

无名结构体的声明与定义(C11标准)

struct { // 无名结构体
    int x;
    char y;
} z;   // 直接定义变量 z
//结构体类型没有名字,只能在声明时定义变量(如 z),后续无法再创建新变量(因为没有类型名)。


typedef struct { // 无名结构体 + typedef
    int width;
    int height;
} Rect; // Rect 是类型别名
//通过 typedef 为无名结构体赋予别名(如 Rect),后续可通过别名创建变量。


//嵌套无名结构体
struct Data {
    int id;
    struct { // 匿名结构体
        int x;
        int y;
    }; // 注意:此处没有成员名
};

int main() {
    struct Data d;
    d.id = 1;
    d.x = 10; // 直接访问匿名结构体的成员
    d.y = 20;
    printf("id=%d, x=%d, y=%d\n", d.id, d.x, d.y);
    return 0;
}


// 外层结构体 Data 可以直接访问匿名结构体的成员 x 和 y。
// 常用于简化嵌套结构体的访问。

// 用途: 1.一次性变量:当结构体仅需定义一次变量时,无需命名类型。
//       2.简化嵌套访问:通过 C11 的匿名结构体,减少代码冗余。     
//       3.联合体(Union)中的匿名结构体:结合 union 实现数据的不同解释方式

//PS:    1.类型不可复用:无名结构体没有类型名,无法直接作为函数参数或返回值(需结合 typedef)。
//       2.C11 兼容性:匿名结构体是 C11 标准引入的特性,需确保编译器支持(如 GCC/Clang 需开启 -std=c11)。
//       3.代码可读性:过度使用无名结构体可能降低代码可读性,需权衡使用场景。

结构体嵌套

struct score 
{
    int math;
    int chinese;

}

struct students
{
    char name[32];
    char sex;
    int age;
    int number;
    struct score stu_score;  //嵌套定义一个 struct score 类型的变量
                             //成员名可以与程序中的变量名相同,二者不代表同一对象。
}student1, student2;

结构体变量引用

  • 不能将一个结构体变量作为一个整体进行输入和输出。只能对结构体变量中的各个成员分别进行输入输出。
  • 引用结构体变量中的成员的方式为:
    结构体变量名.成员名

    struct students student1;
  • 如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低一级的成员。只能对最低的成员进行赋值或存取以及运算。
    student1.sex;
    student1.stu_score.math;
    //结构体声明参考结构体嵌套
  • 对结构体变量的成员可以像普通变量一样进行各种运算
    student2.score = student1.score;
    sum = student1.score + student2.score;
    student1.age ++;
    ++ student1.age;

结构体变量初始化

//在定义时直接初始化
struct score 
{
    int math;
    int chinese;

}

struct students
{
    char name[32];
    char sex;
    int age;
    int number;
    struct score stu_score;  //嵌套定义一个 struct score 类型的变量
                             
}student1 = {"LIMING", 'M', 18, 123456, {89, 90}};
struct students student2 = {"LIHUA", 'M', 18, 123457, {81, 95}};


//成员变量乱序初始化
struct students student3={.name="李白".number=124553, .age=46, .sex='M',\
                             .stu_score.math = 99, .stu_score.chinese = 99};

结构体数组

  • 顾名思义,结构体数组就是存放多个相同结构体类型的数组结构。结构体数组与以前介绍过的数据值型数组不同之处在于每个数组元素都一个结构体类型的数据,它们分别包括各个成员(分量)项。

定义结构体数组

struct students
{
    char name[32];
    char sex;
    int age;
    int number;
                             
};
struct students stu[3]; //定义了一个存储3个 struct students 类型结构体的变量 stu


//也可用以下方法定义
struct student
{
    char name[32];
    ....

}stu[3];struct

{
    char name[32];
     ...
}stu[3];


结构体数组初始化

struct student
{
    char name[20];
    char sex;
    int age;
    int number;

}stu[3] = {{"ZhangSan", 'M', 18, 123123},
            {"LiSi", 'M', 18, 12312314},
            {"WangWu", 'M', 18, 12412412}};


//定义数组时,元素个数可以不指定,写成以下形式:
    stu[] = {{...},{...},{...}};   
//编译时,系统会根据给出初值的结构体常量的个数来确定数组元素的个数。

结构体指针

  • 结构体变量的指针就是该变量所占据的内存段的起始地址,指针变量也可以用来指向结构体数组中的元素。
    struct student
    {
        char name[20];
        char sex;
        int age;
        int number;

    };
    struct student a = {"李白"'M', 20, 100123}struct student *p = &a; //p的类型是struct student *占8/4字节
    
//指针可以使用 -> 指向成员变量,如:
    p->age = 21;  //等价于 a.age = 21;

//还有如下写法:
    (*P).age = 21;
    (&a)->age = 21;



//结构体指针指向结构体数组的地址
    struct student arr[3] = {{"ZhangSan", 'M', 18, 123123},
                            {"LiSi", 'M', 18, 12312314},
                            {"WangWu", 'M', 18, 12412412}};

    struct student *p = arr;
//此时可以用如下方式访问变量
    arr[i].age = 21;
    p[i].gae = 21;
    (p + i)->age = 21;
    *(p + i).age = 21;

结构体大小及地址

  • 在 C 语言中,结构体的大小(即占用的内存空间)和地址分配规则由成员变量的类型、顺序及编译器的内存对齐规则共同决定。
  • 结构体的大小并非简单等于所有成员变量大小的总和,而是遵循内存对齐规则,目的是提高 CPU 访问内存的效率。以下是结构体大小计算规则:
      1. 对齐基准(Alignment),基本数据类型的对齐基准通常等于其自身大小。结构体成员的对齐要求等于其内部最大成员的对齐基准。
      1. 成员地址偏移,每个成员的地址必须是其对齐基准的整数倍。如果当前偏移地址不满足对齐要求,编译器会在前一个成员后插入填充字节(Padding)。
      1. 结构体总大小,结构体的总大小必须是其最大成员对齐基准的整数倍。如果最后一个成员后仍有空间不足对齐,编译器会插入填充字节。

  • 地址分配规则
    • 起始地址:结构体变量的地址等于其第一个成员的地址。
    • 成员顺序:成员按声明顺序依次分配地址,遵循对齐规则。
    • 嵌套结构体:嵌套的结构体成员的对齐基准是其内部的最大对齐基准。
//  示例1
struct Example1 {
    char a;      // 1 字节
    int b;       // 4 字节(对齐基准为4)
    short c;     // 2 字节(对齐基准为2)
};

Offset 0: char a (1 字节)
Offset 1-3: 填充 3 字节(因为 int b 需要从 4 的倍数地址开始)
Offset 4-7: int b (4 字节)
Offset 8-9: short c (2 字节)
Offset 10-11: 填充 2 字节(总大小需是最大对齐基准 4 的整数倍)


// 示例2
struct Example2 {
    int b;       // 4 字节
    short c;     // 2 字节
    char a;      // 1 字节
};

Offset 0-3: int b (4 字节)
Offset 4-5: short c (2 字节)
Offset 6: char a (1 字节)
Offset 7: 填充 1 字节(总大小需是最大对齐基准 4 的整数倍)


// 示例3
struct Inner {
    double d;    // 8 字节(对齐基准为8)
    int i;       // 4 字节
};

struct Outer {
    char a;           // 1 字节
    struct Inner inner; // 对齐基准为8(Inner的最大对齐基准)
    short s;          // 2 字节
};

Offset 0: char a (1 字节)
Offset 1-7: 填充 7 字节(Inner需要从8的倍数地址开始)
Offset 8-15: double d (8 字节)
Offset 16-19: int i (4 字节)
Offset 20-21: short s (2 字节)
Offset 22-23: 填充 2 字节(总大小需是8的整数倍)

结构体大小和成员偏移量的查看

    printf("Size: %zu\n", sizeof(struct Example1)); // 输出 12

    printf("Offset of b: %zu\n", offsetof(struct Example1, b)); // 输出 4
                                                                //需添加头文件 #include <stddef.h>     

补充说明

    1. 类型与变量是不同的概念,只能对变量赋值,存取或运算,而不能对一个类型赋值,存取或运算。在编译时,对类型是不分配空间的,只对变量分配空间。
    1. 对结构体中的成员(即 域)可以单元使用,它的作用与地位相当于普通变量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值