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

C语言再学习—内存,链表

一、变量与指针

1.volatile

由于待会检查内存地址时候用到volatile,故在此介绍一下。

volatile 是 C/C++ 中的一个类型修饰符,用于告诉编译器该变量的值可能以编译器无法预测的方式被改变(如硬件自动更新、多线程共享等),从而禁止编译器对该变量进行优化。

核心作用

  1. 禁止编译器优化
    编译器通常会对变量访问进行优化(如缓存到寄存器、重排序),但 volatile 会强制每次访问都直接读写内存。

  2. 保证内存可见性
    确保每次读取变量时都获取内存中的最新值,而非缓存副本。

2.测试程序:

 在文件中查找.map文件查看到一下地址:

    a                                        0x20000000   Data           4  main.o(.data)
    c                                        0x20000004   Data           1  main.o(.data)
    p1                                       0x20000008   Data           4  main.o(.data)
    p2                                       0x2000000c   Data           4  main.o(.data)
    buf                                      0x20000010   Data         100  main.o(.bss)

结论:变量,能读能写,在内存里面

           指针,保存的是地址,在32位处理器中,地址都是32位,无论什么类型的指针变量,都是4字节

3.malloc

malloc(memory allocation 的缩写 ,即内存分配)是一个用于动态内存分配的标准库函数,其函数原型为:void* malloc(size_t size);

malloc 函数用于在堆(heap)内存中分配指定大小的连续内存块,并返回一个指向该内存块起始地址的指针。它主要用于在程序运行时,根据实际需求动态地申请内存,而不是在编译时就确定好内存大小,这在处理一些大小不确定的数据(如根据用户输入决定长度的数组、链表节点等)时非常有用。

例如,要分配一个能存储 10 个 int 类型数据的内存块,因为在常见系统中 int 类型占 4 个字节,所以可以这样调用:malloc(10 * sizeof(int));,使用 sizeof(int) 而不是直接写 4,是为了增强代码的可移植性,因为不同系统中 int 类型的字节数可能不同。

返回值

malloc 函数返回一个 void* 类型的指针,指向分配好的内存块的起始地址。如果内存分配失败(比如系统没有足够的空闲内存 ),则返回 NULL 。因此在使用返回的指针前,通常需要检查它是否为 NULL ,以避免出现野指针引用导致程序崩溃。

使用完内存后,通过 free 函数释放该内存块(free 是与 malloc 配套使用的函数,用于释放 malloc 分配的内存 ),并将指针赋值为 NULL ,防止成为野指针。

示例代码:

#include <stdio.h>
#include <stdlib.h>int main() {// 分配一个能存储5个int类型数据的内存块int *ptr = (int *)malloc(5 * sizeof(int)); if (ptr == NULL) {printf("内存分配失败\n");return 1;}// 使用分配的内存for (int i = 0; i < 5; i++) {ptr[i] = i + 1;printf("%d ", ptr[i]);}// 释放内存,防止内存泄漏free(ptr); ptr = NULL; return 0;
}

4.结构体里的字节

当在结构体当中定义这些变量时 ,按常理来说person3所占字节为1+4=5,person4所占字节为1+1+4=6。但是现实不是,如上图,为了效率,在person3时候sex前面三个字节是浪费掉,空出来的。在person4定义的时候,既然有high了,那么就少浪费一个。使得整个sex\high\age打包起来成为一个整体。这样person4所占字节为8,person3也是8。

接着来看下图: 

在结构体里面定义了两个char还有一个int(如表格中所画),当有一个int类型的指针给c赋值时。d原本应该显示的B被吞没了,这是因为 int *p是一个4字节的大小,不能因为只存一个字符'C'就不用四个字节的空间了。

二、链表

定义链表:

#include <stdio.h>
// 正确定义结构体
typedef struct _node {char value;struct _node *next;
} Node;

定义了一个名为spy的结构体类型,并使用typedef为它创建了两个别名。

  • char value;:存储一个字符串
  • struct _node *next:指向同类型结构体的指针,用于链表结构中连接下一个节点

   typedef ... spy; // 为struct spy创建别名spy typedef ...

头部插入链表:


假设原链表为 1 -> 2 -> NULL,头指针 head 指向节点 1。现在要在头部插入一个节点0,

最终链表变为:0 -> 1 -> 2 -> NULL。

分析:1.首先需要建立一个新节点。总不能凭空出现节点吧,所以就写一个创建节点的程序,哪怕后面要用到,直接拿过来用。

// 创建新节点
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));//malloc返回一个sizeof大小的指针,//所以newNode是一个Node复制粘贴类型的指针 if (newNode == NULL) {                      //检测是否分配成功,如果newNode的地址值为NULL,则没有分配成功 printf("内存分配失败!\n");exit(EXIT_FAILURE);                     //直接终止程序 }newNode->data = data;                       //填入数据 newNode->next = NULL;                       //暂时设置没有下个节点 return newNode;                             
}

