当前位置: 首页 > news >正文

5. C语言 结构体


🧭 一、结构体的本质:把“相关的数据”放到一起

在C语言中,struct 是一种自定义数据类型,用来把多个不同类型的数据组织成一个整体。

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

这就定义了一个“学生”类型,它包含:

  • 一个字符串 name
  • 一个整型 age
  • 一个浮点数 score

✅ 定义变量的几种写法

struct Student s1;                     // 普通变量
struct Student s2 = {"Tom", 18, 95.5}; // 初始化
struct Student class[30];              // 结构体数组
struct Student *p = &s1;               // 指针方式

🧩 二、结构体访问方式

有两种:

方式写法含义
普通访问s1.age访问变量的成员
指针访问p->age通过指针访问(等价于 (*p).age
struct Student s1 = {"Tom", 18, 95.5};
struct Student *p = &s1;printf("%s %d %.1f\n", s1.name, s1.age, s1.score);
printf("%s %d %.1f\n", p->name, p->age, p->score);

🧱 三、结构体的内存分布与对齐

结构体在内存中是按成员顺序连续存放的,但会有“对齐”规则。


✅ 示例:

struct Data {char a;int b;short c;
};

在32位系统中,可能占用 12字节 而不是7字节。

📊 内存布局图(假设4字节对齐):

成员偏移量实际字节说明
a01char
填充1-33对齐到4字节边界
b4-74int
c8-92short
填充10-112对齐到4字节倍数

结构体最终大小必须是最大对齐单位的整数倍(这里是4)。


⚙️ 嵌入式中应用:内存对齐影响性能

在 ARM Cortex-M 中:

  • 非对齐访问可能触发 总线错误
  • 或造成 性能下降

所以有时会用编译指令关闭对齐:

#pragma pack(1)
struct Data { char a; int b; short c; };
#pragma pack()

📌 结果: 按1字节对齐,总共7字节。


🧠 四、结构体与指针在嵌入式中的应用

在嵌入式中,结构体最常见用途是:
寄存器映射(Register Mapping)


✅ 例:GPIO外设寄存器映射

typedef struct {volatile unsigned int MODER;   // 模式寄存器volatile unsigned int OTYPER;  // 输出类型寄存器volatile unsigned int OSPEEDR; // 速度寄存器volatile unsigned int PUPDR;   // 上下拉寄存器volatile unsigned int IDR;     // 输入数据寄存器volatile unsigned int ODR;     // 输出数据寄存器
} GPIO_TypeDef;

💡 然后用宏定义寄存器基地址:

#define GPIOA ((GPIO_TypeDef *)0x40020000)

操作寄存器就像操作结构体成员:

GPIOA->MODER |= (1 << 10);  // 设置PA5为输出
GPIOA->ODR |= (1 << 5);     // 点亮LED

是不是比直接写:

*(volatile unsigned int *)(0x40020000 + 0x14) |= (1 << 5);

更清晰、更安全?


🧮 五、结构体嵌套与数组

结构体可以嵌套使用:

struct Date {int year, month, day;
};struct Student {char name[20];struct Date birthday;
};

访问方式:

struct Student s = {"Tom", {2000, 5, 20}};
printf("%d-%d-%d\n", s.birthday.year, s.birthday.month, s.birthday.day);

结构体数组:

struct Student class[2] = {{"Tom", {2000, 5, 20}},{"Jerry", {2001, 7, 12}}
};

🧩 六、结构体与函数结合(传参、返回)

✅ 1️⃣ 值传递

void printStu(struct Student s) {printf("%s %d %.1f\n", s.name, s.age, s.score);
}

👉 会复制一份整个结构体。


✅ 2️⃣ 指针传递(嵌入式常用)

void printStu(const struct Student *s) {printf("%s %d %.1f\n", s->name, s->age, s->score);
}

👉 更高效,不复制数据。


✅ 3️⃣ 返回结构体

struct Point { int x, y; };struct Point makePoint(int x, int y) {struct Point p = {x, y};return p;
}

代码解析

  1. 结构体定义
    struct Point { int x, y; }; 定义了一个名为 Point 的结构体,包含两个整型成员 xy(可理解为一个二维坐标点)。

  2. 函数 makePoint
    函数作用是创建并返回一个 Point 类型的结构体实例

    • 参数 xy 是坐标值;
    • 函数内部创建局部结构体变量 p,并用 {x, y} 初始化(成员 x 赋值为参数 x,成员 y 赋值为参数 y);
    • 通过 return p 返回这个结构体。

关键:结构体作为返回值的机制

在C语言中,函数可以直接返回结构体(值传递),这与返回基本类型(如 intchar)的逻辑类似:

  • 当函数返回结构体时,编译器会将函数内部的局部结构体变量(如 p完整复制一份,传递给函数调用处的接收变量。
  • 函数执行结束后,内部的局部变量 p 会被销毁,但返回的“副本”已经被保留,因此不会出现“访问已销毁内存”的问题(这一点与返回指针指向局部变量的错误写法完全不同)。

使用示例

可以在 main 函数中调用 makePoint 接收返回的结构体,并使用其成员:

#include <stdio.h>struct Point { int x, y; };struct Point makePoint(int x, int y) {struct Point p = {x, y};return p;  // 返回结构体的副本
}int main() {// 调用函数获取返回的结构体,并用变量接收struct Point pt = makePoint(10, 20);// 使用结构体成员printf("坐标:(%d, %d)\n", pt.x, pt.y);  // 输出:坐标:(10, 20)return 0;
}

优缺点分析

  • 优点
    简单直接,无需手动管理内存(不像返回指针时可能需要 malloc 分配内存,后续还要 free 避免泄漏),适合小型结构体(如 Point 这种仅包含几个成员的类型)。

  • 缺点
    如果结构体很大(例如包含多个数组或复杂成员),返回时的“复制操作”会产生性能开销(因为需要复制整个结构体的内容)。此时更推荐返回结构体指针(但需注意指针指向的内存生命周期,通常用 malloc 动态分配)。

总结:返回结构体的标准写法,对于小型结构体非常实用,且不存在内存安全问题。


🔧 七、typedef 简化写法

在嵌入式开发中,我们常用 typedef 给结构体起别名:

typedef struct {uint16_t id;char name[16];
} Device_t;

这样使用更简洁:

Device_t dev1 = {1001, "Sensor"};

🧠 八、结构体与内存通信

结构体常用于:

  • UART数据帧解析
  • SPI/I2C通信帧封装
  • CAN报文结构体化

✅ 例:CAN数据包

typedef struct {uint32_t id;uint8_t  data[8];uint8_t  len;
} CAN_Frame_t;

封装数据:

CAN_Frame_t frame = {0x321, {1,2,3,4,5,6,7,8}, 8};
send_can_frame(&frame);

解包时:

void onCANReceive(const CAN_Frame_t *f) {printf("ID=0x%X, Len=%d\n", f->id, f->len);
}

🧩 九、结构体与位域(Bitfield)

嵌入式中常用位域来操作寄存器的某一位。

typedef struct {uint32_t EN     : 1;  // 使能uint32_t MODE   : 2;  // 模式uint32_t SPEED  : 3;  // 速度uint32_t RESERVED : 26;
} GPIO_CFG_t;

访问方式:

GPIO_CFG_t cfg;
cfg.EN = 1;
cfg.MODE = 3;

💡 对应寄存器位操作,非常直观。


✅ 十、总结总表

概念说明嵌入式意义
struct把不同类型数据组合在一起封装硬件数据结构
typedef给结构体起别名简化代码风格
指针访问-> 操作成员操作寄存器、通信结构
内存对齐保证性能、兼容性影响结构体大小
位域按位操作寄存器精确控制硬件位
嵌套结构体中再包含结构体模块化设计

结构体数组在定义的同时也可以进行初始化,并且与结构体变量的初始化规定相同,只能对全局的或静态存储类别的结构体数组初始化。这段话对吗

“结构体数组在定义的同时也可以进行初始化,并且与结构体变量的初始化规定相同,只能对全局的或静态存储类别的结构体数组初始化。”

这句话 部分正确,但不完全准确
我们来系统分析一下 👇


🧩 一、结构体数组确实可以在定义时初始化 ✅

在 C 语言中,结构体数组可以像普通数组一样,在定义时直接初始化。

#include <stdio.h>struct Student {char name[10];int score;
};int main() {struct Student class[] = {{"Tom", 90},{"Lily", 85},{"Jack", 78}};for (int i = 0; i < 3; i++)printf("%s: %d\n", class[i].name, class[i].score);
}

