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

week15 指针与结构体

好的,没问题!这份PPT内容非常核心,是C语言学习的重点和难点。为了帮助你考试,我将为你进行全面、深刻的梳理,并补充关键细节和例题。


核心知识梳理与深度讲解

01. 结构体内存布局与对齐(考试绝对重点!)

核心思想: 结构体在内存中占用一段连续的空间,但为了CPU的访问效率(字宽),编译器会在成员之间插入“填充字节”,使每个成员都从其自身大小倍数的地址开始。

关键概念:

  • 字宽: CPU一次能处理的数据位数。32位系统字宽为4字节,64位系统常为8字节。
  • 对齐: 变量在内存中的起始地址必须是某个数(通常是其自身大小或编译器指定值)的整数倍。
  • #pragma pack(N) 预编译指令,强制指定结构体的最大对齐模数为N。N通常为1, 2, 4, 8, 16。

对齐规则(务必牢记):

  1. 成员对齐规则: 每个成员相对于结构体首地址的偏移量(offset)必须是 min(该成员自身大小, N) 的整数倍。
  2. 结构体总大小规则: 整个结构体的总大小必须是 min(所有成员中最大对齐值, N) 的整数倍。

例题精讲(来自PPT):

#pragma pack(4)
typedef struct {char a[3];  // 大小3字节,对齐值=min(1,4)=1int b;      // 大小4字节,对齐值=min(4,4)=4short c;    // 大小2字节,对齐值=min(2,4)=2
} unpacked_struct;

内存布局分析:

  1. a[0] 在偏移量0。
  2. a[1] 在偏移量1。
  3. a[2] 在偏移量2。
  4. 下一个可用地址是3。但int b的对齐值是4,所以编译器在a[2]后面插入1个填充字节,让b从偏移量4开始。
  5. b占用4字节(偏移量4~7)。
  6. short c的对齐值是2,偏移量8是2的倍数,所以c直接从偏移量8开始。
  7. c占用2字节(偏移量8~9)。
  8. 现在总字节数是10。但结构体总大小必须是 min(max(1,4,2), 4) = 4 的倍数。10不是4的倍数,所以最后需要填充2个字节到12字节。

为什么sizeof(unpacked_struct)是12?
就是因为有 “成员间的填充”“最终的整体填充”

考试技巧: 遇到结构体大小计算题,画图!一步一步计算偏移量和填充。


02. 位域(Bit Fields)

核心思想: 允许将结构体成员定义为特定位数的字段,从而高效地使用内存(尤其是硬件寄存器映射)。

关键点:

  • 语法: 类型 成员名 : 位宽;
  • 类型: 必须是整型或_Bool
  • 溢出: 赋值超过位宽所能表示的范围时,会发生高位截断。
    struct A { unsigned int t : 2; };
    struct A a;
    a.t = 3; // 二进制11,合法,值为3
    a.t = 4; // 二进制100,被截断为00,值为0,并可能有编译器警告
    
  • 内存打包: 连续定义的位域会尽量打包到同一个“存储单元”(如一个int)中。
  • 匿名位域: : 位数,用于占位,不表示实际成员。
  • 0宽度位域: : 0,强制下一个位域从新的存储单元开始。

例题(来自PPT):

struct BF11 {int a : 4;int   : 2;  // 匿名位域,跳过2位int b : 5;
};

这个结构体a占4位,然后空2位,b占5位。它们被打包在同一个int(假设32位)中。总位数4+2+5=11 < 32,所以sizeof(struct BF11)通常是4(一个int的大小)。

考试常见题型: 计算位域结构体的大小,或通过联合体(union)查看位域的具体布局。


03. 结构体指针与链表(重中之重!)

1. 自引用结构体:
结构体不能直接包含一个自身的实例(会导致无限递归定义),但可以包含一个指向自身类型的指针。这是构建链式数据结构(如链表、树)的基础。

typedef struct NODE {int data;           // 数据域struct NODE *next;  // 指针域,指向下一个节点
} Node;

