【C语言】动态内存管理

    • 1、为什么存在动态内存分配?
    • 2、动态内存管理函数介绍
      • (1)malloc
      • (2)free
      • (3)calloc
      • (4)realloc
    • 3、常见动态内存错误
      • (1)使用free释放动态内存开辟的一部分空间
      • (2)对同一块动态开辟内存多次释放
      • (3)向堆区申请空间后忘记释放
    • 4、笔试题分析
      • (1)题目一
      • (2)题目二
    • 5、柔性数组
      • (1)概念和声明
      • (2)特点
      • (3)使用场景
      • (4)优缺点分析
    • 6、总结和回顾

1、为什么存在动态内存分配?

❔首先,让我们思考一下,为什么需要动态内存管理,它有什么优势吗?

我们当前使用的内存开辟方式

int var;//在栈区开辟四个字节
char str[10];//在栈区开辟10个字节的连续内存单元
  • 通过观察可以发现,上面开辟空间的方式有以下特点:
  1. 开辟空间大小固定
  2. 必须在声明时给定大小,在编译时分配内存

❔那么这样的特点会造成什么样的缺陷呢?

  • 例如我需要统计班上n名同学的期末考试成绩,对于这种情况,我们不知道需要开辟多少空间以供使用,如果开辟空间少了会造成数组越界,开辟多了会造成空间浪费。
#include<stdio.h>
#include<stdlib.h>int stu[30];//空间大小固定,当数据量超过开辟空间大小,会造成数组越界
int main()
{int n; scanf("%d", &n);for (int i = 0; i < n; i++){int score; scanf("%d", &score);stu[i] = score;}return 0;
}

☑️对于这种情况,就需要动态内存管理出手了。

2、动态内存管理函数介绍

在这里,我们将介绍malloccallocrealloc三个函数,以及内存释放函数free

(1)malloc

  • mallocmemory allocation的简写,意为内存分配。

  • 让我们来看看malloc函数的函数原型文档信息

函数原型

void* malloc (size_t size);

🔸返回类型void*
🔸参数类型size_t(无符号整形)

文档信息

在这里插入图片描述

  • 通过阅读函数原型和文档信息,我们可以得出以下特性。
  1. malloc函数返回类型为void*,不能识别开辟空间的类型,因此需要用户自主决定类型。

✔️这里我们通常会使用强制类型转换来决定开辟空间的类型

  • 例如这里我想开辟10个整形的连续内存空间单元,int*malloc开辟的内存空间进行强制类型转换。
int *a = (int*)malloc(10);

这10个内存空间都存储了什么呢,malloc有对其进行初始化操作吗?
👇🏼那么就引出了第二条特性。

  1. malloc函数只能分配内存空间,并不能对内存空间的元素初始化。
  • 可以看到malloc成功开辟出10个int类型的内存空间单元,但开辟的空间元素均为随机值
    在这里插入图片描述
    ✔️因此对于malloc申请的空间,需要我们手动初始化。
#include<stdio.h>
#include<stdlib.h>int main()
{//开辟十个整形大小的内存空间int* a = (int*)malloc(sizeof(int)*10);//手动初始化for (int i = 0; i < 10; i++){*(a + i) = i + 1;}return 0;
}
  • 调试可以看到我们开辟的10个整形内存空间存放了10个整形数据
    请添加图片描述

那么问题来了,malloc是否会申请空间失败呢,失败了又会作何处理呢?

  • 可以看到,当我们申请-1内存空间大小单元,a指针接收的地址为0x0000000000000000,是一个空地址,表示malloc申请空间失败。
    在这里插入图片描述

👇🏼那么就引出了第三条特性。

  1. 如果内存空间开辟成功,则返回该内存空间的起始位置;否则返回空指针NULL,注意该空指针不应该被解引用。

✔️正因为会有开辟失败的情况,因此需要对malloc的返回值进行检查,避免解引用空指针

  • 这里使用perror函数,用于将错误信息输出到标准设备(stderr),方便开发者定位错误。
int *a = (int*)malloc(10);
if (a == NULL)
{perror("malloc fail!");exit(-1);
}
  • 例如开辟-1内存空间大小的整形空间,可以看到输出窗口打印了错误信息。
    在这里插入图片描述