✅ 输出:

Tom: 90
Lily: 85
Jack: 78

这里 class 是一个局部结构体数组(在函数内定义),
但仍然可以在定义时初始化。
👉 所以说“只能对全局或静态结构体数组初始化”是错误的


🧠 二、问题的根源:编译期与运行期的区别

其实 C 语言对 “初始化” 的要求是:

只有在 定义时(编译期) 才能进行静态初始化。
如果变量已经定义完毕,则不能再赋初值。

举个例子:

struct Point { int x, y; };// ✅ 正确:定义时初始化
struct Point p1 = {10, 20};// ❌ 错误:不能在定义后初始化
struct Point p2;
p2 = {30, 40};   // ❌ 语法错误

结构体数组同理

struct Point arr1[] = {{1,2}, {3,4}};   // ✅ 可以
struct Point arr2[2];
arr2 = {{1,2}, {3,4}};                  // ❌ 不行

这与是不是“全局或静态”无关,而是取决于是否是定义时


⚙️ 三、全局 / 静态结构体数组的确有“静态初始化”的特性 ✅

如果结构体数组是全局或静态变量(static),
它们会被放入 静态存储区(.data/.bss 段)
即使你不初始化,也会被自动清零

struct Point {int x, y;
};struct Point globalArr[2]; // 全局变量,自动初始化为0int main() {static struct Point staticArr[2]; // 静态局部变量,也会自动初始化为0printf("%d %d\n", globalArr[0].x, staticArr[0].x); // 输出 0 0
}