2. 链表基本概念:

  • 节点: 就是上面的Node,是数据和指针的结合体。
  • 头指针: 指向链表第一个节点的指针。链表是通过头指针来访问的
  • 尾节点: 最后一个节点,其next指针为NULL

3. 链表核心操作(必须会写代码!)

a. 遍历:

void printList(Node *head) {Node *current = head; // 用临时指针遍历,不要移动head!while (current != NULL) {printf("%d -> ", current->data);current = current->next;}printf("NULL\n");
}

b. 创建节点(头插法):

Node* createNode(int data) {Node *newNode = (Node*)malloc(sizeof(Node));if (newNode == NULL) {printf("内存分配失败!\n");exit(1);}newNode->data = data;newNode->next = NULL;return newNode;
}// 头插法:新节点插入在链表头部
Node* addAtHead(Node *head, int data) {Node *newNode = createNode(data);newNode->next = head; // 新节点指向原头节点return newNode;       // 新节点成为新的头节点
}

c. 指定位置插入(难点!需要使用二级指针或返回头指针)

方法一:返回新的头指针(适用于可能在头部插入的情况)

Node* insertNode(Node *head, int index, int data) {if (index < 0) return head; // 索引无效if (index == 0) { // 在头部插入return addAtHead(head, data);}Node *current = head;// 找到要插入位置的前一个节点(index-1)for (int i = 0; current != NULL && i < index - 1; i++) {current = current->next;}if (current == NULL) { // 索引超出链表长度printf("索引超出范围\n");return head;}Node *newNode = createNode(data);newNode->next = current->next; // 新节点指向原位置节点current->next = newNode;       // 前驱节点指向新节点return head;
}

方法二:使用二级指针(更优雅,避免返回值处理)

