【C语言】结构体

有时候需要将不同类型的数据组合为一个整体,以便于引用。例如,一名学生有学号、姓名、性别、年龄等属性,如果针对每个属性都单独定义一个变量,那么当有多名学生时变量就难以分清。结构体就是用来管理不同类型的数据组合。

结构体的声明与定义

结构体的声明一般形式为:

struct 结构体名{
    成员1;
    成员2;
    ...
}; //注意分号

例如:

struct student{
    int num;
    char name[20];
    char gender;
    int age;
};

结构体定义:

//struct 结构体名 变量名
struct student s;

结构体的初始化只能在定义时进行:

struct student s={100,"John",'M',20}; //定义并初始化

如果已经定义了结构体,就不能再初始化了,只能对它的每个成员单独赋值,如s.num=1;

结构体成员的访问

采用“结构体变量名.成员名”的形式来访问结构体成员。

#include <stdio.h>
//工作中往往把结构体声明放在头文件中
struct student{ 
    int num;
    char name[20];
    char gender;
    int age;
};

int main() {
    struct student s={100,"John",'M',20}; //定义并初始化
    struct student sarr[3]; //结构体数组
    s.age = 21;
    printf("%d %s %c %d\n",s.num,s.name,s.gender,s.age);
    printf("---------------------\n");
    for (int i = 0; i < 3; ++i) {
        scanf("%d%s %c%d",&sarr[i].num,sarr[i].name,
              &sarr[i].gender,&sarr[i].age);
    }
    for (int i = 0; i < 3; ++i) {
        printf("%d %s %c %d\n",sarr[i].num,sarr[i].name,
               sarr[i].gender,sarr[i].age);
    }
    return 0;
}

执行结果:

结构体指针

一个结构体变量的指针就是该变量所占据内存段的起始地址。
结构体指针的使用:

#include <stdio.h>

struct student{
    int num;
    char name[20];
    char gender;
    int age;
};

int main() {
    struct student sarr[3]={1,"KK",'F',18,
            2,"HH",'M',20,
            3,"LL",'M',19};
    struct student *p; //定义结构体指针
    p=sarr;
    //获取成员方法一,成员运算符优先级高于取值运算符,所以要加括号(*p).num
    printf("%d %s %c %d\n",(*p).num,(*p).name,(*p).gender,(*p).age);
    //获取成员方法二,更为常用的方法
    printf("%d %s %c %d\n",p->num,p->name,p->gender,p->age);
    printf("---------------\n");
    p=p+1;
    printf("%d %s %c %d\n",p->num,p->name,p->gender,p->age);
    return 0;
}

执行结果:

typedef 的使用

typedef 声明新的类型名来代替已有的类型名。

#include <stdio.h>

typedef struct student{
    int num;
    char name[20];
    char gender;
    int age;
}stu,*pstu; //stu等价于struct student,pstu等价于struct student*

typedef int INTEGER;

int main() {
    stu s={1,"KK",'M',20};
    pstu p=&s;
    INTEGER i=10;
    printf("i=%d,p->num=%d\n",i,p->num);
    return 0;
}

typedef 代替结构体能简化定义结构体编写;代替基础类型时能方便修改类型,而不需要到函数中逐个修改。

结构体对齐

结构体对齐是为了cpu高效存取内存上的数据。
先运行以下程序观察结构体的大小:

#include <stdio.h>

typedef struct A {
    int a;
    char b;
    short c;
}A;

typedef struct B {
    char a;
    int b;
    short c;
}B;

int main() {
    A a;
    B b;
    printf("A size is %d\n",sizeof(a));
    printf("B size is %d\n",sizeof(b));
    return 0;
}

执行结果:

为什么这两个结构体所占字节大小不一致?这里按下不表,先来学习下结构体的对齐规则。

结构体对齐规则

结构体的对齐规则:

  1. 第一个成员的首地址为0
  2. 每个成员的首地址是自身大小的整数倍
  3. 结构体的总大小是其成员中所含最大类型的整数倍

结构体A对齐后占8个字节:

结构体B对齐后占12个字节:

指定默认对齐字节数

通过 #pragma pack()宏命令可以指定默认对齐字节数,可选参数有 1/2/4/8/16,不带参或参数非以上值,将恢复默认值。

//要成对使用,防止对之后的结构体对齐产生影响
#pragma pack(2) //按照2字节对齐
typedef struct B {
    char a;
    int b;
    short c;
}B;
#pragma pack() //取消指定对齐,恢复缺省对齐,

但是若结构体中的最大数据类型比宏声明的小,该声明无效,按照原来的规则进行。

结构体对齐的原因

结构体对齐准确来说是结构体内存对齐。

先来了解下对齐和非对齐的概率:当cpu需要连续取4个字节的数据时,若数据的起始地址能被4整除,则称其对齐访问,反之,则为非对齐访问。

若数据总线宽度为32位,即cpu一次可读取4字节数据,现代计算机是按字节编址,为什么规定cpu只能从地址为4的倍数开始读,而不能从任意位置开始连续读4个字节?
这需要一定的计算机组成原理知识,这里仅进行简要描述。下面给出简单的主存储器的基本组成框图。

数据总线宽度为32位,所以在按字节编址方式下,每次最多可以存取4个存储单元的内容。MAR给出要存取的地址,经过地址译码器译码后一根字选线有效,读取该行的4个存储单元内容。cpu从地址为4的倍数开始连续读4个字节只需一次访存。有些复杂指令集的cpu(如x86)虽然可以完成非对齐访问,但cpu也不是一次性读出四个字节,而是采取多次读取对齐的内存,然后进行数据拼接,从而实现对非对齐数据的访问。
例如,一个4字节的int型数据被在地址为4,5,6,7的存储单元中,只需一次访问即可读出;若放在地址为2,3,4,5的存储单元中,那么就需要两次访存才能读出。

结构体对齐带来了以下好处:

  1. 节省内存:特别是在嵌入式软件开发中。
  2. 硬件要求:许多硬件平台要求数据对齐到特定的内存边界。 如果不满足这些要求,可能会导致硬件异常。
  3. 性能优化:对齐的数据可以更快地被cpu访问,非对齐的数据可能需要多次内存访问才能完全读取或写入,这会降低性能。
posted @   hzyuan  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
点击右上角即可分享
微信分享提示