而对于普通(自动)局部变量:

int main() {struct Point localArr[2];  // 自动变量,不会自动初始化!printf("%d\n", localArr[0].x); // ⚠️ 未定义行为!
}

✅ 四、正确说法总结

你那句话可以修改为更严谨的版本:

结构体数组在定义时可以进行初始化,且初始化方式与结构体变量相同。
全局或静态结构体数组未显式初始化时,会被自动初始化为零;
局部(自动)结构体数组则不会自动初始化。


📘 五、嵌入式实际例子

嵌入式中常见这样的定义:

typedef struct {uint8_t id;uint16_t period;void (*callback)(void);
} Task;Task taskTable[] = {{1, 1000, task1_handler},{2, 500,  task2_handler},{3, 200,  task3_handler}
};

✅ 这是合法的“静态初始化”,
因为 taskTable 在定义时已经被初始化。

但如果你写:

Task taskTable[3];
taskTable = { ... }; // ❌ 不允许

则编译报错。


🧭 结论总结表:

情况能否初始化默认值行为
全局结构体数组✅ 定义时初始化未初始化自动为 0
静态结构体数组✅ 定义时初始化未初始化自动为 0
局部结构体数组✅ 定义时初始化未初始化为随机值
定义后再赋初值❌ 不允许-

什么是静态结构体数组和局部结构体数组

非常好的问题!🎯
这个问题实际上涉及到 C语言中的存储类别(Storage Class)变量生命周期
理解这两个概念,对于嵌入式系统和系统编程至关重要,能帮助你更加明确数据的存储位置和生命周期,避免常见的内存管理错误。


🧭 一、静态结构体数组(Static Structure Array)

1️⃣ 什么是静态结构体数组?