有同学会问,什么情况malloc开辟内存空间会失败呢?
⚫️开辟内存空间过大
开辟的内存空间超过了当前环境(编译器版本,程序的内存寻址64位、32位)下堆的最大可分配内存。

⚫️内存碎片导致无法开辟大空间
长时间使用动态内存分配和释放可能导致内存碎片,使得虽然总体上有足够的内存,但是无法找到足够大的连续内存块。

⚫️开辟内存空间大小不合法
例如开辟空间大小为0的内存空间。

  1. 如果参数 size0malloc的行为是标准是未定义的,取决于编译器。
  • 对于这种情况,我们来测试一下。请添加图片描述
    ✔️可以看到,虽然没有申请到任何空间,但是指针a不为NULL,即malloc仍然返回了一块地址。(不同的编译器会有不同的结果,博主使用的是vs2022)
  1. malloc开辟的内存空间会在程序结束时自动归还给操作系统。

那么问题来了,如果我在程序中使用后不再需要这块内存空间,没有立即释放会导致什么问题呢

  • 内存泄漏(Memory Leak):内存泄漏是指程序在申请内存后,未能在不再需要时正确释放,导致这部分内存无法被再次使用。随着程序的运行,内存泄漏会逐渐累积,可能导致可用内存减少。
  • 程序性能下降:如果内存泄漏严重,程序可能会因为可用内存不足而频繁触发内存分配和回收(这里主要是realloc的分配回收),这会降低程序的性能。

❔有没有在使用完毕后立即释放该空间的方法呢?
👇🏼那么就引出了free函数来解决这个问题。

(2)free

函数原型

void free (void* ptr);

🔸返回类型void
🔸参数类型void*

文档信息

在这里插入图片描述

  • 通过阅读函数原型和文档信息,我们可以得出以下特性。
  1. 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的
  • 观察下面代码,我们尝试用free去释放一个在栈上分配的内存。
#include<stdio.h>
#include<stdlib.h>int main()
{int a = 1;int* b = &a;if (a == NULL){perror("malloc fail!");exit(-1);}free(b);return 0;
}
  • 可以看到,这里出现了断言错误,错误信息指出表达式 _CrtIsValidHeapPointer(block) 失败,其中 block 是传递给 free 函数的指针。

☑️因为局部变量a是在栈上分配的,不是通过malloccallocrealloc在堆上分配的。因此a在程序结束时会自动被编译器回收,不应该让开发者手动释放
在这里插入图片描述

  1. 如果传入的指针是空指针,free函数将不做任何操作。
  2. free函数无法修改指针本身指向内存块的地址。

我们来测试一下

  • 可以看到,当指针afree函数释放后,初始化的1~10变为了随机值,但指针a指向的仍然是被分配内存块的地址。

✔️使用free函数释放指针a指向的内存块,则这块空间归还给了操作系统,因此被初始化的1~10变为了随机值,但是指针a仍然指向原来内存块的地址,这意味着a指针变为了”野指针“。这是很不安全的。
请添加图片描述
☑️为了解决问题,开发者应当手动给a指针置空,这样就能避免野指针的产生。

a=NULL;
  • 置空后就不会指向原内存块的地址了
    在这里插入图片描述

🔹需要注意的是,mallocfree函数都在头文件stdlib.h中,记得要包含头文件哦

  • 到这里,我们就可以解决上面的给n个学生统计成绩的问题了。
#include<stdio.h>
#include<stdlib.h>//int stu[30];//空间大小固定,当数据量超过开辟空间大小,会造成数组越界
int main()
{int n;scanf("%d", &n);//分配n个整形大小的内存空间int* stu = (int*)malloc(sizeof(int) * n);if (stu == NULL){perror("malloc fail!");exit(-1);}for (int i = 0; i < n; i++){int score; scanf("%d", &score);stu[i] = score;}//释放内存块free(stu);//指针置空,防止出现野指针stu = NULL;return 0;
}

🔴我们来小结一下,使用malloc分配内存要记得判空,预防malloc申请失败,使用完这块空间后要记得将其归还给操作系统,使用free函数释放内存空间,最后给指针置空,防止出现野指针。

(3)calloc

函数原型