(注释已经很详细,在此不做分析)

2.插入的时候,首先定义新的节点,传入数据。再将0节点的末尾指向1,再更新头节点,让它指向0

// 在链表头部插入节点
void insertAtHead(Node** head_ref, int data) {   //Node**head_ref代表是指向头指针的指针。//因此传入头指针的地址(二级指针)。int data:要插入的新节点的数据值。Node* newNode = createNode(data);           //定义新节点 newNode->next = *head_ref;                   //*head_ref 目前是原始头指针的值(即原头节点的地址)//此行将新节点的 next 指针指向原头节点,从而将新节点插入到链表头部。 *head_ref = newNode;                        //修改原始头指针的值,使其指向新节点。此时新节点成为链表的第一个节点。
}
/* 示例:链表插入过程
假设原链表为 1 -> 2 -> NULL,头指针 head 指向节点 1。
1.调用 insertAtHead(&head, 0):head_ref 是 &head(即头指针的地址)。创建新节点 0,其 next 初始为 NULL。
2.执行 newNode->next = *head_ref:*head_ref 是 head 的值(即节点 1 的地址)。新节点 0 的 next 指向节点 1。
3.执行 *head_ref = newNode:head 的值被更新为新节点 0 的地址。最终链表变为:0 -> 1 -> 2 -> NULL。*/ 

 若是不添加*head_ref = newNode; ,则状态如下图,head还是指向1。

 

尾部插入链表:

// 在链表尾部插入节点
void insertAtTail(Node** head_ref, int data) {Node* newNode = createNode(data);               //定义新节点 if (*head_ref == NULL) {                        //处理链表为空的情况 ,newNode就是唯一节点 *head_ref = newNode;return;}Node* current = *head_ref;                       // 定义头节点,然后用while来实现遍历,找到NULL while (current->next != NULL) {                 //判断本节点是否不指向NULL current = current->next;                   //若是节点不指向NULL,则找到下一个节点 }current->next = newNode;                       //若是指向NULL则说明现在的current->next是原来的最后一个节点。//因为在原先创建新节点的时候,指向地址就是NULL所以这里的newNode不需要再指向NULL 
}

 

 删除指定值的第一个节点:

// 删除指定值的第一个节点
void deleteNode(Node** head_ref, int key) {   //参数1:链表头节点的指针的指针  参数2:需要删除的节点的值 Node* temp = *head_ref;Node* prev;if (temp != NULL && temp->data == key) {   //如果一上来就发现temp非空并且它被指定到了 *head_ref = temp->next;                //那么指定头节点为temp的下一位,相当于吧temp孤立在最前面了 free(temp);                            //释放temp return;                                //直接返回 }while (temp != NULL && temp->data != key) { //若是一开始temp不是目标值 prev = temp;                            //prev记录下当前temp temp = temp->next;                      //temp指向下一个地址,直到是目标值之后跳出循环 }if (temp == NULL) return;                 //如果为NULL说明不存在目标节点,直接返回 prev->next = temp->next;                  //先把前一个的next指向下下个的地址 free(temp);                               //释放temp 
}

分为两个部分:

1.头地址temp就是目标值

2.目标值在中间

整体代码: 