静态结构体数组 是指那些声明时使用了 static 关键字的结构体数组。
它的 存储位置 位于 静态存储区,它的生命周期 贯穿整个程序的执行过程,即从程序启动到程序退出。

✅ 特性:

  • 存储位置:静态存储区(也叫 .data.bss 段)。
  • 生命周期:程序开始时分配,程序结束时释放。
  • 默认初始化:如果没有显式初始化,自动初始化为零
  • 作用范围:局部静态变量作用域仍然限于定义它的函数内,但它的生命周期从程序开始一直持续到结束。

例子:

#include <stdio.h>typedef struct {int id;char name[20];
} Employee;void printEmployeeInfo() {static Employee employees[2] = {{1, "Alice"},{2, "Bob"}};// 自动初始化for (int i = 0; i < 2; i++) {printf("ID: %d, Name: %s\n", employees[i].id, employees[i].name);}
}int main() {printEmployeeInfo(); // 第一次调用printEmployeeInfo(); // 第二次调用,注意数组不会被重新初始化return 0;
}

关键点:

  • static 关键字:让 employees 数组保持在静态存储区,并且在函数调用结束后不会销毁,而是保持它的值(不会被重新初始化)。
  • 生命周期:静态结构体数组 employees 在程序的整个运行过程中都会存在。

输出:

ID: 1, Name: Alice
ID: 2, Name: Bob
ID: 1, Name: Alice
ID: 2, Name: Bob

解释
第二次调用 printEmployeeInfo() 时,employees 数组并没有重新初始化,而是使用了第一次调用时的值。


2️⃣ 静态结构体数组的用途:

在嵌入式编程中,静态结构体数组非常常见,用于存储:

  • 固定数量的配置数据。
  • 状态机的状态。
  • 系统级常量。

🧩 二、局部结构体数组(Automatic Structure Array)

1️⃣ 什么是局部结构体数组?

局部结构体数组 是指那些在 函数内部 声明的 普通结构体数组,并且没有使用 static 关键字。
它的 存储位置 位于 栈区,它的生命周期 仅限于函数调用期间,当函数调用结束时,局部数组就会被销毁。

✅ 特性:

  • 存储位置:栈区(stack)。
  • 生命周期:在函数调用时创建,函数调用结束时销毁。
  • 默认初始化:不进行初始化时,栈内存中的值是随机的(即没有自动初始化)。
  • 作用范围:只能在函数内部访问,函数外部无法访问。

例子:

#include <stdio.h>typedef struct {int id;char name[20];
} Employee;void printEmployeeInfo() {Employee employees[2] = {{1, "Alice"},{2, "Bob"}};for (int i = 0; i < 2; i++) {printf("ID: %d, Name: %s\n", employees[i].id, employees[i].name);}
}int main() {printEmployeeInfo();  // 第一次调用printEmployeeInfo();  // 第二次调用,数组被重新初始化return 0;
}

关键点:

  • 栈区存储employees 数组在栈区分配内存,并且在 printEmployeeInfo 函数执行完后会被销毁。
  • 生命周期:每次调用 printEmployeeInfo() 时,employees 数组都会重新创建并初始化。

输出:

ID: 1, Name: Alice
ID: 2, Name: Bob
ID: 1, Name: Alice
ID: 2, Name: Bob

解释
每次调用 printEmployeeInfo(),数组 employees 都会重新创建和初始化,且栈空间会在每次函数调用结束时释放。


2️⃣ 局部结构体数组的用途:

局部结构体数组常用于:

  • 函数内部临时存储数据。
  • 存储每次调用时需要初始化的、临时的结构体数据。

🧠 三、全局结构体数组 vs 静态结构体数组 vs 局部结构体数组

为了更好地理解它们的区别,我们总结了下面的对比表:

特性全局结构体数组静态结构体数组局部结构体数组
存储位置数据段(.data)数据段(.data)栈区
生命周期整个程序运行期间整个程序运行期间函数调用期间
初始化默认初始化为零(未初始化的会自动清零)自动清零或显式初始化未初始化时随机值
访问可以在程序的任何地方访问只能在定义它的函数内访问只能在定义它的函数内访问
示例struct Employee arr[10];static struct Employee arr[10];struct Employee arr[10];

