江哥带你玩转C语言 - 16-内存管理和链表
Posted 极客江南
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了江哥带你玩转C语言 - 16-内存管理和链表相关的知识,希望对你有一定的参考价值。
内存管理
进程空间
- 程序,是经源码编译后的可执行文件,可执行文件可以多次被执行,比如我们可以多次打开 office。
- 而进程,是程序加载到内存后开始执行,至执行结束,这样一段时间概念,多次打开的wps,每打开一次都是一个进程,当我们每关闭一个 office,则表示该进程结束。
- 程序是静态概念,而进程动态/时间概念。
###进程空间图示
有了进程和程序的概念以后,我们再来看一下,程序被加载到内存以后内存空间布局是什么样的
栈内存(Stack)
- 栈中存放任意类型的变量,但必须是 auto 类型修饰的,即自动类型的局部变量, 随用随开,用完即消。
- 内存的分配和销毁系统自动完成,不需要人工干预
- 栈的最大尺寸固定,超出则引起栈溢出
- 局部变量过多,过大 或 递归层数太多等就会导致栈溢出
int ages[10240*10240]; // 程序会崩溃, 栈溢出
#include <stdio.h>
int main()
{
// 存储在栈中, 内存地址从大到小
int a = 10;
int b = 20;
printf("&a = %p\\n", &a); // &a = 0060FEAC
printf("&b = %p\\n", &b); // &b = 0060FEA8
return 0;
}
堆内存(Heap)
- 堆内存可以存放任意类型的数据,但需要自己申请与释放
- 堆大小,想像中的无穷大,但实际使用中,受限于实际内存的大小和内存是否连续性
int *p = (int *)malloc(10240 * 1024); // 不一定会崩溃
#include <stdio.h>
#include <stdlib.h>
int main()
{
// 存储在栈中, 内存地址从小到大
int *p1 = malloc(4);
*p1 = 10;
int *p2 = malloc(4);
*p2 = 20;
printf("p1 = %p\\n", p1); // p1 = 00762F48
printf("p2 = %p\\n", p2); // p2 = 00762F58
return 0;
}
malloc函数
函数声明 | void * malloc(size_t _Size); |
---|---|
所在文件 | stdlib.h |
函数功能 | 申请堆内存空间并返回,所申请的空间并未初始化。 |
常见的初始化方法是 | memset 字节初始化。 |
参数及返回解析 | |
参数 | size_t _size 表示要申请的字符数 |
返回值 | void * 成功返回非空指针指向申请的空间 ,失败返回 NULL |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
/*
* malloc
* 第一个参数: 需要申请多少个字节空间
* 返回值类型: void *
*/
int *p = (int *)malloc(sizeof(int));
printf("p = %i\\n", *p); // 保存垃圾数据
/*
* 第一个参数: 需要初始化的内存地址
* 第二个初始: 需要初始化的值
* 第三个参数: 需要初始化对少个字节
*/
memset(p, 0, sizeof(int)); // 对申请的内存空间进行初始化
printf("p = %i\\n", *p); // 初始化为0
return 0;
}
free函数
- 注意: 通过malloc申请的存储空间一定要释放, 所以malloc和free函数总是成对出现
函数声明 | void free(void *p); |
---|---|
所在文件 | stdlib.h |
函数功能 | 释放申请的堆内存 |
参数及返回解析 | |
参数 | void* p 指向手动申请的空间 |
返回值 | void 无返回 |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// 1.申请4个字节存储空间
int *p = (int *)malloc(sizeof(int));
// 2.初始化4个字节存储空间为0
memset(p, 0, sizeof(int));
// 3.释放申请的存储空间
free(p);
return 0;
}
calloc函数
函数声明 | void *calloc(size_t nmemb, size_t size); |
---|---|
所在文件 | stdlib.h |
函数功能 | 申请堆内存空间并返回,所申请的空间,自动清零 |
参数及返回解析 | |
参数 | size_t nmemb 所需内存单元数量 |
参数 | size_t size 内存单元字节数量 |
返回值 | void * 成功返回非空指针指向申请的空间 ,失败返回 NULL |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
/*
// 1.申请3块4个字节存储空间
int *p = (int *)malloc(sizeof(int) * 3);
// 2.使用申请好的3块存储空间
p[0] = 1;
p[1] = 3;
p[2] = 5;
printf("p[0] = %i\\n", p[0]);
printf("p[1] = %i\\n", p[1]);
printf("p[2] = %i\\n", p[2]);
// 3.释放空间
free(p);
*/
// 1.申请3块4个字节存储空间
int *p = calloc(3, sizeof(int));
// 2.使用申请好的3块存储空间
p[0] = 1;
p[1] = 3;
p[2] = 5;
printf("p[0] = %i\\n", p[0]);
printf("p[1] = %i\\n", p[1]);
printf("p[2] = %i\\n", p[2]);
// 3.释放空间
free(p);
return 0;
}
realloc函数
函数声明 | void *realloc(void *ptr, size_t size); |
---|---|
所在文件 | stdlib.h |
函数功能 | 扩容(缩小)原有内存的大小。通常用于扩容,缩小会会导致内存缩去的部分数据丢失。 |
参数及返回解析 | |
参数 | void * ptr 表示待扩容(缩小)的指针, ptr 为之前用 malloc 或者 calloc 分配的内存地址。 |
参数 | size_t size 表示扩容(缩小)后内存的大小。 |
返回值 | void* 成功返回非空指针指向申请的空间 ,失败返回 NULL。 |
- 注意点:
- 若参数ptr==NULL,则该函数等同于 malloc
- 返回的指针,可能与 ptr 的值相同,也有可能不同。若相同,则说明在原空间后面申请,否则,则可能后续空间不足,重新申请的新的连续空间,原数据拷贝到新空间, 原有空间自动释放
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// 1.申请4个字节存储空间
int *p = NULL;
p = realloc(p, sizeof(int)); // 此时等同于malloc
// 2.使用申请好的空间
*p = 666;
printf("*p = %i\\n", *p);
// 3.释放空间
free(p);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// 1.申请4个字节存储空间
int *p = malloc(sizeof(int));
printf("p = %p\\n", p);
// 如果能在传入存储空间地址后面扩容, 返回传入存储空间地址
// 如果不能在传入存储空间地址后面扩容, 返回一个新的存储空间地址
p = realloc(p, sizeof(int) * 2);
printf("p = %p\\n", p);
// 2.使用申请好的空间
*p = 666;
printf("*p = %i\\n", *p);
// 3.释放空间
free(p);
return 0;
}
链表
- 链表实现了,内存零碎数据的有效组织。比如,当我们用 malloc 来进行内存申请的时候,当内存足够,但是由于碎片太多,没有连续内存时,只能以申请失败而告终,而用链表这种数据结构来组织数据,就可以解决上类问题。
##静态链表
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 1.定义链表节点
typedef struct node{
int data;
struct node *next;
}Node;
int main()
{
// 2.创建链表节点
Node a;
Node b;
Node c;
// 3.初始化节点数据
a.data = 1;
b.data = 3;
c.data = 5;
// 4.链接节点
a.next = &b;
b.next = &c;
c.next = NULL;
// 5.创建链表头
Node *head = &a;
// 6.使用链表
while(head != NULL){
int currentData = head->data;
printf("currentData = %i\\n", currentData);
head = head->next;
}
return 0;
}
动态链表
-
静态链表的意义不是很大,主要原因,数据存储在栈上,栈的存储空间有限,不能动态分配。所以链表要实现存储的自由,要动态的申请堆里的空间。
-
有一个点要说清楚,我们的实现的链表是带头节点。至于,为什么带头节点,需等大家对链表有个整体的的认知以后,再来体会,会更有意义。
-
空链表
- 头指针带了一个空链表节点, 空链表节点中的next指向NULL
- 头指针带了一个空链表节点, 空链表节点中的next指向NULL
#include <stdio.h>
#include <stdlib.h>
// 1.定义链表节点
typedef struct node{
int data;
struct node *next;
}Node;
int main()
{
Node *head = createList();
return 0;
}
// 创建空链表
Node *createList(){
// 1.创建一个节点
Node *node = (Node *)malloc(sizeof(Node));
if(node == NULL){
exit(-1);
}
// 2.设置下一个节点为NULL
node->next = NULL;
// 3.返回创建好的节点
return node;
}
- 非空链表
- 头指针带了一个非空节点, 最后一个节点中的next指向NULL
- 头指针带了一个非空节点, 最后一个节点中的next指向NULL
动态链表头插法
- 1.让新节点的下一个节点等于头结点的下一个节点
- 2.让头节点的下一个节点等于新节点
#include <stdio.h>
#include <stdlib.h>
// 1.定义链表节点
typedef struct node{
int data;
struct node *next;
}Node;
Node *createList();
void printNodeList(Node *node);
int main()
{
Node *head = createList();
printNodeList(head);
return 0;
}
/**
* @brief createList 创建链表
* @return 创建好的链表
*/
Node *createList(){
// 1.创建头节点
Node *head = (Node *)malloc(sizeof(Node));
if(head == NULL){
return NULL;
}
head->next = NULL;
// 2.接收用户输入数据
int num = -1;
printf("请输入节点数据\\n");
scanf("%i", &num);
// 3.通过循环创建其它节点
while(num != -1){
// 3.1创建一个新的节点
Node *cur = (Node *)malloc(sizeof(Node));
cur->data = num;
// 3.2让新节点的下一个节点指向头节点的下一个节点
cur->next = head->next;
// 3.3让头节点的下一个节点指向新节点
head->next = cur;
// 3.4再次接收用户输入数据
scanf("%i", &num);
}
// 3.返回创建好的节点
return head;
}
/**
* @brief printNodeList 遍历链表
* @param node 链表指针头
*/
void printNodeList(Node *node){
Node *head = node->next;
while(head != NULL){
int currentData = head->data;
printf("currentData = %i\\n", currentData);
head = head->next;
}
}
动态链表尾插法
- 1.定义变量记录新节点的上一个节点
- 2.将新节点添加到上一个节点后面
- 3.让新节点成为下一个节点的上一个节点
#include <stdio.h>
#include <stdlib.h>
// 1.定义链表节点
typedef struct node{
int data;
struct node *next;
}Node;
Node *createList();
void printNodeList(Node *node);
int main()
{
Node *head = createList();
printNodeList(head);
return 0;
}
/**
* @brief createList 创建链表
* @return 创建好的链表
*/
Node *createList(){
// 1.创建头节点
Node *head = (Node *)malloc(sizeof(Node));
if(head == NULL){
return NULL;
}
head->next = NULL;
// 2.接收用户输入数据
int num = -1;
printf("请输入节点数据\\n");
scanf("%i", &num);
// 3.通过循环创建其它节点
// 定义变量记录上一个节点
Node *pre = head;
while(num != -1){
// 3.1创建一个新的节点
Node *cur = (Node *)malloc(sizeof(Node));
cur->data = num;
// 3.2让新节点链接到上一个节点后面
pre->next = cur;
// 3.3当前节点下一个节点等于NULL
cur->next = NULL;
// 3.4让当前节点编程下一个节点的上一个节点
pre = cur;
// 3.5再次接收用户输入数据
scanf("%i", &num);
}
// 3.返回创建好的节点
return head;
}
/**
* @brief printNodeList 遍历链表
* @param node 链表指针头
*/
void printNodeList(Node *node){
Node *head = node->next;
while(head != NULL){
int currentData = head->data;
printf("currentData = %i\\n", currentData);
head = head->next;
}
}
动态链优化
#include <stdio.h>
#include <stdlib.h>
// 1.定义链表节点
typedef struct node{
int data;
struct node *next;
}Node;
Node *createList();
void printNodeList(Node *node);
void insertNode1(Node *head, int data);
void insertNode2(Node *head, int data);
int main()
{
// 1.创建一个空链表
Node *head = createList();
// 2.往空链表中插入数据
insertNode1(head, 1);
insertNode1(head, 3);
insertNode1(head, 5);
printNodeList(head);
return 0;
}
/**
* @brief createList 创建空链表
* @return 创建好的空链表
*/
Node *createList(){
// 1.创建头节点
Node *head = (Node *)malloc(sizeof(Node));
if(head == NULL){
return NULL;
}
head->next = NULL;
// 3.返回创建好的节点
return head;
}
/**
* @brief insertNode1 尾插法插入节点
* @param head 需要插入的头指针
* @param data 需要插入的数据
* @return 插入之后的链表
*/
void insertNode1(Node *head, int data){
// 1.定义变量记录最后一个节点
Node *pre = head;
while(pre != NULL && pre->next != NULL){
pre = pre->next;
}
// 2.创建一个新的节点
Node *cur = (Node *)malloc(sizeof(Node));
cur->data = data;
// 3.让新节点链接到上一个节点后面
pre->next = cur;
// 4.当前节点下一个节点等于NULL
cur->next = NULL;
// 5.让当前节点编程下一个节点的上一个节点
pre = cur;
}
/**
* @brief insertNode1 头插法插入节点
* @param head 需要插入的头指针
* @param data 需要插入的数据
* @return 插入之后的链表
*/
void insertNode2(Node *head, int data){
// 1.创建一个新的节点
Node *cur = (Node *)malloc(sizeof(Node));
cur->data = data;
// 2.让新节点的下一个节点指向头节点的下一个节点
cur->next = head->next;
// 3.让头节点的下一个节点指向新节点
head->next = cur;
}
/**
* @brief printNodeList 遍历链表
* @param node 链表指针头
*/
void printNodeList(Node *node){
Node *head = node->next;
while(head != NULL){
int currentData = head->data;
printf("currentData = %i\\n", currentData);
head = head->next;
}
}
链表销毁
/**
* @brief destroyList 销毁链表
* @param head 链表头指针
*/
void destroyList(Node *head){
Node *cur = NULL;
while(head != NULL){
cur = head->next;
free(head);
head = cur;
}
}
链表长度计算
/**
* @brief listLength 计算链表长度
* @param head 链表头指针
* @return 链表长度
*/
int listLength(Node *head){
int count = 0;
head = head->next;
while(head){
count++;
head = head->next;
}
return count;
}
链表查找
/**
* @brief searchList 查找指定节点
* @param head 链表头指针
* @param key 需要查找的值
* @return
*/
Node *searchList(Node *head, int key){
head = head->next;
while(head){
if(head->data == key){
break;
}else{
head = head->next;
}
}
return head;
}
链表删除
void deleteNodeList(Node *head, Node *find){
while(head->next != find){
head = head->next;
}
head->next = find->next;
free(find);
}
作业
- 给链表排序
/**
* @brief bubbleSort 对链表进行排序
* @param head 链表头指针
*/
void bubbleSort(Node *head){
// 1.计算链表长度
int len = listLength(head);
// 2.定义变量记录前后节点
Node *cur = NULL;
// 3.相邻元素进行比较, 进行冒泡排序
for(int i = 0; i < len - 1; i++){
cur = head->next;
for(int j = 0; j < len - 1 - i; j++){
printf("%i, %i\\n", cur->data, cur->next->data);
if((cur->data) > (cur->next->data)){
int temp = cur->data;
cur->data = cur->next->data;
cur->next->data = temp;
}
cur = cur->next;
}
}
}
/**
* @brief sortList 对链表进行排序
* @param head 链表头指针
*/
void sortList(Node *head){
// 0.计算链表长度
int len = listLength(head);
// 1.定义变量保存前后两个节点
Node *sh, *pre, *cur;
for(int i = 0; i < len - 1; i ++){
sh = head; // 头节点
pre = sh->next; // 第一个节点
cur = pre->next; // 第二个节点
for(int j = 0; j < len - 1 - i; j++){
if(pre->data > cur->data){
// 交换节点位置
sh->next = cur;
pre->next = cur->next;
cur->next = pre;
// 恢复节点名称
Node *temp = pre;
pre = cur;
cur = temp;
}
// 让所有节点往后移动
sh = sh->next;
pre = pre->next;
cur = cur->next;
}
}
}
- 链表反转
/**
* @brief reverseList 反转链表
* @param head 链表头指针
*/
void reverseList(Node *head){
// 1.将链表一分为二
Node *pre, *cur;
pre = head->next;
head->next = NULL;
// 2.重新插入节点
while(pre){
cur = pre->next;
pre->next = head->next;
head->next = pre;
pre = cur;
}
}
如果觉得文章对你有帮助,点赞、收藏、关注、评论,一键四连支持,你的支持就是江哥持续更新的动力。
以上是关于江哥带你玩转C语言 - 16-内存管理和链表的主要内容,如果未能解决你的问题,请参考以下文章