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

【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()实现无限扩容的特点。最后通过【内存分布】分析柔性数组和指针数组的不同点,得出了其优势所在。

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

请添加图片描述

相关文章:

【C语言】动态内存管理

1、为什么存在动态内存分配&#xff1f;2、动态内存管理函数介绍&#xff08;1&#xff09;malloc&#xff08;2&#xff09;free&#xff08;3&#xff09;calloc&#xff08;4&#xff09;realloc 3、常见动态内存错误&#xff08;1&#xff09;使用free释放动态内存开辟的一…...

Cocoa和Cocoa Touch是什么语言写成的?什么是Cocoa?编程语言中什么是框架?为什么苹果公司Cocoa类库有不少NS前缀?Swift编程语言?

Cocoa和Cocoa Touch是什么语言写成的? 二者主要都是用Objective-C语言编写而成的。 什么是Cocoa? Cocoa是苹果操作系统macOS和iOS上的应用程序开发框架集合&#xff0c;核心语言是Objective-C编程语言&#xff0c;在移动平台被称为Cocoa Touch&#xff0c;Cocoa包含多个子框架…...

Qt Creator 中使用 vcpkg

Qt Creator 中使用 vcpkg Qt Creator 是一个跨平台的轻量级 IDE&#xff0c;做 Qt 程序开发的同学们肯定对这个 IDE 都比较属于。这个 IDE 虽然没有 Visual Stdio 功能那么强&#xff0c;但是由于和 Qt 集成的比较深&#xff0c;用来开发 Qt 程序还是很顺手的。 早期&#xf…...

Python GUI 开发 | PySide6 PyQt6 学习手册

本文是个 Python GUI 开发的目录&#xff0c;方便读者系统性学习的&#xff0c;笔者后续会满满填充此目录中的内容&#xff0c;感兴趣的小伙伴可以关注一手。&#xff08;主要是偏向 PySide6 方向的&#xff09; 0x01&#xff1a;PySide6 & PyQt6 基础入门 0x0101&#xff…...

Xposed-Hook

配置 Xposed 模块的 AndroidManifest.xml&#xff1a; <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android"package"your.package.name"><applicationandr…...

鸿蒙物流项目之基础结构

目录&#xff1a; 1、项目结构2、三种包的区别和使用场景3、静态资源的导入4、颜色样式设置5、修改项目名称和图标6、静态包基础目录7、组件的抽离8、在功能模块包里面引用静态资源包的组件 1、项目结构 2、三种包的区别和使用场景 3、静态资源的导入 放在har包中&#xff0c;那…...

CSS 中调整元素大小的全面指南

CSS 中调整元素大小的全面指南 1. 原始尺寸&#xff08;固有尺寸&#xff09;示例代码&#xff1a;图像的固有尺寸 2. 设置具体的尺寸示例代码&#xff1a;设置固定宽度和高度 3. 使用百分比示例代码&#xff1a;使用百分比设置宽度 4. 使用百分比作为外边距和内边距示例代码&a…...

文字投影效果

大家好&#xff0c;我是喝西瓜汁的兔叽&#xff0c;今天给大家分享一个常见的文字投影效果。 效果展示 我们来实现一个这样的文字效果。 思路分析 这样的效果如何实现的呢? 实际上是两组相同的文字&#xff0c;叠合在一块&#xff0c;只不过对应的css不同罢了。 首先&…...

【Redis】set 和 zset 类型的介绍和常用命令

1. set 1.1 介绍 set 类型和 list 不同的是&#xff0c;存储的元素是无序的&#xff0c;并且元素不允许重复&#xff0c;Redis 除了支持集合内的增删查改操作&#xff0c;还支持多个集合取交集&#xff0c;并集&#xff0c;差集 1.2 常用命令 命令 介绍 时间复杂度 sadd …...

Clion开发STM32时使用stlink下载程序与Debug调试