🧩 四、嵌入式应用实例:寄存器映射

在嵌入式开发中,我们经常会定义结构体数组用于表示寄存器(如GPIO、SPI、I2C等),
而静态结构体数组则特别适合用于这种硬件寄存器映射,保证整个程序的生命周期内这些数据不被丢失。

#define GPIOA_BASE 0x40020000typedef struct {volatile uint32_t MODER;    // 0x00: GPIO mode registervolatile uint32_t OTYPER;   // 0x04: GPIO output type registervolatile uint32_t OSPEEDR;  // 0x08: GPIO output speed registervolatile uint32_t PUPDR;    // 0x0C: GPIO pull-up/pull-down register
} GPIO_TypeDef;#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)  // 寄存器基地址映射int main() {// 静态结构体数组static GPIO_TypeDef gpioPorts[3] = {{0x00, 0x00, 0x00, 0x00},  // GPIOA{0x00, 0x00, 0x00, 0x00},  // GPIOB{0x00, 0x00, 0x00, 0x00}   // GPIOC};// 访问 GPIOA 寄存器GPIOA->MODER |= (0x01 << 10);  // 配置PA5为输出return 0;
}

🧭 五、总结与最佳实践

类型说明最佳实践
全局结构体数组生命周期:程序开始到结束;存储在 .data.bss 中;用于存储程序中全局共享数据,如配置信息、硬件状态等。
静态结构体数组生命周期:函数调用期间;存储在静态存储区;自动清零。用于跨多次函数调用保持数据状态,如状态机、任务队列等。
局部结构体数组生命周期:函数调用期间;存储在栈区;不可自动初始化。用于函数内部的临时数据存储。

结构体初始化

本节我们会深入讨论:

  • 结构体的初始化方式;
  • 如何根据不同存储类别(局部变量、全局变量、静态变量)初始化结构体;
  • 如何正确使用结构体初始化器(包括嵌套结构体、数组成员等);
  • 如何解决初始化中可能遇到的问题。

🧭 一、结构体初始化概述

在 C 语言中,结构体初始化 就是为结构体的每个成员赋初值
它可以在定义结构体时直接完成,也可以在定义后手动初始化。

1️⃣ 基本初始化

假设我们有一个简单的结构体类型:

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

我们可以通过以下两种方式来初始化结构体:

// 方式1:定义时直接初始化
struct Student s1 = {"Tom", 18, 90.5};// 方式2:先定义,再初始化
struct Student s2;
s2.age = 20;
s2.score = 85.0;
strcpy(s2.name, "Jerry");

🧩 二、结构体成员初始化规则

结构体初始化时,成员的初始化顺序与成员声明顺序一致。
即,第一个成员先初始化,第二个成员其次,依此类推。

struct Student s1 = {"Tom", 18, 90.5};

这里:

  • "Tom" 会初始化 name
  • 18 会初始化 age
  • 90.5 会初始化 score

🧠 默认初始化规则:

  • 静态/全局结构体变量:如果没有显式初始化,成员会自动初始化为零(0 或 NULL)。
  • 局部结构体变量:如果没有显式初始化,成员的值是随机的(垃圾值)。

🧭 三、嵌套结构体的初始化

如果结构体中有另一个结构体成员,你可以采用递归方式初始化

示例:嵌套结构体初始化

struct Date {int day, month, year;
};struct Student {char name[20];struct Date birthdate;  // 嵌套结构体float score;
};// 定义并初始化
struct Student s1 = {"Tom", {20, 5, 1990}, 85.5};

这里:

  • birthdate 是一个 struct Date 类型的嵌套结构体,我们可以在初始化 s1 时同时为 birthdateday, month, year 赋值。

🧩 四、初始化方式的不同

1️⃣ 按成员顺序初始化(按顺序赋值)

struct Student s1 = {"Tom", 18, 90.5}; // 按顺序初始化