void* calloc (size_t num, size_t size);

返回类型void*
参数类型size_t(无符号整形)
num为要开辟内存块中的元素个数size为每个元素分配的字节数

文档信息
在这里插入图片描述
特性

  1. num个字节数为size的元素开辟一块内存,所有元素初始化为0。
  2. malloc功能不同的地方在于calloc可以将内存块初始化为0。

我们来测试一下

请添加图片描述

(4)realloc

函数原型】:

void* realloc (void* ptr, size_t size);

返回类型void*
参数类型void*size_t(无符号整形)
ptr要调整的内存地址size是调整之后的新大小

文档信息
在这里插入图片描述

特性

  1. 可用于重新分配原内存块的大小(有可能把内存移动到一个新位置)
  2. 如果传入的指针为空指针,realloc()的作用和malloc()一致
  3. 如果需要缩小内存块大小,则在原内存块释放一部分空间

请添加图片描述

🟤扩容机制

  • 本地扩容
    当前内存块的后面就有足够的空间可以扩容,此时直接在后面续上新的空间即可

  • 异地扩容
    当后边没有足够的空间可以扩容realloc函数会找一个满足空间大小的新的连续空间。把旧的空间的数据,拷贝到新空间的前面的位置,并且把旧的空间释放掉(无需手动释放),同时返回新的空间的地址

在这里插入图片描述

注意:如果扩容失败了,relloc会返回一个NULL指针,因此当扩容失败我们不能在其上面赋值。这里要加个判断,别忘了把tmp指针置空。

int* tmp=(int*)relloc(p,sizeof(int)*20);if(tmp == NULL)
{perror("relloc fail!");exit(-1);
}p = tmp;
tmp = NULL;

3、常见动态内存错误

(1)使用free释放动态内存开辟的一部分空间

#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(sizeof(int) * 100);for (int i = 0; i < 100; i++){*p = i;}int* origin = p;for (int i = 0; i < 10; i++) {p++;}free(p);return 0;
}

在这里插入图片描述

  • 为什么当p指针偏移后再使用free释放会出现报错呢?
    ✔️这是因为free函数需要做到申请多少空间就释放多少空间,因此释放了一部分空间后,就会超出申请的空间外,造成内存访问错误。

(2)对同一块动态开辟内存多次释放

#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(sizeof(int) * 100);free(p);free(p);return 0;
}

在这里插入图片描述

  • 可以看到,当对同一块内存块释放多次时出现了报错。这是为什么呢?
    ✔️这是因为当调用第一次free()时,指针p指向的内存块的数据已经还给操作系统了,此时p指针就成为了一个野指针,当我们再次使用free()释放p时就会造成释放【野指针】的错误
#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(sizeof(int) * 100);free(p);p = NULL;free(p);return 0;
}

☑️这里我们只需要给p指针置空即可,这样就不会出现报错了

(3)向堆区申请空间后忘记释放

#include<stdio.h>void test() 
{int* p = (int*)malloc(sizeof(int) * 100);}int main()
{test();return 0;
}
  • 观察上面代码,可以发现主函数调用了test函数,test函数中向堆区申请了100个整形大小的空间,但是并没有释放,那么这就会造成【内存泄漏】的问题。
#include<stdio.h>void test() 
{int* p = (int*)malloc(sizeof(int) * 100);free(p);p = NULL;
}int main()
{test();return 0;
}

☑️这里我们只需要在test函数中释放p指向的内存块,再给p指针置空即可

4、笔试题分析

(1)题目一

#include<stdio.h>
#include<stdlib.h>char* GetMemory(void)
{char p[] = "hello world";return p;
}void Test(void)
{char* str = NULL;str = GetMemory();//为什么会销毁?//因为 p指针 出了函数栈帧后会销毁,此时p指针指向的内存块中的内容呗操作系统回收,因此打印出错printf(str);
}int main()
{Test();return 0;
}
  • 观察以上代码,其中有一个错误,分析错误的地方和原因。

✔️分析可知这是栈空间地址问题Test()函数中调用了GetMemory()函数并p字符数组的首元素地址,此时str指针接收了p数组的首元素地址,但是GetMemory()函数调用结束后,局部变量p字符数组出了函数作用域,被操作系统回收,p数组的内容被清除,因此打印str时出现错误。