// 函数参数是 Node**,即指向头指针的指针
void insertNode(Node **headRef, int index, int data) {if (index < 0) return;Node *newNode = createNode(data);if (index == 0) {newNode->next = *headRef;*headRef = newNode; // 直接修改传入的头指针return;}Node *current = *headRef;for (int i = 0; current != NULL && i < index - 1; i++) {current = current->next;}if (current == NULL) {printf("索引超出范围\n");free(newNode); // 记得释放创建失败的节点return;}newNode->next = current->next;current->next = newNode;
}
// 调用方式:insertNode(&head, 1, 100); // 传递head的地址

为什么用二级指针? 因为C语言是值传递。如果我们想在函数内部修改调用者的一个指针变量(比如头指针head),就必须传递这个指针变量的地址(即二级指针)。

d. 删除节点(同样需要二级指针或返回头指针)

// 方法一:返回头指针
Node* deleteNode(Node *head, int index) {if (head == NULL || index < 0) return head;if (index == 0) {Node *temp = head;head = head->next;free(temp);return head;}Node *current = head;for (int i = 0; current != NULL && i < index - 1; i++) {current = current->next;}if (current == NULL || current->next == NULL) {printf("索引超出范围\n");return head;}Node *temp = current->next; // 要删除的节点current->next = temp->next; // 绕过要删除的节点free(temp);return head;
}

4. 链表 vs 数组(必考简答题)

特性 数组 链表
内存分配 静态连续,编译时确定大小 动态离散,运行时动态分配
大小调整 固定大小,不易扩展 灵活,易于扩展和收缩
访问效率 O(1) 随机访问,通过下标直接定位 O(n) 顺序访问,必须从头遍历
插入/删除 O(n),需要移动大量元素 O(1)(已知位置时),只需修改指针
空间开销 小,只有数据本身 大,每个节点额外包含指针域
缓存友好性 好,数据连续,缓存命中率高 差,数据分散,缓存命中率低

结论:

  • 数组适用于查询多、增删少,且数据量大小可预知的场景。
  • 链表适用于频繁增删、数据量变化大的场景。无法使用二分查找

综合例题与练习

1. 计算结构体大小(实战)

#pragma pack(2)
struct Example {char a;int b;short c;double d;
};

问:sizeof(struct Example) 是多少?(假设sizeof(double)=8
解答: pack(2),最大对齐模数为2。

  • a(偏移0,大小1)
  • b需要对齐到min(4,2)=2的倍数,偏移2(填充1字节),大小4(偏移2~5)
  • c需要对齐到min(2,2)=2的倍数,偏移6是2的倍数,大小2(偏移6~7)
  • d需要对齐到min(8,2)=2的倍数,偏移8是2的倍数,大小8(偏移8~15)
  • 总大小=16,是最大对齐模数2的倍数。所以结果是16

2. 链表反转(经典面试/考题)
要求: 写出反转链表的函数。

// 迭代法(推荐)
Node* reverseList(Node *head) {Node *prev = NULL;Node *current = head;Node *next = NULL;while (current != NULL) {next = current->next; // 保存下一个节点current->next = prev; // 当前节点指向前一个,实现反转prev = current;       // prev和current一起后移current = next;}return prev; // 循环结束时,prev是新的头节点
}// 递归法(理解思路)
Node* reverseListRecursive(Node *head) {if (head == NULL || head->next == NULL) {return head;}Node *newHead = reverseListRecursive(head->next);head->next->next = head;head->next = NULL;return newHead;
}

3. 查找链表中间节点(快慢指针法)

Node* findMiddle(Node *head) {if (head == NULL) return NULL;Node *slow = head;Node *fast = head;while (fast != NULL && fast->next != NULL) {slow = slow->next;       // 慢指针走一步fast = fast->next->next; // 快指针走两步}return slow; // 当快指针走到末尾时,慢指针就在中间
}

希望这份超详细的总结和例题能帮助你彻底掌握这些知识点!祝你考试顺利!

http://www.jsqmd.com/news/189176/

相关文章:

  • 你的远程NAS随时会失联?这份“永不掉线”配置指南请收好
  • Mac软件管理的终极解决方案:Applite图形化工具完全指南
  • 基于BMO磁性细菌优化的WSN网络最优节点部署算法matlab仿真
  • WorkshopDL完整教程:轻松下载Steam创意工坊模组的跨平台利器
  • 成年人的选择题没有对错,祝好运
  • 单相锁相环。 采用simlink仿真嵌C语言实现锁相环,整个仿 单相锁相环。 采用simlin...
  • OpenCore Legacy Patcher完整教程:让旧Mac焕发新生的终极指南
  • Diaphora二进制差异分析工具实战指南
  • Sunshine游戏串流完整指南:3步打造个人云游戏平台
  • AI赋能原则6解读思考:深度专业、跨界能力与软件协同的复合竞争力-AI时代的人才新逻辑
  • 【课程设计/毕业设计】复杂背景下卷积神经网络在森林火灾识别中的研究与应用
  • 重新定义硬件监控:hwinfo跨平台解决方案的终极指南
  • 鸿蒙开源阅读:打造你的专属数字书房,优化阅读体验
  • Windows 11绕过硬件限制终极指南:3种简单方法让老电脑重获新生
  • KeymouseGo自动化操作神器:彻底解放双手的智能助手
  • NSC_BUILDER:任天堂Switch文件处理全能工具深度解析
  • WorkshopDL深度解析:突破平台限制的终极Steam模组下载方案
  • FFXIV ACT动画跳过插件完整使用指南:5分钟快速上手终极教程
  • 深度解密Diaphora编译单元分析核心技术
  • WorkshopDL终极指南:如何轻松实现Steam创意工坊跨平台下载
  • Unity游戏视觉优化终极指南:轻松解锁隐藏内容
  • 深入理解CSS弹性布局:构建现代响应式网页的
  • 【ESP32】 ESP32-C3 介绍
  • 终极自动化神器:3步掌握KeymouseGo高效工作法
  • 老电脑升级Windows 11的终极解决方案
  • 人机共生体:未来技术团队管理的10条新法则
  • ncmdumpGUI:打破网易云音乐格式限制的终极解决方案
  • 【ESP32】 Arduino 全面介绍
  • 城通网盘高速解析技术:专业级下载加速方案深度解析
  • 老旧Mac升级终极指南:突破限制重获macOS新系统体验