这里:

  • "Tom" 会初始化 name
  • 18 会初始化 age
  • 90.5 会初始化 score

2️⃣ 通过指定成员名初始化(命名初始化器)

你也可以通过指定成员名的方式来初始化结构体,这种方式能够避免顺序错误的问题,且更具可读性。

struct Student s2 = {.name = "Jerry", .age = 20, .score = 85.0};

这样初始化的好处是,即使你不按照结构体成员声明的顺序写,也可以正确初始化


🧭 五、结构体数组初始化

结构体数组的初始化方式与单个结构体相似,只不过是为数组中的每个结构体逐一赋值。

示例:

struct Student class[3] = {{"Tom", 18, 90.5},{"Jerry", 20, 85.0},{"Alice", 19, 88.5}
};

这里我们初始化了一个含有 3 个元素的结构体数组 class,每个元素是一个 struct Student

手动初始化:

struct Student class[3];
class[0] = (struct Student){"Tom", 18, 90.5};
class[1] = (struct Student){"Jerry", 20, 85.0};
class[2] = (struct Student){"Alice", 19, 88.5};

🧩 六、部分初始化(未初始化成员的处理)

如果你只为部分成员赋初值,其他成员将自动初始化为零。

struct Student s1 = {"Tom"};  // 只初始化 name

这里:

  • s1.name 被初始化为 "Tom"
  • s1.ages1.score 会自动初始化为零。

注意:如果初始化时没有给某些成员赋值,零初始化 只对全局和静态存储类别的结构体有效。局部变量在没有显式初始化时会含有垃圾值。


🧭 七、结构体初始化的注意事项

1️⃣ 全局和静态结构体变量自动初始化为零

struct Student s1;  // 全局变量,自动初始化为零

在全局或静态区域定义的结构体,如果没有显式初始化,所有成员都会自动初始化为零

  • int 型成员初始化为 0
  • char[] 型成员初始化为空字符串 ""
  • 指针 型成员初始化为 NULL

2️⃣ 局部结构体变量不自动初始化

int main() {struct Student s1;  // 局部变量,不会自动初始化printf("Name: %s\n", s1.name);  // 可能是垃圾值
}

局部变量如果没有显式初始化,成员的值是 不确定的会包含垃圾值,这是 C 语言的一个常见陷阱。


🧩 八、结构体的内存分配与初始化

1️⃣ 栈内存中的结构体

当你声明一个结构体时,它的成员会被保存在栈上。栈上的结构体成员不会自动初始化,除非显式地进行初始化。

void func() {struct Student s = {"Tom", 18, 90.5};  // 局部变量printf("Name: %s, Age: %d, Score: %.1f\n", s.name, s.age, s.score);
}

2️⃣ 堆内存中的结构体(动态分配)

当你使用 malloc 动态分配结构体时,结构体成员的值是未定义的(也是垃圾值)。如果你想初始化它们,必须手动进行。

struct Student *s = malloc(sizeof(struct Student));
if (s != NULL) {s->age = 20;strcpy(s->name, "Alice");s->score = 88.5;
}

🧭 九、总结与最佳实践

情况说明最佳实践
结构体数组初始化结构体数组可以在定义时进行初始化。对结构体数组进行逐个初始化,避免漏项。
部分初始化未初始化的成员会自动初始化为零。始终显式初始化成员,避免错误。
全局/静态变量全局变量和静态变量会自动初始化为零。对于静态/全局结构体,初始化时可以省略部分项。
局部变量局部结构体变量不会自动初始化,必须显式初始化。局部结构体变量必须初始化,否则会有垃圾值。

结构体数组


🧭 一、结构体数组的基本概念

1️⃣ 什么是结构体数组?

结构体数组 是一个由多个结构体元素组成的数组。
每个数组元素都是一个结构体,结构体的各个成员可以是不同的数据类型。
你可以通过数组下标来访问数组中的每个结构体。

定义方式:

struct Student {char name[20];int age;float score;
};struct Student class[3];  // 定义一个包含3个学生的结构体数组
  • class[0]class[1]class[2]struct Student 类型的结构体。
  • 你可以通过 class[i] 来访问每个学生的具体信息。

2️⃣ 结构体数组的初始化

结构体数组可以像普通数组一样在定义时进行初始化。

例:初始化结构体数组

struct Student class[3] = {{"Tom", 18, 90.5},{"Jerry", 20, 85.0},{"Alice", 19, 88.5}
};
  • class[0] 被初始化为 {"Tom", 18, 90.5}
  • class[1] 被初始化为 {"Jerry", 20, 85.0}
  • class[2] 被初始化为 {"Alice", 19, 88.5}

手动初始化:

struct Student class[3];class[0].age = 18;
class[0].score = 90.5;
strcpy(class[0].name, "Tom");class[1].age = 20;
class[1].score = 85.0;
strcpy(class[1].name, "Jerry");class[2].age = 19;
class[2].score = 88.5;
strcpy(class[2].name, "Alice");

🧩 二、访问结构体数组成员

你可以通过数组下标访问结构体数组的元素,然后再通过点操作符 . 来访问结构体的成员。

例:

#include <stdio.h>struct Student {char name[20];int age;float score;
};int main() {struct Student class[3] = {{"Tom", 18, 90.5},{"Jerry", 20, 85.0},{"Alice", 19, 88.5}};// 访问第一个学生的信息printf("Name: %s, Age: %d, Score: %.2f\n", class[0].name, class[0].age, class[0].score);// 访问第二个学生的信息printf("Name: %s, Age: %d, Score: %.2f\n", class[1].name, class[1].age, class[1].score);return 0;
}

输出:

Name: Tom, Age: 18, Score: 90.50
Name: Jerry, Age: 20, Score: 85.00

在这个例子中,我们通过 class[0].nameclass[0].ageclass[0].score 访问 class[0] 这个学生的各个成员。


🧭 三、结构体数组的内存布局

每个结构体数组元素占据一定的内存空间,内存空间大小等于单个结构体的大小。
结构体数组的内存是 按结构体成员顺序依次排列 的。

示例:内存布局

假设结构体定义如下:

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

每个 struct Student 占据的内存大小是:

  • name[20]:20 字节
  • age:4 字节(通常是 int 类型的大小)
  • score:4 字节(通常是 float 类型的大小)

在 32 位系统中,假设没有对齐,struct Student 大小为 20 + 4 + 4 = 28 字节。

结构体数组 class[3] 占用的内存:

class[0]  ──────────────► [ "Tom" ] [ 18 ] [ 90.5 ]
class[1]  ──────────────► [ "Jerry" ] [ 20 ] [ 85.0 ]
class[2]  ──────────────► [ "Alice" ] [ 19 ] [ 88.5 ]

🧩 四、结构体数组的动态内存分配

有时,我们需要在程序运行时动态分配结构体数组的内存。
这可以通过 malloc 函数实现。

例:动态分配结构体数组

#include <stdio.h>
#include <stdlib.h>struct Student {char name[20];int age;float score;
};int main() {// 动态分配内存struct Student *class = malloc(3 * sizeof(struct Student));if (class == NULL) {printf("Memory allocation failed!\n");return 1;}// 初始化数据strcpy(class[0].name, "Tom");class[0].age = 18;class[0].score = 90.5;strcpy(class[1].name, "Jerry");class[1].age = 20;class[1].score = 85.0;strcpy(class[2].name, "Alice");class[2].age = 19;class[2].score = 88.5;// 打印数据for (int i = 0; i < 3; i++) {printf("Name: %s, Age: %d, Score: %.2f\n", class[i].name, class[i].age, class[i].score);}// 释放内存free(class);return 0;
}

关键点:

  • 使用 malloc 动态分配内存来存储结构体数组。
  • 必须使用 free() 来释放内存,防止内存泄漏。

🧭 五、结构体数组与指针

