c语音—结构体
1. 结构体基础
-
是什么:结构体是一种用户自定义的数据类型,它允许你将多个不同类型的变量组合在一起,形成一个逻辑上的整体。这些变量称为成员。
-
为什么:用于描述复杂对象(如学生、图书、坐标点),避免了使用多个分散的变量,使代码更清晰、组织更有序、管理更方便。
-
定义与声明:
// 定义结构体类型,此时并不分配内存 struct Student { int id; char name[20]; float score; }; // 声明结构体变量,此时分配内存 struct Student stu1; // 方式一:先定义类型,再声明变量 // 定义类型的同时声明变量 struct Point { int x; int y; } p1, p2; // p1和p2是全局变量 // 使用typedef创建别名(非常常用) typedef struct Employee { int emp_id; char dept[10]; } Emp; // Emp 是 struct Employee 的别名 Emp e1; // 现在可以用 Emp 来声明变量,更简洁
2. 结构体初始化与成员访问
-
初始化:
// 顺序初始化 struct Student stu1 = {101, "Alice", 95.5f}; // 指定成员初始化,顺序可以打乱 struct Student stu2 = {.name = "Bob", .id = 102, .score = 88.0f}; // 部分初始化,未初始化的成员会被系统初始化为0(全局/静态)或随机值(局部) struct Student stu3 = {103}; // 只初始化了id,name和score为0或随机值 -
成员访问:使用点操作符
.stu1.id = 1001; strcpy(stu1.name, "Charlie"); // 对数组赋值需要使用strcpy printf("Score: %.1f\n", stu1.score);
3. 结构体指针
-
声明与赋值:
struct Student *pStu; pStu = &stu1; // 指向stu1的指针 -
通过指针访问成员:使用箭头操作符
->(*pStu).id = 1002; // 先解引用,再用点操作符,括号必不可少 pStu->id = 1002; // 更简洁、更常用的箭头操作符
4. 结构体数组
- 声明与初始化:
// 声明一个包含3个元素的结构体数组 struct Student class[3] = { {1, "Tom", 99.5f}, {2, "Jerry", 80.0f}, {3, "Spike", 60.5f} }; // 访问数组中的元素 printf("The second student's name is %s\n", class[1].name); class[2].score = 61.0f; // 修改第三个学生的分数 // 指针遍历数组 struct Student *p; for (p = class; p < class + 3; p++) { printf("ID:%d, Name:%s\n", p->id, p->name); }
5. 结构体与函数
-
值传递:将整个结构体拷贝一份传给函数。缺点:如果结构体很大,拷贝开销大。
void printStudent(struct Student s) { printf("ID: %d\n", s.id); } printStudent(stu1); // 发生一次结构体拷贝 -
地址传递:传递结构体的指针。优点:高效,无拷贝开销;缺点:函数内部可以修改原结构体(如果不想被修改,可用
const修饰指针)。void updateScore(struct Student *s, float new_score) { s->score = new_score; // 会修改原结构体的值 } updateScore(&stu1, 100.0f); // 使用const防止修改 void printStudentConst(const struct Student *s) { // s->id = 5; // 这行代码会编译错误,因为s指向的内容是只读的 printf("ID: %d\n", s->id); }面试重点:永远优先使用传递指针的方式,除非结构体非常小。
6. 字符串操作(重点)
字符数组赋值:
- 错误:
char str[20]; str = "Hello";(数组名是常量地址,不能被赋值) - 正确:使用
strcpy函数:strcpy(str, "Hello");
常用字符串函数 (#include <string.h>)
strlen():获取长度strcpy(): 字符串复制。strcmp():字符串比较strcat(): 字符串连接。strstr(): 在主串中寻找子串。strtok(): 用分隔符切割字符串。itoa(): 将整数转换为字符串。
常用内存操作函数 (#include <string.h>)
memset(): 将内存块的前 n 字节设置为特定值。memcpy(): 从源内存地址拷贝 n 字节到目标地址。memmove(): 功能同memcpy,但能正确处理内存重叠区域。
面试题:内存对齐
1、为什么要进行内存对齐?内存对齐的优点。
- ① 什么是内存对齐:数据在内存中的起始地址按照一定的规则进行排列,而不是顺序紧密地排列。
- ② 优势:提升CPU的访问效率。
- 内存划分的基本单位是字节,但CPU访问内存不是逐字节读取,而是通过内存总线按照“块”(如2B、4B、8B)进行读取。
- 对齐的好处:如果数据存储遵循内存对齐规则,CPU可以在一个读取周期内就完整地获取到数据。
- 不对齐的代价:如果数据存储未遵循对齐规则,它可能跨越两个或多个CPU读取块。这时需要多个读取周期,并将读取到的字节块进行拼接才能得到完整数据,这大大降低了访问效率。
2、怎么进行内存对齐?
- 默认对齐方式:
- Windows默认:8字节对齐
- Linux默认:4字节对齐
- 指定对齐方式:
- 可以使用预编译指令(如
#pragma pack(n))来显式地指定对齐模数。
![fe6515c7b3dabedc5354712a7a97717]()
- 可以使用预编译指令(如
3、求结构体大小(遵循的规则)
计算结构体大小需要遵循以下三个核心规则:
- 成员变量偏移量规则:结构体内每个成员变量的起始地址的偏移量(相对于结构体首地址),必须是
Min(该成员自身大小, 当前有效对齐模数)的整数倍。 - 结构体总大小规则:整个结构体的总大小,必须是
Min(结构体中最大基本成员的大小, 当前有效对齐模数)的整数倍。 - 结构体嵌套规则:在计算含有嵌套结构体的复杂结构时,子结构体的“最大基本数据类型” 会被视为当前结构体的一个普通成员来参与规则2的计算。
例题:
![4b5f74c867197787deb185e29ff839c]()



浙公网安备 33010602011771号