请添加图片描述

(2)题目二

#include<stdio.h>
#include<stdlib.h>void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}int main()
{Test();return 0;
}
  • 观察以上代码,其中有一个错误,分析错误的地方和原因。

✔️分析可知,这段代码是可以编译运行打印出world的,但是这是非法的,因为str指向一块动态开辟内存的空间,使用free()函数释放该空间后,str指向内存块的空间被销毁了,但str指针仍然指向被原来的内存块。接下来的操作就是非法的了,我们向一个已经被释放的空间插入字符串,这就造成了【非法访问内存】问题。

☑️正确做法是给str指针置空即可

#include<stdio.h>
#include<stdlib.h>void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);//问题:没有置空//虽然运行没有出错但是 其实是非法访问//正确做法:加上置空str = NULL;if (str != NULL){strcpy(str, "world");printf(str);}
}int main()
{Test();return 0;
}

5、柔性数组

(1)概念和声明

柔性数组,也称为伸缩性数组成员,是C99标准中引入的一种结构体特性。它允许在结构体定义的最后一个元素声明一个未知大小(可动态分配)的数组,使得结构体能够处理可变长度的数据

🟤柔性数组的声明有两种形式

  • 结构体最后一个成员为 数组名[0] 的形式
typedef struct st_type
{int x;int a[0];
}type_a;
  • 结构体最后一个成员为 数组名[] 的形式

✔️两种柔性数组的声明方式不同,在某些编译器支持int a[0]的写法,而有些则支持int a[]的写法,具体取决于编译器的实现

(2)特点

  1. 柔性数组必须是结构体的最后一个成员
    在这里插入图片描述

  2. 柔性数组前面必须至少一个其他成员

  • 即结构体本身必须要有空间,否则将无法通过结构体找到柔性数组,那么就无法为柔性数组分配空间。
typedef struct st_type
{int a[];
}type_a;
  1. 柔性数组不占内存空间
  • 前面我们学过了内存对齐,知道了计算结构体内存空间时每个成员都是计算在内的。
  • 但观察下图,可以发现柔性数组不参与结构体大小的计算
    在这里插入图片描述

(3)使用场景

  • 使用柔性数组首先要开辟结构体空间随后再去给柔性数组开辟空间
type_a* s = (type_a*)malloc(sizeof(type_a)+sizeof(int) * 10);

其中sizeof(type_a)是结构体空间,sizeof(int)*10是为柔性数组开辟的十个整形空间。

注意:分配的内存应该大于结构体的大小,以适应柔性数组的预期大小

  • 下面我们来测试一下柔性数组是否被正确声明,并给其初始化。
#include<stdio.h>
#include<stdlib.h>typedef struct st_type
{int x;int a[];
}type_a;int main()
{//开辟结构体空间,并给柔性数组10个整形空间type_a* s = (type_a*)malloc(sizeof(type_a)+sizeof(int) * 10);if (s == NULL) {perror("malloc fail");exit(-1);}s->x = 99;for (int i = 0; i < 10; i++) {s->a[i] = i + 1;}for (int i = 0; i < 10; i++) {printf("%d ", s->a[i]);}printf("\n");free(s);return 0;
}

在这里插入图片描述

✔️通过观察运行结果可以发现,柔性数组被正确声明和初始化。(和普通数组的使用区别不大)

🟠此外,柔性数组可以动态改变大小,那么我们就可以使用所学的realloc()去实现动态扩容操作。

type_a* tmp = (type_a*)realloc(s,sizeof(type_a) + sizeof(int) * 20);
if (s == NULL) {perror("malloc fail");exit(-1);
}
  • 给开辟的空间初始化并打印

在这里插入图片描述
❕需要注意的是,柔性数组的大小不计入结构体的空间总大小
在这里插入图片描述

❔那么,要如何正确释放柔性数组开辟的空间呢?
☑️使用free(s)即可。这是因为柔性数组的内存是与结构体的其他部分连续分配的,因此无法单独释放柔性数组的空间(即free(s->a)是错误的)

柔性数组的内存和结构体的内存是不可分割的,它们共享同一块内存区域。