结构体数组本质上是一个 指向结构体的指针,所以可以使用指针来操作结构体数组。
对于结构体数组,可以用指针加偏移量的方式来访问各个元素。

示例:

struct Student class[3] = {{"Tom", 18, 90.5},{"Jerry", 20, 85.0},{"Alice", 19, 88.5}
};// 使用指针访问结构体数组
struct Student *p = class;printf("%s\n", p->name);  // 访问 class[0]
p++;
printf("%s\n", p->name);  // 访问 class[1]
p++;
printf("%s\n", p->name);  // 访问 class[2]

输出:

Tom
Jerry
Alice

这里,我们将结构体数组 class 的首地址赋给指针 p,然后通过指针偏移访问数组中的元素。


🧩 六、结构体数组的常见应用场景

1️⃣ 存储多个对象

结构体数组常用于 存储多个相同类型的对象,例如:

  • 存储多个 学生 数据;
  • 存储多个 传感器 的状态;
  • 存储多个 任务 的信息。

2️⃣ 操作硬件寄存器

在嵌入式开发中,结构体数组用于操作多个硬件寄存器。例如,多个 GPIO端口的配置

#define GPIOA_BASE 0x40020000
#define GPIOB_BASE 0x40020400typedef struct {volatile uint32_t MODER;   // GPIO模式寄存器volatile uint32_t OTYPER;  // GPIO输出类型寄存器volatile uint32_t OSPEEDR; // GPIO输出速度寄存器volatile uint32_t PUPDR;   // GPIO上下拉寄存器
} GPIO_TypeDef;GPIO_TypeDef *GPIOs[2] = {(GPIO_TypeDef *)GPIOA_BASE, (GPIO_TypeDef *)GPIOB_BASE};

📘 七、总结

特性描述
结构体数组的定义struct Student class[3];
结构体数组的初始化在定义时可以直接初始化,或者手动逐一赋值
结构体数组的访问通过下标访问元素,class[0].nameclass[i].name
动态分配内存使用 mallocfree 动态分配内存
结构体数组与指针数组名即为首元素的指针,可通过指针访问数组元素
应用场景存储多个对象、硬件寄存器操作等

http://www.dtcms.com/a/530445.html

相关文章:

  • 怎么查网站的备案信息茶叶企业网站开发源码
  • 网站怎么制作高唐住房建设局网站
  • 广告片制作哪家好关键词优化seo外包
  • 祁东县建设局网站wordpress发帖提示升级vip
  • 建站的公司全国工程建设系统网站
  • 建公司网站哪里好佛山网站建设公司如何组建
  • 做音乐网站的目的wordpress本地如何安装
  • 温州给企业做网站苏州高端建站公司
  • 淘宝网站建设协议做百度推广首先要做网站吗
  • 莆田 网站建设排名第一的手机清理软件
  • 怎样加强企业网站建设微信小程序教程
  • 网站建设模板登录界面深圳logo设计公司哪家好
  • 设计做图免费网站提升关键词优化排名软件
  • 模板网站代理网站怎么做聚合
  • 什么网站有加工外发做的wordpress 上传头像
  • 广州高铁新建站在哪里WordPress 陈星
  • 电商网站设计周志伦敦做网站
  • 写建设网站的具体步骤非标自动化外包平台
  • 企业网站建设的开发方式有建站系统排行榜
  • 电商网站竞价推广策略自己公司网站如何添加qq
  • 买网站自己做广告设计公司的成本票项目有哪些
  • 建立网站链接结构的基本方式有建设网站建设哪家快
  • 青锐成长计划网站开发过程wordpress音乐主题
  • 发布公司信息的网站龙岩网红餐厅
  • phpcms手机网站模板好的建筑设计公司
  • 网站建设人员工作计划家里装修
  • 广东卫视你会怎么做网站外贸网站建设熊掌号
  • 怎样做旅游城市住宿网站嘉定房产网站建设
  • 企业网站建设的案例广西建设厅培训中心
  • 做微商有哪些网站可以免费宣传锕锕锕锕锕锕锕好湿免费网址