#include <stdio.h>
#include <stdlib.h>// 定义链表节点结构
typedef struct Node {int data;           // 数据域struct Node* next;  // 指针域,指向下一个节点
} Node;// 创建新节点
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));//malloc返回一个sizeof大小的指针,//所以newNode是一个Node复制粘贴类型的指针 if (newNode == NULL) {                      //检测是否分配成功,如果newNode的地址值为NULL,则没有分配成功 printf("内存分配失败!\n");exit(EXIT_FAILURE);                     //直接终止程序 }newNode->data = data;                       //填入数据 newNode->next = NULL;                       //此节点没有下个个节点 return newNode;                             
}// 在链表头部插入节点
void insertAtHead(Node** head_ref, int data) {   //Node**head_ref代表是指向头指针的指针。//因此传入头指针的地址(二级指针)。int data:要插入的新节点的数据值。Node* newNode = createNode(data);           //定义新节点 newNode->next = *head_ref;                   //*head_ref 目前是原始头指针的值(即原头节点的地址)//此行将新节点的 next 指针指向原头节点,从而将新节点插入到链表头部。 *head_ref = newNode;                        //修改原始头指针的值,使其指向新节点。此时新节点成为链表的第一个节点。
}
/* 示例:链表插入过程
假设原链表为 1 -> 2 -> NULL,头指针 head 指向节点 1。
1.调用 insertAtHead(&head, 0):head_ref 是 &head(即头指针的地址)。创建新节点 0,其 next 初始为 NULL。
2.执行 newNode->next = *head_ref:*head_ref 是 head 的值(即节点 1 的地址)。新节点 0 的 next 指向节点 1。
3.执行 *head_ref = newNode:head 的值被更新为新节点 0 的地址。最终链表变为:0 -> 1 -> 2 -> NULL。*/ // 在链表尾部插入节点
void insertAtTail(Node** head_ref, int data) {Node* newNode = createNode(data);               //定义新节点 if (*head_ref == NULL) {                        //处理链表为空的情况 ,newNode就是唯一节点 *head_ref = newNode;return;}Node* current = *head_ref;                       // 定义头节点,然后用while来实现遍历,找到NULL while (current->next != NULL) {                 //判断本节点是否不指向NULL current = current->next;                   //若是节点不指向NULL,则找到下一个节点 }current->next = newNode;                       //若是指向NULL则说明现在的current->next是原来的最后一个节点。//因为在原先创建新节点的时候,指向地址就是NULL所以这里的newNode不需要再指向NULL 
}// 删除指定值的第一个节点
void deleteNode(Node** head_ref, int key) {   //参数1:链表头节点的指针的指针  参数2:需要删除的节点的值 Node* temp = *head_ref;Node* prev;if (temp != NULL && temp->data == key) {   //如果一上来就发现temp非空并且它被指定到了 *head_ref = temp->next;                //那么指定头节点为temp的下一位,相当于吧temp孤立在最前面了 free(temp);                            //释放temp return;                                //直接返回 }while (temp != NULL && temp->data != key) { //若是一开始temp不是目标值 prev = temp;                            //prev记录下当前temp temp = temp->next;                      //temp指向下一个地址,直到是目标值之后跳出循环 }if (temp == NULL) return;                 //如果为NULL说明不存在目标节点,直接返回 prev->next = temp->next;                  //先把前一个的next指向下下个的地址 free(temp);                               //释放temp 
}// 遍历链表并打印节点值
void printList(Node* head) {Node* current = head;while (current != NULL) {printf("%d -> ", current->data);current = current->next;}printf("NULL\n");
}// 释放链表内存
void freeList(Node* head) {Node* temp;while (head != NULL) {temp = head;head = head->next;free(temp);}
}int main() {Node* head = NULL;  // 初始化空链表// 插入节点insertAtTail(&head, 1);  // 链表: 1 -> NULLinsertAtTail(&head, 2);  // 链表: 1 -> 2 -> NULLinsertAtHead(&head, 0);  // 链表: 0 -> 1 -> 2 -> NULLinsertAtTail(&head, 3);  // 链表: 0 -> 1 -> 2 -> 3 -> NULLprintf("初始链表: ");printList(head);// 删除节点deleteNode(&head, 1);  // 链表: 0 -> 2 -> 3 -> NULLprintf("删除节点1后: ");printList(head);// 释放链表freeList(head);head = NULL;  // 防止野指针return 0;
}

http://www.lqws.cn/news/602677.html

相关文章:

  • React Native 接入 eCharts
  • RocketMQ延迟消息是如何实现的?
  • 前端处理跨域的4种方式
  • 为什么js是单线程?
  • 转录组分析流程(七):GSEA分析
  • 安装emsdk 4.0.10报Connection reset by peer解决
  • OpenCV篇——项目(一)OCR识别读取银行卡号码
  • 内部类与Lambda的衍生关系(了解学习内部类,Lambda一篇即可)
  • Windows10/11 轻度优化 纯净版,12个版本!
  • 【分治思想】归并排序 与 逆序对
  • Nginx重定向协议冲突解决方案:The plain HTTP request was sent to HTTPS port
  • CertiK《Hack3d:2025年第二季度及上半年Web3.0安全报告》(附报告全文链接)
  • OEM怎么掌握软件开发能力
  • 记本好书:矩阵力量:线性代数全彩图解+微课+Python编程
  • Python OrderedDict 用法详解
  • 学习昇腾开发的第11天--主要接口调用流程
  • CMU-15445(6)——PROJECT#2-BPlusTree-Task#1
  • 记一次Ubuntu22安装MongoDB8并同步本地数据过程
  • 应急响应类题练习——玄机第四章 windows实战-emlog
  • 微信小程序使用秋云ucharts echarts
  • 高阶数据结构------并查集
  • 数据结构day7——文件IO
  • STM32——存储器映射(Memory mapping)
  • 反向传播 梯度消失
  • OSE3.【Linux】练习:编写进度条及pv命令项目中的进度条函数
  • 07CSRF 漏洞保护
  • vite项目中引入tailwindcss,难倒AI的操作
  • Modbus协议
  • 数字图像处理学习笔记
  • Spring IOC容器核心阶段解密:★Bean实例化全流程深度剖析★