🟤让我们分析一下这块内存的布局

  • 前面是结构体的非柔性数组部分int x
  • 后面是柔性数组部分int a[]

这块内存是一个整体,free()只能释放整个分配的内存块,而不能单独释放其中的某一个部分。

☑️因此,如果需要单独管理其中的空间,我们可以使用指针数组替换柔性数组,方便更灵活的管理内存

#include <stdio.h>
#include <stdlib.h>typedef struct st_type
{int x;int* a; // 使用指针代替柔性数组
} type_a;int main()
{// 分配结构体空间type_a* s = (type_a*)malloc(sizeof(type_a));if (s == NULL) {perror("malloc fail");exit(-1);}// 分配柔性数组的空间s->a = (int*)malloc(sizeof(int) * 10);if (s->a == NULL) {perror("malloc fail");free(s); // 释放结构体空间exit(-1);}s->x = 99;// 给柔性数组赋值for (int i = 0; i < 10; i++) {s->a[i] = i + 1;}// 输出柔性数组的值for (int i = 0; i < 10; i++) {printf("%d ", s->a[i]);}printf("\n");// 释放柔性数组的空间free(s->a);// 释放结构体的空间free(s);return 0;
}

🔴如果想在某些情况共享内存,在另一些情况分开管理内存,这个时候可以使用联合体union来实现

#include <stdio.h>
#include <stdlib.h>typedef struct st_type
{int x;//使用联合体将指针和柔性数组联合union {int a[]; // 柔性数组int* ptr; // 指针} u;
} type_a;int main()
{// 分配结构体空间,并给柔性数组10个整形空间type_a* s = (type_a*)malloc(sizeof(type_a) + sizeof(int) * 10);if (s == NULL) {perror("malloc fail");exit(-1);}s->x = 99;// 使用柔性数组for (int i = 0; i < 10; i++) {s->u.a[i] = i + 1;}// 输出柔性数组的值for (int i = 0; i < 10; i++) {printf("%d ", s->u.a[i]);}printf("\n");// 释放整个结构体空间free(s);// 如果需要单独管理内存,可以这样type_a* s2 = (type_a*)malloc(sizeof(type_a));if (s2 == NULL) {perror("malloc fail");exit(-1);}s2->u.ptr = (int*)malloc(sizeof(int) * 10);if (s2->u.ptr == NULL) {perror("malloc fail");free(s2);exit(-1);}// 使用指针for (int i = 0; i < 10; i++) {s2->u.ptr[i] = i + 1;}// 输出指针数组的值for (int i = 0; i < 10; i++) {printf("%d ", s2->u.ptr[i]);}printf("\n");// 释放指针数组的空间free(s2->u.ptr);// 释放结构体的空间free(s2);return 0;
}

(4)优缺点分析

🟤柔性数组对比我们常用的指针数组有什么不同呢?

  1. 柔性数组
//开辟结构体空间和柔性数组空间
type_a* s = (type_a*)malloc(sizeof(type_a) + sizeof(int) * 10);
if (s == NULL) {perror("malloc fail");exit(-1);
}
//同时释放结构体空间和柔性数组空间
free(s);

✔️可以看到,柔性数组使用了一次malloc,一次free

  1. 指针数组
//开辟结构体空间
type_a* s = (type_a*)malloc(sizeof(type_a));
if (s == NULL) {perror("malloc fail");exit(-1);
}//开辟结构体指针数组空间
int* tmp = (int*)malloc(sizeof(int) * 10);//把这块空间给指针数组
s->a = tmp;
//初始化
for (int i = 0; i < 10; i++) {s->a[i] = i + 1;
}//打印
for (int i = 0; i < 10; i++) {printf("%d ", s->a[i]);
}printf("\n");
//由于内存地址不连续,因此需要分开释放
free(s->a);
free(s);

✔️可以看到,指针数组使用了两次malloc,两次free

  1. 内存分布对比

柔性数组在这里插入图片描述

指针数组
在这里插入图片描述

🟢因此使用柔性数组的主要优势有以下两点
☑️ 方便内存申请和释放
当我们不需要单独管理结构体中数组的内存大小时,使用柔性数组可以使代码更清晰简洁,无需多次对内存进行操作,实现内存管理的简化
☑️ 提高代码运行速度
结构体和数组的内存是连续分配的,释放内存时只需要一次 free 操作,避免了多次分配和释放内存导致的内存碎片问题,访问柔性数组的元素时,内存访问更加高效,减少了缓存不命中的可能性。