一、下载程序 先创建一个文件夹&#xff1a; 命名&#xff1a;stlink.cfg 写入以下代码: # choose st-link/j-link/dap-link etc. #adapter driver cmsis-dap #transport select swdsource [find interface/stlink.cfg]transport select hla_swdsource [find target/stm32f4x.…...

HarmonyOS:给您的应用添加通知

一、通知介绍 通知旨在让用户以合适的方式及时获得有用的新消息&#xff0c;帮助用户高效地处理任务。应用可以通过通知接口发送通知消息&#xff0c;用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应用&#xff0c;通知主要有以下使用场景&#xff1a; 显示…...

scrape登录(js逆向)

url:链接 首先登录抓包 可以 看到token进行了加密 可以直接去全局搜索token 可以看到在这里进行了加密 进入encode&#xff0c;处理from后进行加密 首先这一段复制js&#xff0c;缺什么补什么&#xff0c; code cb_utob function(e) {if (e.length < 2) {var r e.cha…...

后台管理系统通用页面抽离=>高阶组件+配置文件+hooks

目录结构 配置文件和通用页面组件 content.config.ts const contentConfig {pageName: "role",header: {title: "角色列表",btnText: "新建角色"},propsList: [{ type: "selection", label: "选择", width: "80px&q…...

RDP协议详解

以下内容包含对 RDP&#xff08;Remote Desktop Protocol&#xff0c;远程桌面协议&#xff09;及其开源实现 FreeRDP 的较为系统、深入的讲解&#xff0c;涵盖协议概要、历史沿革、核心原理、安全机制、安装与使用方法、扩展与未来发展趋势等方面&#xff0c; --- ## 一、引…...

Spring Boot - 数据库集成06 - 集成ElasticSearch

Spring boot 集成 ElasticSearch 文章目录 Spring boot 集成 ElasticSearch一&#xff1a;前置工作1&#xff1a;项目搭建和依赖导入2&#xff1a;客户端连接相关构建3&#xff1a;实体类相关注解配置说明 二&#xff1a;客户端client相关操作说明1&#xff1a;检索流程1.1&…...

虚幻UE5手机安卓Android Studio开发设置2025

一、下载Android Studio历史版本 步骤1&#xff1a;虚幻4.27、5.0、5.1、5.2官方要求Andrd Studio 4.0版本&#xff1b; 5.3、5.4、5.5官方要求的版本为Android Studio Flamingo | 2022.2.1 Patch 2 May 24, 2023 虚幻官网查看对应Andrd Studiob下载版本&#xff1a; https:/…...

Flutter开发环境配置

下载 Flutter SDK 下载地址&#xff1a;https://docs.flutter.cn/get-started/install M1/M2芯片选择带arm64字样的Flutter SDK。 解压 cd /Applications unzip ~/Downloads/flutter_macos_arm64_3.27.3-stable.zip执行 /Applications/flutter/bin/flutterManage your Flut…...

[免费]微信小程序智能商城系统(uniapp+Springboot后端+vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序智能商城系统(uniappSpringboot后端vue管理端)&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序智能商城系统(uniappSpringboot后端vue管理端) Java毕业设计_哔哩哔哩_bilibili 项目介绍…...

rust操作pgsql、mysql和sqlite

rust中&#xff0c;有很多技术可以操作pgsql、mysql和sqlite&#xff0c;以sqlx为主流技术。我们以sqlx操作sqlite为示例&#xff0c;操作pgsql和mysql的办法是一样的。 Cargo.toml: [package] name "test" version "0.1.0" edition "2021"…...

5. 【Vue实战--孢子记账--Web 版开发】-- 主页UI

我们在实现个人中心的时候简单的搭建了一个主页UI&#xff0c;但是这个主页并不是我们需要的&#xff0c;在这一节我们将一起实现主页UI的搭建。 一、功能 主页UI的原型如下: 首页UI原型包括左侧菜单和顶部header&#xff0c;左侧菜单包含多个功能模块的链接&#xff1a;首页…...

RabbitMQ5-死信队列

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

使用 PyTorch 实现逻辑回归并评估模型性能

1. 逻辑回归简介 逻辑回归是一种用于解决二分类问题的算法。它通过一个逻辑函数&#xff08;Sigmoid 函数&#xff09;将线性回归的输出映射到 [0, 1] 区间内&#xff0c;从而将问题转化为概率预测问题。如果预测概率大于 0.5&#xff0c;则将样本分类为正类&#xff1b;否则分…...

Rust 函数使用详解

Rust 函数使用详解 函数是 Rust 程序的基本构建块之一。通过函数&#xff0c;我们可以将代码组织成可重用的模块。本文将从函数签名语法、函数参数、语句与表达式、函数返回值等角度详细介绍 Rust 函数的使用&#xff0c;并通过综合示例展示这些知识点的实际应用。 1. 函数签名…...

ARM TEE

在ARM的语境中&#xff0c;TEE是Trusted Execution Environment&#xff08;可信执行环境&#xff09;的缩写。ARM TEE就是基于ARM架构实现的可信执行环境&#xff0c;以下是具体介绍&#xff1a; 定义与原理 定义&#xff1a;ARM TEE是基于独立硬件&#xff0c;和主操作系统…...

Miniconda 安装及使用

文章目录 前言1、Miniconda 简介2、Linux 环境说明2.1、安装2.2、配置2.3、常用命令2.4、常见问题及解决方案 前言 在 Python 中&#xff0c;“环境管理”是一个非常重要的概念&#xff0c;它主要是指对 Python 解释器及其相关依赖库进行管理和隔离&#xff0c;以确保开发环境…...

解析 Oracle 中的 ALL_SYNONYMS 和 ALL_VIEWS 视图:查找同义词与视图的基础操作

目录 前言1. ALL_SYNONYMS 视图2. ALL_VIEWS 视图3. 扩展 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 1. ALL_SYNONYMS 视图 在 Oracle 数据库中&#xff0c;同义词&#xff08;Synonym&#xff09;是对数…...

C#面向对象(封装)

1.什么是封装? C# 封装 封装 被定义为“把一个或多个项目封闭在一个物理的或者逻辑的包中”。 在面向对象程序设计方法论中&#xff0c;封装是为了防止对实现细节的访问。 抽象和封装是面向对象程序设计的相关特性。 抽象允许相关信息可视化&#xff0c;封装则使开发者实现所…...

【深度分析】DeepSeek大模型技术解析:从架构到应用的全面探索

深度与创新&#xff1a;AI领域的革新者 DeepSeek&#xff0c;这个由幻方量化创立的人工智能公司推出的一系列AI模型&#xff0c;不仅在技术架构上展现出了前所未有的突破&#xff0c;更在应用领域中开启了无限可能的大门。从其混合专家架构&#xff08;MoE&#xff09;到多头潜…...

【新春特辑】2025年1月科技浪潮中的AI最新时事与科技趋势

2025年1月科技浪潮中的AI最新时事与科技趋势 一、AI科技时事 人工智能代理&#xff08;AI Agent&#xff09;的发展 最新进展&#xff1a;人工智能代理正逐步成为科技领域的新热点。这些代理能够自主执行特定任务&#xff0c;如管理日程、回复邮件等。然而&#xff0c;它们仍…...

使用PyTorch实现逻辑回归:从训练到模型保存与性能评估

1. 引入必要的库 首先&#xff0c;需要引入必要的库。PyTorch用于构建和训练模型&#xff0c;pandas和numpy用于数据处理&#xff0c;scikit-learn用于计算性能指标。 import torch import torch.nn as nn import torch.optim as optim import pandas as pd import numpy as …...

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.24 随机宇宙:生成现实世界数据的艺术

1.24 随机宇宙&#xff1a;生成现实世界数据的艺术 目录 #mermaid-svg-vN1An9qZ6t4JUcGa {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-vN1An9qZ6t4JUcGa .error-icon{fill:#552222;}#mermaid-svg-vN1An9qZ6t4JUc…...

C#面试常考随笔8:using关键字有哪些用法?

1. using 指令&#xff1a;引入命名空间 最常用的用法。通过using 命名空间名字&#xff0c;可以在程序中直接使用该命名空间中的类型&#xff0c;而无需指定类型的完整命名空间路径。例如&#xff1a; using System; using System.Collections.Generic; class Program {sta…...

lstm代码解析1.2

在使用 LSTM&#xff08;长短期记忆网络&#xff09;进行训练时&#xff0c;model.fit 方法的输入数据 X 和目标数据 y 的形状要求是不同的。具体来说&#xff1a; 1. 输入数据 X 的形状 LSTM 层期望输入数据 X 是三维张量&#xff0c;形状为 (samples, timesteps, features)…...

JavaScript系列(52)--编译优化技术详解

JavaScript编译优化技术详解 &#x1f680; 今天&#xff0c;让我们深入探讨JavaScript的编译优化技术。通过理解和应用这些技术&#xff0c;我们可以显著提升JavaScript代码的执行效率。 编译优化基础概念 &#x1f31f; &#x1f4a1; 小知识&#xff1a;JavaScript引擎通常…...

【Python】第七弹---Python基础进阶:深入字典操作与文件处理技巧

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【MySQL】【Python】 目录 1、字典 1.1、字典是什么 1.2、创建字典 1.3、查找 key 1.4、新增/修改元素 1.5、删除元素 1.6、遍历…...

【狂热算法篇】探秘图论之Dijkstra 算法:穿越图的迷宫的最短路径力量(通俗易懂版)

羑悻的小杀马特.-CSDN博客羑悻的小杀马特.擅长C/C题海汇总,AI学习,c的不归之路,等方面的知识,羑悻的小杀马特.关注算法,c,c语言,青少年编程领域.https://blog.csdn.net/2401_82648291?typebbshttps://blog.csdn.net/2401_82648291?typebbshttps://blog.csdn.net/2401_8264829…...

Electricity Market Optimization 探索系列(一)

​ 本文参考链接&#xff1a;Linear Programming Mini Example 先从一个线性规划的例子说起&#xff1a; 问题背景&#xff1a; 现在需要使用两台发电机满足用户的用电需求&#xff0c;发电机一的发电功率上限是 6MW&#xff0c;发电机二的发电功率上限是 4MW&#xff0c;发电…...

<iframe>标签和定时调用函数setInterval

iframe 标签和定时调用函数 setInterval 问题描述&#xff1a;解决方法&#xff1a; 问题描述&#xff1a; 今天遇到一个前端问题&#xff0c;在浏览器页面上传Excel文件后&#xff0c;然后点击导入按钮&#xff0c;经后端Java类读取文件内容校验后&#xff0c;将校验结果返回…...

网工_HDLC协议

2025.01.25&#xff1a;网工老姜学习笔记 第9节 HDLC协议 9.1 HDLC高级数据链路控制9.2 HDLC帧格式&#xff08;*控制字段&#xff09;9.2.1 信息帧&#xff08;承载用户数据&#xff0c;0开头&#xff09;9.2.2 监督帧&#xff08;帮助信息可靠传输&#xff0c;10开头&#xf…...

Elasticsearch:如何搜索含有复合词的语言

作者&#xff1a;来自 Elastic Peter Straer 复合词在文本分析和标记过程中给搜索引擎带来挑战&#xff0c;因为它们会掩盖词语成分之间的有意义的联系。连字分解器标记过滤器等工具可以通过解构复合词来帮助解决这些问题。 德语以其长复合词而闻名&#xff1a;Rindfleischetik…...

【Go语言圣经】第六节:方法

第六章&#xff1a;方法 6.1 方法声明 在函数声明时&#xff0c;在其名字之前放上一个变量&#xff0c;这就是声明了变量对应类型的一个方法&#xff0c;相当于为这种类型定义了一个独占的方法。 下例为 Point 类型声明了计算两个点之间距离的方法&#xff1a; package mai…...

[答疑]DDD伪创新哪有资格和仿制药比

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 远航 2025-1-24 10:40 最近的热门话题仿制药&#xff0c;想到您经常批评的伪创新&#xff0c;这两者是不是很像&#xff1f; UMLChina潘加宇 伪创新哪有资格和仿制药比。 仿制药的…...

MySQL(高级特性篇) 13 章——事务基础知识

一、数据库事务概述 事务是数据库区别于文件系统的重要特性之一 &#xff08;1&#xff09;存储引擎支持情况 SHOW ENGINES命令来查看当前MySQL支持的存储引擎都有哪些&#xff0c;以及这些存储引擎是否支持事务能看出在MySQL中&#xff0c;只有InnoDB是支持事务的 &#x…...

javascript常用函数大全

javascript函数一共可分为五类&#xff1a; •常规函数 •数组函数 •日期函数 •数学函数 •字符串函数 1.常规函数 javascript常规函数包括以下9个函数&#xff1a; (1)alert函数&#xff1a;显示一个警告对话框&#xff0c;包括一个OK按钮。 (2)confirm函数&#xff1a;显…...

第26节课:内容安全策略(CSP)—构建安全网页的防御盾

目录 CSP基础CSP的作用CSP的主要属性 配置CSP通过响应头配置CSP通过HTML <meta>标签配置CSP属性设置详解指定多个来源 配置示例说明 常见错误配置实践&#xff1a;CSP与XSS防护示例1&#xff1a;防止内联脚本和样式说明示例2&#xff1a;限制图片来源说明 限制与注意事项…...

【大坑】使用element-ui弹窗$confirm自动弹出

插入element-ui的弹窗后页面一刷新自动弹出&#xff0c;事件绑定、调用位置&#xff08;生命周期&#xff09;均没有问题&#xff0c;通过不断注释组件发现是main.js全局引入导致的问题。如果需要在某些组件中使用三方弹窗&#xff0c;可以按需引入&#xff0c;而不是全局注册 …...

Spring的AOP思想中事物管理注意点

我们以事务管理实现AOP思想 通过在Service层加入事务管理,因为Service层可能使用多个DAO(多条SQL语句) 要保证这些SQL要么同时成功,要么同时失败,例如:学生Serivce:删除学生的时候,还需要删除学生关联信息(选课信息) 只有都删除成功才提交,如果有一条执行失败…...

PHP Mail:高效邮件发送解决方案详解

PHP Mail&#xff1a;高效邮件发送解决方案详解 引言 在互联网时代&#xff0c;邮件作为最常用的沟通方式之一&#xff0c;已经成为企业和个人不可或缺的通讯工具。PHP作为一种流行的服务器端脚本语言&#xff0c;在邮件发送方面具有天然的优势。本文将详细介绍PHP Mail&…...

python recv的概念和使用案例

recv 是网络编程中用于从套接字接收数据的核心函数&#xff0c;常见于 TCP/UDP 通信。以下是其概念、用法和案例详解&#xff1a; 概念 作用&#xff1a;从已连接&#xff08;TCP&#xff09;或已绑定&#xff08;UDP&#xff09;的套接字接收数据。参数&#xff1a; bufsize:…...

安卓(android)读取手机通讯录【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可在代码地址查看&#xff09; 1.熟悉内容提供者(Content Provider)的概念和作用。 2.掌握内容提供者的创建和使用方法。 4.掌握内容URI的结构和用途。 二、实验条件 1.熟悉内容提供者的工作原理。 2.掌握内容提供者访问其…...