【C语言】—— 动态内存管理
【C语言】——动态内存管理
- 一、动态内存管理概述
- 1.1、动态内存的概念
- 1.2、动态内存的必要性
- 二、 m a l l o c malloc malloc 函数
- 2.1、函数介绍
- 2.2、应用举例
- 三、 c a l l o c calloc calloc 函数
- 四、 f r e e free free 函数
- 4.1、函数介绍
- 4.2、应用举例
- 五、 r e a l l o c realloc realloc 函数
- 5.1、函数介绍
- 5.2、应用举例
- 六、常见的动态内存错误
- 6.1、对NULL指针进行解引用
- 6.2、对动态开辟空间的越界访问
- 6.3、对非动态开辟的内存使用 f r e e free free 释放
- 6.4、使用 f r e e free free 释放一块动态开辟内存的一部分
- 6.5、对同一块动态内存多重释放
- 6.6、动态开辟内存忘记释放(内存泄漏)
- 七、动态内存经典笔试题分析
- 7.1、题一
- 7.2、题二
- 7.3、题三
- 7.4、题四
- 八、柔性数组
- 8.1、什么是柔性数组
- 8.2、柔性数组的特点
- 8.2、柔性数组的使用
- 8.3、柔性数组的优势
- 九、C/C++中内存区域划分
一、动态内存管理概述
1.1、动态内存的概念
在了解为什么要有动态内存管理之前,我们得先知道动态内存的定义。
动态内存是指动态的内存空间,意思就是:能动态开辟的内存空间,动态就是申请了这块空间后,可动态的修改这块空间的大小,根据需要,动态地释放和分配内存空间。
1.2、动态内存的必要性
为什么要有动态内存呢?
既然有动态内存,那与之相对的就是静态内存
什么是静态内存呢?其实静态内存我们天天都在用,只是不知道它是静态内存而已
下面两种内存开辟方式就是静态内存
int val = 20;//在栈空间上开辟四个字节
char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间
但是静态内存的开辟有两个缺点:
- 空间开辟的大小是固定的
- 数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小不能调整
但是,在实际需求中,我们往往只有在程序运行时才能知道所需的空间大小,用数组开辟空间,往往容易造成内存溢出(空间开小),或者内存浪费(空间开大),无法满足实际的需求
因此,C语言引入了动态内存开辟,让程序员自己可以申请和释放空间,并根据需要,自己调整开辟后空间的大小。这样不仅提高了内存的利用率,也极大地增强了程序的灵活性与扩展性。
二、 m a l l o c malloc malloc 函数
2.1、函数介绍
C语言中提供了一个动态内存分配的函数: m a l l o c malloc malloc
功能:向内存申请一块连续可用的空间(可当成数组),并返回指向这块空间的指针
-
参数 s i z e size size_ t t t s i z e size size:
- 分配的
内存的大小,以字节为单位。即开辟 s i z e size size 字节大小的空间 - 如果参数为
0, m a l l o c malloc malloc 的行为是标准未定义的,取决于编译器
- 分配的
-
返回值 v o i d void void *:
- 返回指向开辟空间的指针,因为 m a l l o c malloc malloc 函数 事先并不知道使用者开辟空间存放什么类型的数据,因此指针为 v o i d void void* 类型,以便能接受所有类型。
- 使用者可根据自己的需要,将其强制类型转换成自己所需要的类型,以便能进行解引用操作。
- 如果开辟失败,则返回 空指针,因此 m a l l o c malloc malloc 的返回值一定要做检查
2.2、应用举例
#include<stdlib.h>int main()
{int* p = NULL;p = (int*)malloc(10 * sizeof(int));if (NULL == p){perror("malloc fail");return 1;}return 0;
}
上述代码是使用 m a l l o c malloc malloc 函数开辟 10 个整型变量的空间,也即 40 个字节的空间
- 首先,因为 m a l l o c malloc malloc 函数的返回值是指针,我们需用指针变量 p p p 接收其返回值,创建 p p p 时,并不知道其指向的空间,所以先初始化为 NULL。
- 接着,使用 m a l l o c malloc malloc 函数开辟空间,因为我们要存放的是整型变量,而 m a l l o c malloc malloc 的返回值类型为 v o i d void void* 我们通过强制类型转换将其返回类型转换为 i n t int int* ,并用 p p p 来接收
- 因为 m a l l o c malloc malloc 函数有可能开辟失败1,只有当
返回值不为空的情况我们才使用它,因此需判断 p p p 指针是否为空。若为空,则用 p e r r o r perror perror 函数2打印出错误信息,并返回 13。 - 若开辟成功,我们就可以愉快地使用这块空间啦
需要注意的是: m a l l o c malloc malloc 开辟的空间并不会将其初始化
三、 c a l l o c calloc calloc 函数
开辟动态内存空间,C语言还提供了一个函数叫 c a l l o c calloc calloc ,原型如下:
- 函数的功能是为 n u m num num 个大小为 s i z e size size 的元素
开辟一块空间,并将这块空间初始化为 0 - 与函数 m a l l o c malloc malloc 的区别只在于 c a l l o c calloc calloc 会返回地址之前把申请的空间的每个字节初始化为全 0
举个例子:
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (NULL != p){int i = 0;for (i = 0; i < 10; i++){printf(" %d", *(p + i));}printf("\n");}return 0;
}
运行结果:
四、 f r e e free free 函数
4.1、函数介绍
上述 m a l l o c malloc malloc 函数、 c a l l o c calloc calloc 函数以及后面讲的 r e a l l o c realloc realloc 函数所申请的空间,并不满足作用域的规则。只有当程序退出时,用他们开辟的动态空间才会归还给操作系统,换言之,程序不退出,就不会主动归还空间。这时,我们就需要 f r e e free free函数 来对其主动释放
f r e e free free 函数是专门用于动态开辟的内存空间的释放和回收,声明如下:
f r e e free free 函数用来释放动态开辟的内存
- 如果参数 p t r ptr ptr 指向的空间不是动态开辟的,那 f r e e free free 函数的行为是未定义的
- 如果参数是 p t r ptr ptr 是NULL指针,则函数什么事都不做
- 需要注意的是, f r e e free free 函数
不会改变指针所指向的值,释放后它依然指向相同的内存空间。因此我们要手动将释放后的 p t r ptr ptr 置空,避免出现野指针。
m a l l o c malloc malloc、 c a l l o c calloc calloc 以及 f r e e free free 函数的声明都在 < s t d l i b . h > <stdlib.h> <stdlib.h> 中
4.2、应用举例
我们来看个例子:
#include<stdio.h>
#include<stdlib.h>int main()
{int* ptr = NULL;ptr = (int*)malloc(10 * sizeof(int));if (NULL != ptr){int i = 0;for (i = 0; i < 10; i++){*(ptr + i) = 0;}}free(ptr);ptr = NULL;return 0;
}
那我们来看看下面这种情况行不行呢?
int main()
{int* ptr = NULL;ptr = (int*)malloc(5 * sizeof(int));if (NULL != ptr){int i = 0;for (i = 0; i < 5; i++){*ptr = 0;ptr++;}}free(ptr);ptr = NULL;return 0;
}
当然是不行的,为什么呢?
因为传递给 f r e e free free 函数的是要释放空间的 起始地址
上面函数的 p t r ptr ptr 以及不再指向要释放空间的起始地址了,当然是不行的。
五、 r e a l l o c realloc realloc 函数
5.1、函数介绍
可能有小伙伴问:前面你说动态内存可根据需要,动态调整所开辟空间的大小,但前面介绍 m a l l o c malloc malloc、 c a l l o c calloc calloc 以及 f r e e free free函数 都只是在将动态空间的开辟和释放,如何调整空间的大小呢?别急,我们接下来要讲的 r e l l o c relloc relloc函数 就是完成调整开辟空间的大小的任务的
r e a l l o c realloc realloc 函数的出现让动态内存管理更加灵活
有时我们会发现之前申请的空间太小了,有时我们又会觉得申请的空间太大了,那为了合理的使用内存,,我们一定会对内存的大小做出灵活的调整。那 r e a l l o c realloc realloc函数 就可以做到对动态开辟内存大小的调整
先来看看 r e l l o c relloc relloc函数 的声明:
- p t r ptr ptr 是要调整的内存地址
- s i z e size size 是调整之后的大小(可以变大,也可变小)
- 返回值为调整之后的内存起始位置
r e a l l o c realloc realloc 调整内存大小分为三种情况:
- 原有空间之后有足够大的空间
如上图: r e l l o c relloc relloc 已经开辟 20 个字节的空间,现在我想扩容到 40 字节,同时
原有空间后方空间足够扩展新空间
此时 r e a l l o c realloc realloc 函数直接在后方追加空间,原来空间的数据不发生变化
2. 原有空间之后没有足够大的空间
还是上面那个图,现在我想将他扩容到 400 字节,很明显,已开辟空间
后方没有足够的空间,总不能把别人踢开,自己霸占吧
这时, r e a l l o c realloc realloc 函数就会在堆空间 另外找一个 合适大小的空间。
具体流程如下:
- r e a l l o c realloc realloc 函数先在堆空间上找一块新的空间,并且满足大小要求
- 后将旧空间的数据拷贝到新空间中
- 接着释放旧空间
- 最后返回新空间的起始地址
3. 空间调整失败
r e a l l o c realloc realloc 可能出现空间调整失败的情况,此时返回的是空指针
r e a l l o c realloc realloc 不仅仅能将空间的变大,还能将空间变小,只需要第二个参数的值小于原空间的大小就好了,因为缩小空间比较简单,这里就不再过多介绍,但需要注意的是,缩小空间可能会造成数据丢失,因此需小心使用
同时 r e a l l o c realloc realloc函数 不仅能调整空间大小,还能完成 m a l l o c malloc malloc函数 的功能:当第一个参数 p t r ptr ptr 传递的是 空指针 时, r e a l l o c realloc realloc 函数就不再是调整空间大小了,你都没空间,我还怎么调。此时 r e a l l o c realloc realloc 函数会 新开辟 s i z e size size 字节大小的空间。
5.2、应用举例
看了上面三种情况,大家想一想,应该怎样接收 r e a l l o c realloc realloc 调整之后的返回值呢?
可以直接用原来的指针 p p p 接收吗?
显然是不行,如果 r e a l l o c realloc realloc 调整成功,那确实没问题,但如果失败了呢?此时返回的是空指针。本来 p p p 还维护着原来的空间,现在直接变空指针,那原来的空间再也找不到了,这就造成了内存泄漏。
正确的方法是创建新的指针 p t r ptr ptr 来接收,当 p t r ptr ptr 不为 NULL,再将 p t r ptr ptr 的值传给 p p p
如下:
#include<stdlib.h>int main()
{int* p = (int*)malloc(5 * sizeof(int));if (NULL == p){perror("malloc fail");return 1;}//1 2 3 4 5for (int i = 0; i < 5; i++){*(p + i) = i + 1;}//希望将空间调整为40个字节int* ptr = NULL;ptr = (int*)realloc(p, 40);if (NULL != ptr){p = ptr;}else{perror("realloc fail");}//调整成功,使用40个字节;调整失败,继续使用20个字节/**************业务处理**************/free(p);p = NULL;return 0;
}
六、常见的动态内存错误
6.1、对NULL指针进行解引用
#include<stdlib.h>int main(
{int* p = (int*)malloc(INT_MAX);*p = 20;//如果p的值是空指针,就会有问题free(p);p = NULL;return 0;
}
动态开辟的空间,应该先对返回值进行判断,确保空间开辟成功
上述代码所要开辟的空间太大,开辟失败,返回的是空指针,而下面一句代码对空指针进行解引用,是错误的
#include<stdlib.h>int main()
{int* p = (int*)malloc(10 * INT_MAX);if (NULL == p){perror("malloc fail");return 1;}*p = 20;free(p);p = NULL;return 0;
}
6.2、对动态开辟空间的越界访问
#include<stdlib.h>int mian()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (i = 0; i <= 10; i++){*(p + i) = i;}free(p);p = NULL;return 0;
}
可以看到,当 i i i = 10 时,就是对 m a l l o c malloc malloc 开辟的空间越界访问了。
动态内存的空间与数组是非常相似的,要注意不能对其越界访问。
6.3、对非动态开辟的内存使用 f r e e free free 释放
void test()
{int a = 10;int* p = &a;free(p);//ok?
}
变量 a a a 并不是动态开辟的变量,用 f r e e free free 释放是错误的
6.4、使用 f r e e free free 释放一块动态开辟内存的一部分
这种情况即是,传给 f r e e free free 的指针并不是动态开辟内存的起始地址,指针跑后面去了。
void test()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}
注意:动态内存一定是 一同申请,一同释放。无法做到只释放一部分空间
6.5、对同一块动态内存多重释放
void test()
{int* p = (int*)malloc(100);//···free(p);free(p);//重复释放
}
这种释放有办法可以避免:释放完后及时把 p p p 置为空指针,这样,即使再次释放,传的是空指针, f r e e free free 什么都不会做,不会造成什么影响
6.6、动态开辟内存忘记释放(内存泄漏)
#include<stdlib.h>
void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}int main()
{test();while (1);return 0;
}
上述代码,你会发现,一旦出了函数,就再也找不到开辟的那 100 个字节的空间(这代码写的比较极端,一直死循环,程序一直不结束) 。找不到开辟的空间更别提将其释放,空间就一直在那占着,就造成了内存泄漏。
想一想,如果我们一直向内存申请空间,但从来不释放。要知道,内存的总大小是有限的,这样就会把内存耗干,机器就挂了。
总结: 用 m a l l o c malloc malloc、 c a l l o c calloc calloc、 r e a l l o c realloc realloc 申请的空间,尽量做到:
谁(可能是函数)申请的就谁释放,即 m a l l o c malloc malloc 和 f r e e free free 成对出现- 如果不能释放,要
告诉使用的人记得释放
七、动态内存经典笔试题分析
7.1、题一
void GetMemory(char* p)
{p = (char*)malloc(100);
}void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}int main()
{Test();return 0;
}
请问运行 T e s t Test Test 函数会有什么样的结果?
运行 T e s t Test Test 函数,程序会崩溃!
为什么呢?我们来分析一下
先来看这段代码想要做什么:
- 首先,它定义了一个 G e t M e m o r y GetMemory GetMemory函数,很明显,这个函数是完成动态开辟空间任务的
- 接着 T e s t Test Test函数 中创建了指针 s t r str str,将变量传给 G e t M e m o r y GetMemory GetMemory,即希望指针 s t r str str 管理动态开辟的空间
- 最后往空间中存入 " h e l l o w o r l d " "hello world" "helloworld",并打印
代码的逻辑没问题,那就是代码本身出问题咯
通过调试我们发现, G e t M e m o r y GetMemory GetMemory函数 并没有改变 s t r str str 的值,它依然是个空指针。
为什么呢?因为 G e t M e m o r y GetMemory GetMemory是 传值传参,而不是传址传参!传值传参无法改变主调函数中的值,出了函数 s t r str str 依然是空指针,而后面打印 s t r str str 指向的内容,是要对其解引用的,对空指针解引用自然会出问题。
同时,函数中 m a l l o c malloc malloc 是实打实开辟了空间的,只有程序结束才销毁,而函数中的变量出了函数作用域就销毁,这样函数中所开辟的 100 个字节空间出了 G e t M e m o r y GetMemory GetMemory函数 后也无法找到,造成内存泄漏=
可能有小伙伴会问: G e t M e m o r y GetMemory GetMemory函数 的参数类型就是 c h a r char char* 啊,为什么还是传值传参呢?这里我们要指针传址传参的本质:传递的是变量的地址,因为主调函数中要传的值本身就是指针 c h a r char char* 类型,要改变指针变量,就要传递指针变量的指针,即二级指针。这里可不敢看到 G e t M e m o r y GetMemory GetMemory函数 参数中是 c h a r char char* 就认为他是传址传参
正确写法应该是这样:
void GetMemory(char** p)
{*p = (char*)malloc(100);
}void Test(void)
{char* str = NULL;//传str的地址GetMemory(&str);strcpy(str, "hello world");printf(str);//释放动态空间free(str);str = NULL;
}
当然, G e t M e m o r y GetMemory GetMemory函数 我们也可以直接让他返回 p p p,以实现目的
char* GetMemory()
{char* p = (char*)malloc(100);return p;
}void Test(void)
{char* str = GetMemory();strcpy(str, "hello world");printf(str);free(str);str = NULL;
}
7.2、题二
char* GetMemory(void)
{char p[] = "hello world";return p;
}void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}int main()
{Test();return 0;
}
运行结果:
为什么会这样呢?
问题还是出在 G e t M e m o r y GetMemory GetMemory函数
G e t M e m o r y GetMemory GetMemory函数 中创建的 p p p 数组,在出了函数作用域后就销毁了,因此函数返回 p p p,用 s t r str str 接收,而实际上 s t r str str 接收的地址是指向一块已经归还的空间,此时的 s t r str str 是野指针。再去访问 s t r str str 所指向的空间是非法访问,打印出的值是随机值。
7.3、题三
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}
int main()
{Test();return 0;
}
可能有小伙伴会觉得这段代码咋一看好像没什么问题啊
确实,大家有没有发现代码与我们第一题修改后的代码非常像,但大家仔细想想它还缺少什么?
这段代码唯一的问题是:没有 f r e e free free,动态申请内存空间后他并没有还回去。
虽然这里没有 f r e e free free 程序也没有问题,因此程序结束后会自动释放空间,但以后遇到复杂的情况就不好说了,因此我们要 养成主动释放内存空间的习惯
7.4、题四
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;
}
这题的问题相信大家都能看得出来:
T e s t Test Test函数 上来先开辟 100 字节动态空间,并创建 s t r str str 变量维护它,再向空间中放 " h e l l o " "hello" "hello"
但紧接着,释放 s t r str str,却没将 s t r str str 置空,此时的 s t r str str是野指针。将空间释放,即将其还给操作系统,我们是没有使用权限了,但是这块空间本身还在
下面的 i f if if 语句判断为真,往 s t r str str 指向的空间放入 " w o r l d " "world" "world",此时 s t r str str 指向的空间我们已经没有使用权限了,但依然进行修改,为非法内存访问。
八、柔性数组
8.1、什么是柔性数组
也许有些小伙伴从来没有听过柔性数组这个概念,但是它确实是存在的
C99 中,结构体的最后一个元素允许是未知大小的数组,这就叫做:柔性数组成员。
typedef struct st_type
{int i;int a[0];
}type_a;
有些编译器会报错无法编译,可以改成:
typedef struct st_type
{int i;int a[];
}type_a;
8.2、柔性数组的特点
- 结构体中的柔性数组成员
前面必须至少一个其他成员 - s i z e o f sizeof sizeof 返回的这种结构体
大小不包括柔性数组的内存 - 包含柔性数组成员的结构体一般用 m a l l o c malloc malloc函数 进行内存的动态分配,并且
分配的内存应该大于结构体的大小,以适应柔性数组的预期大小
例如:
typedef struct st_type
{int i;int a[0];
}type_a;
int main()
{printf("%d\n", sizeof(type_a));return 0;
}
运行结果:
8.2、柔性数组的使用
#include<stdio.h>
#include<stdlib.h>typedef struct st_type
{int i;int a[0];
}type_a;int main()
{int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a) + 10 * sizeof(int));if (NULL == p){perror("malloc fail");return 1;}//业务处理p->i = 10;for (i = 0; i < 10; i++){p->a[i] = i;}//调整空间type_a* ptr = (type_a*)realloc(p, sizeof(type_a) + 20 * sizeof(int));if (ptr != NULL){p = ptr;}free(p);p = NULL;return 0;
}
柔性数组的结构:
既然这块空间是 m a l l o c malloc malloc 出来的,也就是说他可以通过 r e a l l o c realloc realloc 来调整大小,所以这个数组可变长变短,不就是柔性吗
8.3、柔性数组的优势
上述 t y p e type type_ a a a 结构,也可以设计为下面的结构,也能完成同样的效果
#include<stdio.h>
#include<stdlib.h>typedef struct st_type
{int i;int* p_a;
}st_type;
int main()
{st_type* p = (st_type*)malloc(sizeof(st_type));p->i = 100;p->p_a = (int*)malloc(p->i * sizeof(int));///业务处理for (i = 0; i < 100; i++){p->p_a[i] = i;}//释放空间free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}
图示:
上述两个方法都可以达到类似的效果
但是使用柔性数组有两个好处:
- 方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把结构体返回给用户。用户调用 f r e e free free 可以释放结构体,但是用户并不知道结构体内的成员也需要 f r e e free free,所以你不能指望用户来发现这个事。
所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次 f r e e free free 就可以把所有的内存释放掉
- 有利于访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片4
九、C/C++中内存区域划分
C/C++程序内存分配的几个区域
内核空间:操作系统核心(内核)运行的地方,在这个区域,操作系统可以直接访问硬件,并执行特权指令。我们用户是无权访问这块空间的栈区:在执行函数时,函数内局部变量的存储单元都是在栈上创建,函数执行结束时这些存储单元自动释放。栈内存分配运算内置于处理器的指令中,效率很高,但是分配的内存容量有限。栈区只要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。堆区:堆区一般是用来存储程序运行期间动态分配内存的地方。堆区的内存分配是在程序运行时动态进行的,程序员可以通过调用标准库函数(如 m a l l o c malloc malloc、 c a l l o c calloc calloc、 r e a l l o c realloc realloc等)来在堆区中分配内存,并在不需要时手动释放这些内存(使用 f r e e free free函数)。使用堆区需要注意内存泄漏( m e m o r y l e a k memory leak memoryleak)的问题,即程序在不再需要某块内存时没有释放它,导致程序占用的内存越来越多。数据段:数据段也叫做静态区,主要用来存放全局变量、静态数据、全局变量。程序结束后由系统释放代码段:存放函数体(类成员函数和全局函数)的二进制代码、只读常量(字符串常量)
内存开辟失败:动态内存开辟失败的原因一般都是所空间太大,没有足够的空间 ↩︎
p e r r o r perror perror函数:有关该函数的具体介绍请看:《【C语言】——字符串函数的使用与模拟实现(下)》 ↩︎
返回值为 1: m a i n main main 函数程序正常退出,返回值为 0;异常退出,返回值为 1 ↩︎
我们开辟内存时,不会紧接着上一块内存开辟,而会留下一点空隙,开辟次数越多,留下的空隙也就也多,这些空隙称为内存碎片。 ↩︎
相关文章:
【C语言】—— 动态内存管理
【C语言】——动态内存管理 一、动态内存管理概述1.1、动态内存的概念1.2、动态内存的必要性 二、 m a l l o c malloc malloc 函数2.1、函数介绍2.2、应用举例 三、 c a l l o c calloc calloc 函数四、 f r e e free free 函数4.1、函数介绍4.2、应用举例 五、 r e a l l o …...
Oracle到PostgreSQL的不停机数据库迁移
1970 年,数据库之父 Edgar Frank Codd 发表了“数据的关系模型”论文,该论文为往后的关系型数据库的发展奠定了基础。1979 年,基于关系模型理论的数据库产品 Oracle 2 首次亮相,并在过去的三四十年时间里,横扫全球数据…...
OpenAI 发布新款大型语言模型 GPT-4o,带大家了解最新ChatGPT动态。
OpenAI 发布新款大型语言模型 GPT-4o 昨日OpenAI 举办了一场线上活动,正式发布了其最新研发的 AI 模型 GPT-4o,并详细介绍了该模型的强大功能和未来发展规划。此次发布标志着 AI 技术的重大突破,为用户提供了更加便捷、高效的 AI 工具&#…...
网络编程套接字(一) 【简单的Udp网络程序】
网络编程套接字<一> 理解源端口号和目的端口号PORT VS PID认识TCP协议和UDP协议网络字节序socket编程接口sockaddr结构简单的UDP网络程序服务端创建套接字服务端绑定运行服务器客户端创建套接字关于客户端的绑定问题启动客户端启动客户端本地测试INADDR_ANY 理解源端口号…...
【CANoe】CAPL中生成报告常用的测试函数
文章目录 一、常用函数1、测试标题、描述、Comment2、测试步骤3、延时4、报告中插入图片5、报告中插入窗口截图二、实例源码三、报告效果一、常用函数 1、测试标题、描述、Comment testCaseTitle("TC 3.1", "Test Case 3.1"); testCaseDescription...
WEB后端复习——MVC、SSM【含登录页面代码】
MVC(Model-View-Controller)是一种软件设计模式,用于将应用程序分解为三个相互关联的组件:模型(Model)、视图(View)和控制器(Controller)。这种模式在构建用户…...
灵卡科技HDMI音视频采集及H.264编码一体化采集卡—LCC260
推荐一款由灵卡科技倾力打造的高品质HDMI音视频采集卡——LCC260。以创新的技术,精湛的工艺和卓越的性能,为您提供全方位的音视频解决方案。 LCC260是一款集HDMI音视频采集与H.264编码于一身的全功能采集卡。它的输入端配备了最先进的HDMI 1.4a标准接口&…...
智能自助终端主板RK3288/RK3568在酒店前台自助机方案的应用,支持鸿蒙,支持免费定制
酒店前台自助机解决方案是一款基于自助服务终端,能够让客人通过简单的操作完成入住登记/退房的解决方案,大幅提高酒店的工作效率,提升客人体验,降低人力成本。 该方案解决了以下传统前台登记入住方式的痛点: 1、人流量…...
Visual Studio环境搭载
环境搭建步骤: 下载软件 安装软件 运行软件 1 下载软件 在百度搜索 visual studio,选择 如下图中的选项 进入Visual Studio 官网后,选择 下载Windows版,并选择Community 2017 社区版本进行下载保存软件到电脑中 2 安装软件 双击…...
添砖Java之路(其八)——继承,final关键字
目录 继承: super关键字: 方法重写: 继承特点: 继承构造方法: final关键字: 继承: 意义:让类于类之间产生父类于子类的关系,子类可以直接使用父类中的非私有成员(包…...
一篇详解Git版本控制工具
华子目录 版本控制集中化版本控制分布式版本控制 Git简史Git工作机制Git和代码托管中心局域网互联网 Git安装基础配置git的--local,--global,--system的区别 创建仓库方式1git init方式2git clone git网址 工作区,暂存区,本地仓库…...
谷歌邮箱2024最新注册教程
大家好,我是蓝胖子,今天教大家如何注册谷歌邮箱 谷歌邮箱的注册后面的用途会经常用得到 首先,需要魔法自行解决 第一步:打开谷歌官网 www.google.com 确保谷歌官网能正常打开 第二步:创建账号 接下来可能会遇到这…...
Spring事务深度解析
Spring事务深度解析 介绍 在现代的软件开发中,事务管理是一个非常重要的话题。Spring框架提供了强大的事务管理功能,使得开发人员能够轻松地处理数据库操作的一致性和并发性问题。本文将深入探讨Spring事务的原理和使用方法。 什么是事务?…...
机器学习周报第41周
目录 摘要Abstract一、文献阅读1.1 摘要1.2 背景1.3 论文方法1.3.1 局部特征提取1.3.2 局部特征转换器 (LoFTR) 模块1.3.4 建立粗粒度匹配1.3.5 精细匹配 1.4 损失1.5 实现细节1.6 实验1.6.1 单应性估计1.6.2 相对位姿估计 二、论文代码总结 摘要 本周阅读了一篇特征匹配领域的…...
gin框架学习笔记(三) ——路由请求与相关参数
参数种类与参数处理 查询参数 在讲解查询参数的定义之前,我们先来看一个例子,当我打开了CSDN,我现在想查看我的博客浏览量,那么我就需要点击我的头像来打开我的个人主页,像下面这样: 我们现在把浏览器的网址取下来,…...
HTML常用标签-多媒体标签(图片、音频、视频)
多媒体标签 1 图片标签2 音频标签3 视频标签 1 图片标签 img(重点) 图片标签,用于在页面上引入图片 代码 <!-- src用于定义图片的连接 title用于定义鼠标悬停时显示的文字 alt用于定义图片加载失败时显示的提示文字 --> <img src"路径" title"悬停显…...
Flutter 中的 AnimatedIcon 小部件:全面指南
Flutter 中的 AnimatedIcon 小部件:全面指南 AnimatedIcon是Flutter Material组件库中的一个独特动画组件,它允许开发者在两个图标之间进行平滑的过渡动画。这使得它非常适合用于表示应用程序的状态变化,如菜单打开/关闭、搜索打开/关闭等。…...
0510Goods的Maven项目
0510Goods的Maven项目包-CSDN博客 数据库字段 商品主页 修改页面 点击商品主页更改信息, 跳转到修改页面, 并保留初始信息。 商品类别最多选取三项,最少选取一项 添加界面 商品类别最多选取三项,最少选取一项...
使用Pyramid、Mako和PyJade生成 HTML
Pyramid 是一个流行的 Python Web 框架,而 Mako 和 PyJade 是用于模板引擎的工具,它们可以与 Pyramid 配合使用来生成 HTML 内容。但是在实际使用中还是有些差别的,尤其会遇到各种各样的问题,下面我将利用我所学的知识一一为大家解…...
什么是Facebook付费广告营销?
Facebook作为全球最大的社交平台之一,成为了跨境卖家不可或缺的营销阵地。它不仅拥有庞大的用户基数,还提供了丰富的广告工具和社群互动功能,让商家能够精准触达目标市场,提升品牌影响力。云衔科技通过Facebook付费广告营销的专业…...
文献阅读 260404-Effect of climate warming on the timing of autumn leaf senescence reverses after ...
Effect of climate warming on the timing of autumn leaf senescence reverses after the summer solstice 来自 <https://www.science.org/doi/10.1126/science.adf5098> ## Abstract: Structured Abstract INTRODUCTION Ongoing climate change is causing rapid shif…...
OpenClaw浏览器自动化:Qwen3-14b_int4_awq实现智能爬虫
OpenClaw浏览器自动化:Qwen3-14b_int4_awq实现智能爬虫 1. 为什么需要智能爬虫? 上周我需要从几十个电商页面抓取产品参数,传统爬虫遇到三个致命问题:动态加载内容无法解析、反爬机制频繁拦截、非结构化数据难以提取。当我尝试用…...
实测对比:用MMDeploy把MMDetection模型转成TensorRT后,FP16/INT8到底能快多少?
MMDeploy实战:TensorRT量化性能深度评测与优化指南 当我们将训练好的目标检测模型部署到生产环境时,推理速度往往成为关键瓶颈。本文将通过实测数据,揭示如何利用MMDeploy工具链将MMDetection模型转换为TensorRT引擎,并深入分析FP…...
基于YOLO26的人脸识别技术
基于YOLO26的人脸识别技术方案代表了边缘计算与轻量化视觉AI的前沿突破。YOLO26作为Ultralytics团队于2026年初发布的最新一代YOLO模型,通过"无NMS端到端推理+架构精简优化"的核心设计理念,实现了在CPU和边缘设备上43%的推理速度提升,同时保持了优秀的检测精度。本…...
5W功耗实现25TOPS算力,LM2-100-V0算力模组破解AI安防核心难题
在智慧安防边缘AI应用快速部署需求的背景下,设备制造商常面临终端设备算力不足、功耗超标、体积受限、部署太慢等困境。模型越复杂,终端越吃力;设备要小型化,算力要打折扣;长期稳定运行,散热与功耗又成瓶颈…...
SEO如何提升网站权重_外链建设对SEO权重有什么作用
SEO如何提升网站权重_外链建设对SEO权重有什么作用 在当今互联网时代,网站的成功往往取决于其在搜索引擎上的排名。而搜索引擎优化(SEO)作为提升网站在搜索结果中排名的关键手段,其中的外链建设更是不可忽视的一环。SEO如何提升网…...
dify可以干什么
Dify 是一个开源的大模型(LLM)应用开发平台,旨在帮助你快速构建、管理和部署生成式 AI 应用,而无需从零开始写代码。简单来说,你可以把它理解为一个可视化的 AI 应用“工作台”,通过拖拉拽的方式࿰…...
绝版图书购书方案问题(折半枚举 / Meet-in-the-Middle)
绝版图书购书方案问题(折半枚举 / Meet-in-the-Middle) 📚 绝版图书购书方案问题(折半枚举 / Meet-in-the-Middle) 一、题目描述 输入 输出 样例输入 样例输出 提示 二、题目解读 2.1 什么是"购书方案"? 2.2 样例解释 三、算法选择分析 3.1 为什么不能直接用…...
基于QGIS分区统计与栅格重分类的GlobeLand30地表覆盖面积精准测算
1. 数据准备与预处理 做地表覆盖分析的第一步就是获取高质量的数据源。GlobeLand30作为国产30米分辨率全球地表覆盖数据,在精度和易用性上都有不错的表现。我去年参与的一个省级生态评估项目就用到了这套数据,实测下来分类效果相当可靠。 下载数据时有个…...
SEO优化有哪些快速有效的方法_自媒体如何通过SEO快速提升曝光度
SEO优化有哪些快速有效的方法 在当前数字化时代,自媒体如何通过SEO快速提升曝光度成为了许多内容创作者和网络营销人员关注的焦点。搜索引擎优化(SEO)不仅能够提升网站的自然排名,还能有效增加自媒体的曝光度。具体有哪些快速有效…...