6、总结和回顾

最后我们来回顾一下本文所学

  • 首先,我们讲解了三个动态分配内存函数,分别是只负责开辟空间malloc(),可以开辟空间又可以分配字节calloc(),可以重新分配内存块realloc()以及用于释放内存free()函数。
  • 了解这几个函数还不够,如何正确的使用它们很关键。于是总结了三个常见动态内存分配错误情况,方便我们使用时规避它们。
  • 学会了使用它们,就要来几道题目练练手!我们可以通过笔试题加深对它们的理解,了解使用细节。
  • 最后我们谈到了【柔性数组】,了解它的声明,释放等使用细节,以及配合realloc()实现无限扩容的特点。最后通过【内存分布】分析柔性数组和指针数组的不同点,得出了其优势所在。

好,这就是本文的全部内容,感谢阅读🌹

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/10427.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实验八 JSP访问数据库

实验八 JSP访问数据库 目的&#xff1a; 1、熟悉JDBC的数据库访问模式。 2、掌握使用My SQL数据库的使用 实验要求&#xff1a; 1、通过JDBC访问mysql数据&#xff0c;实现增删改查功能的实现 2、要求提交实验报告&#xff0c;将代码和实验结果页面截图放入报告中 实验过程&a…

RabbitMQ5-死信队列

目录 死信的概念 死信的来源 死信实战 死信之TTl 死信之最大长度 死信之消息被拒 死信的概念 死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;一般来说&#xff0c;producer 将消息投递到 broker 或直接到queue 里了&#xff0c;consumer 从 queue 取出消息进…

【项目初始化】

项目初始化 使用脚手架创建项目Vite创建项目推荐拓展 使用脚手架创建项目 Vite Vite 是一个现代的前端构建工具&#xff0c;它提供了极速的更新和开发体验&#xff0c;支持多种前端框架&#xff0c;如 Vue、React 等创建项目 pnpm create vuelatest推荐拓展

一文读懂 Faiss:开启高维向量高效检索的大门

一、引言 在大数据与人工智能蓬勃发展的当下&#xff0c;高维向量数据如潮水般涌现。无论是图像、音频、文本&#xff0c;还是生物信息领域&#xff0c;都离不开高维向量来精准刻画数据特征。然而&#xff0c;在海量的高维向量数据中进行快速、准确的相似性搜索&#xff0c;却…

基于Django的Boss直聘IT岗位可视化分析系统的设计与实现

【Django】基于Django的Boss直聘IT岗位可视化分析系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统采用Python作为主要开发语言&#xff0c;利用Django这一高效、安全的W…

python 语音识别

目录 一、语音识别 二、代码实践 2.1 使用vosk三方库 2.2 使用SpeechRecognition 2.3 使用Whisper 一、语音识别 今天识别了别人做的这个app,觉得虽然是个日记app 但是用来学英语也挺好的,能进行语音识别,然后矫正语法,自己说的时候 ,实在不知道怎么说可以先乱说,然…

栈和队列特别篇:栈和队列的经典算法问题

