CDay09
动态内存分配
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。
相关函数

malloc函数
//原型:
void* malloc( size_t size );
//功能:
分配size字节未初始化的内存
//参数:
size - 要分配的字节数
//返回值:
成功时,返回指向新分配内存的指针。为避免内存泄漏,必须用 free() 或 realloc() 解分配返回的指针。
失败时,返回空指针。
calloc函数
//原型:
void* calloc( size_t num, size_t size );
//功能:
为 num 个对象的数组分配内存,并初始化所有分配存储中的字节为零。(申请size * num字节内存)
//参数:
num - 对象数目
size - 每个对象的大小
//返回值:
成功时,返回指向新分配内存的指针。为避免内存泄漏,必须用 free() 或 realloc() 解分配返回的指针。
失败时,返回空指针。
realloc函数
//原型:
void *realloc( void *ptr, size_t new_size );
//功能:
重新分配给定的内存区域。它必须是之前为 malloc() 、 calloc() 或 realloc() 所分配,并且仍未被 free 或 realloc 的调用所释放。
//参数:
ptr - 指向需要重新分配的内存区域的指针
new_size - 数组的新大小(字节数)
//返回值:
成功时,返回指向新分配内存的指针。返回的指针必须用 free() 或 realloc() 归还。原指针 ptr 被非法化,而且任何通过它的访问是未定义行为(即使重分配是就地的)。
失败时,返回空指针。原指针 ptr 保持有效,并需要通过 free() 或 realloc() 归还。
注意事项
1、ptr必须是以前通过malloc、realloc、calloc函数返回的指针
2、若重新分配内存在原有内存基础上缩小,则直接截断
3、若重新分配内存在原有内存基础上扩大,则优先尽力在原地扩容,如无法原地扩容,则重新在堆里找一块内存进行分配,并将原有数据复制过来后,将原来那片内存free掉。
有关内存的释放(free函数)
如果申请的内存没有被释放,会造成内存泄漏
//例子:
p = malloc(...);
q = malloc(...);
p = q;
内存泄漏:如果程序中存在垃圾,这种现象就叫做内存泄漏。
Q:如何避免内存泄漏? ---及时释放不再用到的内存空间
//原型:
void free( void *ptr );
//功能:
解分配之前由 malloc() 、 calloc()或 realloc() 分配的空间。
//参数:
ptr - 指向要解分配的内存的指针(malloc() 、 calloc()或 realloc()的返回值)
//返回值:
无
Q:free函数如何知道该释放多大的内存空间?
A:free释放的时候会根据传入的地址向前偏移4个字节 从这4字节获取具体的内存块大小并释放。 (实际上的实现很可能使用8字节做为头部:其中每四个字节分别标记大小和是否正在使用)
引入free函数后,可以减少内存泄漏,但是同时也引入了一个新的问题:悬空指针
p = malloc(...);
q = p;
free(p);
注意:
-
若 ptr 为空指针,则函数不进行操作。
-
若 ptr 的值不等于之前从 malloc() 、 calloc() 、 realloc() 或 aligned_alloc() (C11 起) 返回的值,则行为未定义。
-
若 ptr 所指代的内存区域已经被解分配,则行为未定义,即是说已经以ptr 为参数调用 free() 或 realloc() ,而且没有后继的 malloc() 、 calloc() 或 realloc() 调用以 ptr 为结果。
-
若在 free() 返回后通过指针 ptr 访问内存,则行为未定义(除非另一个分配函数恰好返回等于 ptr 的值)。
有关通用指针与空指针
void*:通用指针,可以指向任意类型的对象,同时也可以转换为其他类型的指针。
空指针:不能指向任何对象的指针(用宏NULL表示空指针,其值为0)。所以不能对空指针进行解引用,否则会报NULLPointer Exception(空指针异常)
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
示例
//拼接两个字符串,但是不改变其中任何一个字符串
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char* myStrcat(const char*, const char*);
int main(void)
{
char str1[] = "Hello ";
char str2[] = "World!";
char* resultStr = myStrcat(str1, str2);
puts(resultStr);
free(resultStr);
return 0;
}
char* myStrcat(const char* str1, const char* str2)
{
char* resultStr = malloc(strlen(str1) + strlen(str2) + 1); //1是'\0'
if (resultStr == NULL)
{
printf("Error: malloc failed in myStrcat!");
exit(1);
}
//内存分配成功
strcpy(resultStr, str1);
strcat(resultStr, str2);
return resultStr;
}
动态内存分配是链式结构的基础
链表:用一条 “链” 将所有结点串联起来的结构。
结点:数据域、指针域(存放另一个结点的地址)
链表的分类:
示例:
#include<stdio.h>
#include<stdlib.h>
typedef struct node {
int data;
struct node* next;
}Node;
Node* add_to_list(Node* list, int data);
int main(void)
{
Node* list = NULL;
list = add_to_list(list, 3); // 3
list = add_to_list(list, 2); // 2 --> 3
list = add_to_list(list, 1); // 1 --> 2 --> 3
while (list)
{
printf("%d ", list->data);
list = list->next;
}
return 0;
}
// 头插法
Node* add_to_list(Node* list, int data)
{
//申请空间
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL)
{
printf("malloc failed in add_to_list function!\n");
exit(1);
}
newNode->data = data;
newNode->next = list;
return newNode;
}
二级指针
原则:
如果想修改 指针变量 的值 --> 传递二级指针
如果想修改 指针变量指向的对象 的值 --> 传递一级指针
示例:
#include<stdio.h>
#include<stdlib.h>
typedef struct node {
int data;
struct node* next;
}Node;
void add_to_list(Node** list, int data);
int main(void)
{
Node* list = NULL;
add_to_list(&list, 3); // 3
add_to_list(&list, 2); // 2 --> 3
add_to_list(&list, 1); // 1 --> 2 --> 3
while (list)
{
printf("%d ", list->data);
list = list->next;
}
return 0;
}
// 头插法
void add_to_list(Node** plist, int data)
{
//申请空间
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL)
{
printf("malloc failed in add_to_list function!");
exit(1);
}
newNode->data = data;
newNode->next = *plist;
*plist = newNode;
}
函数指针
指向函数的指针
#include<stdio.h>
#include<math.h>
#define PI 3.1415926
double average(double (*f)(double), double a, double b);
int main(void)
{
double result = average(&sin, 0, PI); // 等价于 average(sin, 0, PI)
printf("%lf", result);
return 0;
}
double average(double (*f)(double), double a, double b)
{
return (*f)((a + b) / 2); // 等价于 f((a + b) / 2)
}
/*
* 注:函数指针与数组类似,函数名可以作为指针使用。
*/
qsort函数
-
如果comp返回值小于0(< 0),那么p1所指向元素会被排在p2所指向元素的左面;
-
如果comp返回值等于0(= 0),那么p1所指向元素与p2所指向元素的顺序不确定;
-
如果comp返回值大于0(> 0),那么p1所指向元素会被排在p2所指向元素的右面。
#include<stdio.h>
#define SIZE(a) (sizeof(a)/sizeof(a[0]))
typedef struct student {
int no;
char name[25];
int chinese;
int math;
int english;
}Student;
typedef struct node {
int data;
struct node* next;
} Node;
int cmp(const void*, const void*); //比较函数
int main(void)
{
Student students[5] = {
{3, "wangyuyan", 100, 90, 90},
{1, "liuyifei", 100, 100, 100},
{2, "zhaolinger", 90, 100, 100},
{5, "xiaolongnv", 90, 90, 100},
{4, "xixi", 90, 100, 90} };
qsort(students, SIZE(students), sizeof(students[0]), cmp);
return 0;
}
// 比较规则:先比较总分大小(逆序), 再比较语文成绩(逆序),再比较数学(逆序),比较英语成绩(逆序),比较学号(顺序)
int cmp(const void* p1, const void* p2) {
Student* s1 = p1;
Student* s2 = p2;
int total1 = s1->chinese + s1->math + s1->english;
int total2 = s2->chinese + s2->math + s2->english;
if (total1 != total2) {
return total2 - total1;
}
if (s1->chinese != s2->chinese) {
return s2->chinese - s1->chinese;
}
if (s1->math != s2->math) {
return s2->math - s1->math;
}
if (s1->english != s2->english) {
return s2->english - s1->english;
}
return s1->no - s2->no;
}
链表
单链表的基本操作
1、增(在某个结点的后面添加)--------------O(1)
newNode->next = cur->next;
cur->nex = newNode;
2、删(在某个结点后删除)--------------O(1)
cur->next = cur->next->next;
3、查
-
根据索引查找元素 ----------O(n)
-
查找链表与特定值相等的元素 ----------O(n)
-
元素大小有序 ----------O(n)
-
元素大小无序 ----------O(n)
-
双向链表的基本操作
1、增加(在某个结点前面添加) ----------O(1)
2、删除(删除该结点) ----------O(1)
3、查找
-
查找前驱结点 ----------O(1),单链表O(n)
-
根据索引查找值 ----------O(n)
- 单链表:平均遍历 n/2
- 双向链表:平均遍历 n/4
-
查找链表中与特定值相等的元素,有序无序都是O(n)
比较单链表与双向链表
- 双向链表会占用更多的内存空间
- 双向链表的性能会优于单链表
在实际工程中,一般会使用双向链表
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了