图均为手绘,代码基于vs2022实现 系列文章目录 数据结构初探: 顺序表 数据结构初探:链表之单链表篇 数据结构初探:链表之双向链表篇 链表特别篇:链表经典算法问题 数据结构:栈篇 数据结构:队列篇 文章目录 系列文章目录前言一.有效的括号(leetcode 20)二.用队列实现栈(leetcode…

使用 OpenResty 构建高效的动态图片水印代理服务20250127

使用 OpenResty 构建高效的动态图片水印代理服务 在当今数字化的时代&#xff0c;图片在各种业务场景中广泛应用。为了保护版权、统一品牌形象&#xff0c;动态图片水印功能显得尤为重要。然而&#xff0c;直接在后端服务中集成水印功能&#xff0c;往往会带来代码复杂度增加、…

C++并行化编程

C并行化编程 C 简介 C 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言&#xff0c;支持过程化编程、面向对象编程和泛型编程。 C 被认为是一种中级语言&#xff0c;它综合了高级语言和低级语言的特点。 C 是由 Bjarne Stroustrup 于 1979 年在新泽西州美…

Java开发vscode环境搭建

1 几个名词 JDK Java Development Kit JRE Java Runtion Environment JVM JDK 包括 Compiler,debugger,JRE等。JRE包括JVM和Runtime Library。 2 配置环境 2.1 安装JDK 类比 C/C的 g工具 官网&#xff1a;https://www.oracle.com/java/technologies/downloads/ 根据自己使…

pytorch基于FastText实现词嵌入

FastText 是 Facebook AI Research 提出的 改进版 Word2Vec&#xff0c;可以&#xff1a; ✅ 利用 n-grams 处理未登录词 比 Word2Vec 更快、更准确 适用于中文等形态丰富的语言 完整的 PyTorch FastText 代码&#xff08;基于中文语料&#xff09;&#xff0c;包含&#xff1…

riscv xv6学习笔记

文章目录 前言util实验sleeputil实验pingpongutil实验primesxv6初始化代码分析syscall实验tracesyscall实验sysinfoxv6内存学习笔记pgtbl实验Print a page tablepgtbl实验A kernel page table per processxv6 trap学习trap实验Backtracetrap实验Alarmlazy实验Lazy allocationxv…

FFmpeg(7.1版本)编译:Ubuntu18.04交叉编译到ARM

一、本地编译与交叉编译 1.本地编译 ① 本地编译&#xff1a;指的是在目标系统上进行编译的过程 , 生成的可执行文件和函数库只能在目标系统中使用。 如 : 在 Ubuntu中&#xff0c;本地编译的可执行文件只能在Ubuntu 系统中执行 , 无法在 Windows / Mac / Android / iOS 系…

创新创业计划书|建筑垃圾资源化回收

目录 第1部分 公司概况........................................................................ 1 第2部分 产品/服务...................................................................... 3 第3部分 研究与开发.................................................…

如何利用天赋实现最大化的价值输出

这种文章&#xff0c;以我现在的实力很难写出来。所以需要引用一些视频。 上92高校容易吗 如果基于天赋努力&#xff0c;非常容易。 如果不是这样&#xff0c;非常非常难。 高考失败人生完蛋&#xff1f;复读考上交大&#xff0c;进入社会才发现学历只是一张纸&#xff0c;98…

LigerUI在MVC模式下的响应原则

LigerUI是基于jQuery的UI框架&#xff0c;故他也是遵守jQuery的开发模式&#xff0c;但是也具有其特色的侦听函数&#xff0c;那么当LigerUI作为View层的时候&#xff0c;他所发送后端的必然是表单的数据&#xff0c;在此我们以俩个div为例&#xff1a; {Layout "~/View…

【力扣】49.字母异位词分组

AC截图 题目 思路 由于互为字母异位词的两个字符串包含的字母相同&#xff0c;因此对两个字符串分别进行排序之后得到的字符串一定是相同的&#xff0c;故可以将排序之后的字符串作为哈希表的键。 可以遍历strs&#xff0c;将其中每一个str排序&#xff0c;然后用unodered_ma…

docker安装nacos2.2.4详解(含:nacos容器启动参数、环境变量、常见问题整理)

一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull nacos:2.2.4 2、离线包下载 两种方式&#xff1a; 方式一&#xff1a; -&#xff09;在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -&#xff09;导出 # 导出镜像到…

【图床配置】PicGO+Gitee方案

【图床配置】PicGOGitee方案 文章目录 【图床配置】PicGOGitee方案为啥要用图床图床是什么配置步骤下载安装PicGoPicGo配置创建Gitee仓库Typora中的设置 为啥要用图床 在Markdown中&#xff0c;图片默认是以路径的形式存在的&#xff0c;类似这样 可以看到这是本地路径&#x…

【C++】类与对象(下)

&#x1f984; 个人主页: 小米里的大麦-CSDN博客 &#x1f38f; 所属专栏: 小米里的大麦——C专栏_CSDN博客 &#x1f381; 代码托管: 小米里的大麦的Gitee仓库 ⚙️ 操作环境: Visual Studio 2022 文章目录 1. 再谈构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit 关键…