【C】动态内存管理
所谓动态内存管理,就是使得内存可以动态开辟,想使用的时候就开辟空间,使用完之后可以销毁,将内存的使用权还给操作系统,那么动态开辟内存有什么用呢?
假设有这么一种情况,你在一家公司中工作,该公司开发了一款app,要有用户来使用这款app,那么添加用户信息的时候需要开辟内存空间,但是该开辟的内存空间又是不确定的,开辟小了不够用,开辟多了又会浪费内存空间,以后如果继续有用户使用该app,又需要开辟内存空间,所以这个时候就需要动态开辟内存空间,要用的时候就开辟,不用的时候就销毁,这就是动态内存管理存在的意义。
学习动态内存管理主要是学习 4 个函数,分别是malloc,free,calloc,realloc 这四个函数,这几个函数都被包含在 stdlib.h 头文件里,使用的时候需要包含头文件stdlib.h。接下来就来依次讲解这四个函数。
值得注意的一点是,动态内存管理对于以后学习数据结构是必要知识,只有学好了动态内存管理,才能学好数据结构。
目录
1 malloc函数
2 free函数
3 calloc函数
4 realloc函数
5 动态内存的注意事项
1) 一定要检查动态开辟内存是否成功
2) 防止对非动态开辟的内存进行释放
3) 要避免使用free函数释放一部分动态开辟内存
4) 防止对同一块内存空间进行多次释放
5) 动态开辟内存后,一定要记得使用free函数释放
6 柔性数组
1) 柔性数组的定义
2) 柔性数组的特点
3) 柔性数组的使用
4) 柔性数组的优势
7 C/C++在内存中的区域划分
1 malloc函数
使用malloc函数时,需要注意以下几点:
1 | malloc函数的参数为字节,也就是想要开辟空间的字节数 |
2 | 如果开辟失败,那么malloc函数的返回值为空指针 |
3 | 如果开辟成功,那么malloc函数的返回值为viod*,需要强制类型转换为想要开辟空间数据的指针类型,如:int*,float*等等 |
4 | 如果参数size为0,那么malloc的行为是未定义 |
使用malloc函数的例子如下:
#include<stdio.h>
#include<stdlib.h>int main()
{ //malloc返回值需要强转为int*int num = 0;scanf("%d", &num);int* arr = (int*)malloc(sizeof(num) * 20);//参数为字节//使用malloc的时候一定要判断返回值,如果开辟失败,那么退出程序if (arr == NULL){//perror函数的功能为打印错误信息perror("malloc fail!\n");//exit为退出函数exit(1);}//开辟成功for (int i = 0; i < num; i++){scanf("%d", &arr[i]);}for (int i = 0; i < num; i++){printf("%d ", arr[i]);}return 0;
}
运行结果:
在上述代码中, 共开辟了10个空间,依次向arr数组里面输入了1,2,3,4,5,6,7,8,9,10这10个数据。
在代码中最关键的一段代码就是判断malloc返回值与强转的那两段代码,在动态开辟数据时,一定要判断malloc函数返回值,也就是判断有没有成功动态开辟内存空间。
2 free函数
free函数的功能主要是用来释放动态开辟的空间,也就是将内存空间的使用权归还给操作系统,其注意事项有以下两条:
1 | free函数的参数为指针类型,这个指针所指向的空间必须是动态开辟的,否则其行为是未定义的 |
2 | 如果参数ptr为NULL,那么free函数什么都不做 |
free函数一般是配合其他动态开辟内存的函数使用,如malloc函数,还有之后的calloc,realloc函数,值得注意的一点是,动态开辟的内存一定要使用free函数释放掉内存,否则可能会出现内存泄露的情况,正确使用free函数的例子如下:
#include<stdio.h>
#include<stdlib.h>int main()
{ //malloc返回值需要强转为int*int num = 0;scanf("%d", &num);int* arr = (int*)malloc(sizeof(num) * 20);//参数为字节//使用malloc的时候一定要判断返回值,如果开辟失败,那么退出程序if (arr == NULL){//perror函数的功能为打印错误信息perror("malloc fail!\n");//exit为退出函数exit(1);}//开辟成功for (int i = 0; i < num; i++){scanf("%d", &arr[i]);}for (int i = 0; i < num; i++){printf("%d ", arr[i]);}//使用完之后用free函数销毁动态开辟的空间free(arr);//释放完之后记得要把指针置为NULL,否则arr会变成野指针arr = NULL;return 0;
}
在上述代码中,使用完free函数释放了arr指针所指向的动态开辟的空间,也就是把动态开辟的空间还给了操作系统,但是arr指针本身还是指向原来动态开辟的空间,所以释放完之后,要把arr置为NULL,否则arr就变成了野指针,会越界访问。
3 calloc函数
calloc函数不同于以上两个函数,calloc函数有两个参数,第一个参数是想要动态开辟空间的元素个数,另一个参数是想要动态开辟的每个元素的字节大小,如开辟10个整型空间,就可以这样写:
calloc(10, sizeof(int))
相同点 | 区别 | |
---|---|---|
1 | 返回值都为void*,都需要对返回值进行强制类型转换 | malloc函数只有一个参数,为开辟的空间字节的大小,calloc函数有两个参数,第一个参数是开辟空间元素的个数,第二个参数是每个元素的字节数 |
2 | 都是开辟成功会返回动态开辟空间首元素地址,开辟失败返回NULL | calloc函数会将所有元素初始化为0,malloc不会,只是开辟空间,不进行初始化 |
使用calloc函数的例子如下:
#include<stdio.h>
#include<stdlib.h>int main()
{int* arr = (int*)calloc(10, sizeof(int));//开辟失败if (arr == NULL){perror("calloc fail!\n");exit(1);}//开辟成功for (int i = 0; i < 10; i++){printf("%d ", arr[i]);}//使用完之后要释放free(arr);//释放完之后置为NULLarr = NULL;return 0;
}
运行结果:
4 realloc函数
realloc函数是这四个函数里面最复杂的一个函数,其最复杂就是因为其能够实现增容。
不管是前面的malloc函数,还是calloc函数都只能实现动态开辟一块空间,并不能根据已有空间来实现增容的效果,而realloc函数可以在原有空间的基础上实现对原有空间的扩大,所以有了realloc函数就可以对内存空间做灵活的调整了。
使用realloc函数的注意事项如下:
1 | 第一个参数为指向要扩容的空间的指针 |
2 | 第二个参数为增容之后空间的大小,注意是增容之后空间的总大小,而不是增容的空间大小,如原来空间为10,想要增容10个空间,第二个参数为20 |
3 | 如果开辟成功,返回值为指向增容后所有空间的指针;如果开辟失败,返回值为NULL |
对于realloc函数增容,有两种情况:
1) 如果原有空间之后有足够的空间来进行增容,那么就会在原有空间之后追加空间,原有空间数据不变,返回值与第一个参数相同。
2) 如果原有空间后面的空间不够要增容的空间大小,那么就会在内存的堆区上另找一块内存空间大小(原有空间 + 要增容的空间)足够的连续空间,并把原有空间的数据复制到新开辟的空间上,然后释放旧空间,返回新开辟空间的首元素地址。
使用realloc函数的例子如下:
#include<stdio.h>
#include<stdlib.h>int main()
{int n = 0;scanf("%d", &n);int* arr = (int*)malloc(sizeof(int) * n);//先用一个中间变量来接收增容后的地址int* tmp = (int*)realloc(arr, sizeof(int) * 2 * n);//开辟失败if (tmp == NULL){perror("realloc fail!\n");exit(1);}//开辟成功,将增容后空间地址赋给原有空间地址arr = tmp;n = 2 * n;for (int i = 0; i < n; i++){scanf("%d", arr + i);}for (int i = 0; i < n; i++){printf("%d ", arr[i]);}//使用完之后要销毁free(arr);//销毁之后置为NULLarr = NULL;return 0;
}
运行结果:
需要注意的一点是,在使用realloc函数增容的时候,一定要先用一个中间变量接受增容后的地址,一定要在确保开辟成功之后,再把增容后的地址赋给旧空间的地址,要是直接赋给旧空间的地址,一旦开辟失败,那么旧空间就找不到了。
5 动态内存的注意事项
1) 一定要检查动态开辟内存是否成功
如果不检查动态开辟空间函数的返回值,如果开辟失败就会造成NULL指针的解引用,如:
#include<stdio.h>
#include<stdlib.h>int main()
{int* arr = (int*)malloc(sizeof(int));*arr = 10;free(arr);arr = NULL;return 0;
}
在上述代码里面,开辟成功了还好,一旦开辟失败就会造成对NULL指针的解引用,势必会报错,正确的代码应该这样写:
#include<stdio.h>
#include<stdlib.h>int main()
{int* arr = (int*)malloc(sizeof(int));//检查返回值是否为NULLif (arr == NULL){perror("malloc fail!\n");exit(1);}//开辟成功*arr = 10;free(arr);arr = NULL;return 0;
}
2) 防止对非动态开辟的内存进行释放
对非动态开辟的内存进行free函数释放,其行为是未知的,如以下这个代码:
#include<stdlib.h>int main()
{int arr[] = {1, 2, 4};free(arr);return 0;
}
在运行的时候会出现下面这种情况:
3) 要避免使用free函数释放一部分动态开辟内存
在使用free函数释放动态开辟空间时,很容易让指向动态开辟空间的指针改变指向位置,如:
#include<stdlib.h>
#include<stdio.h>int main()
{int* ptr = (int*)malloc(sizeof(int) * 10);if (ptr == NULL){exit(1);}for (int i = 0;i < 10; i++){scanf("%d", ptr + i);}printf("%p ", ++ptr);free(ptr);ptr = NULL;
}
在上述代码里面,在释放空间的时候,ptr指针已经不再指向原来动态开辟空间的首元素的地址,而是指向的是第二个元素的地址,在运行代码时,vs编译器也会发生错误:
所以在动态开辟内存后,我们要避免改变动态开辟内存指针的指向。
4) 防止对同一块内存空间进行多次释放
#include<stdlib.h>int main()
{int* ptr = (int*)malloc(40);if (ptr == NULL){exit(2);}free(ptr);free(ptr);return 0;
}
运行后,同样会发生错误:
所以在使用free函数释放完空间之后,一定要记得把指针置为NULL,防止对其多次释放。
5) 动态开辟内存后,一定要记得使用free函数释放
动态开辟的内存会在以下两种情况下归还给操作系统:
(1) 程序运行结束时
(2) 使用free函数释放时
所以一旦一个程序不停止运行,而又没有free函数主动释放内存,就会造成动态开辟的空间一直占用,内存空间越来越少,就会造成内存空间的浪费,也就是内存泄露。
6 柔性数组
1) 柔性数组的定义
在一个结构体里面,最后一个元素允许是未知大小的数组,这个数组就叫做柔性数组成员。注意,柔性数组一定是在结构体里面创建的。
上述定义可能比较抽象,下面举个例子:
struct A
{int i;int a[0];
};
上述代码里面的a数组就是柔性数组成员,数组元素个数为0,代表没有成员。如果上述代码在编译器报错的话,也可以写成以下代码:
struct A
{int i;int a[];
};
上述代码的a数组也是柔性数组,数组里面的元素个数不写,也代表数组里面没有元素。
2) 柔性数组的特点
柔性数组的特点如下:
1 | 结构体中的柔性数组成员前必须有一个成员 |
2 | 用 sizeof 关键字返回结构体的大小不包括柔性数组的内存 |
3 | 包含柔性数组成员的结构体用 malloc 函数进行内存的动态分配,且分配的内存大小应该大于结构体的大小,以适应柔性数组的预期大小 |
如:
#include<stdio.h>typedef struct A
{int i;int a[0];
}A;int main()
{int size = sizeof(A);printf("%d ", size);return 0;
}
运行结果为:
通过上述代码,可以看到,含有柔性数组成员的结构体A,其大小确实为4个字节,不包含柔性数组成员a数组的大小。
3) 柔性数组的使用
对于含有一个柔性数组成员的结构体,应该使用malloc函数来动态开辟空间,例如:
#include<stdio.h>
#include<stdlib.h>typedef struct A
{int x;int a[0];
}A;int main()
{A* pa = (A*)malloc(sizeof(A) + 10 * sizeof(int));//判断是否开辟成功if (pa == NULL){perror("malloc fail!\n");exit(1);}pa->x = 10;for (int i = 9;i >= 0; i--){pa->a[i] = i;}printf("%d ", pa->x);for (int i = 0;i < 10;i++){printf("%d ", pa->a[i]);}//使用完不要忘记销毁free(pa);//释放后要置为NULLpa = NULL;return 0;
}
运行结果为:
上述代码在使用malloc函数开辟带有柔性数组结构体成员的内存空间时,malloc函数里面的参数应写的是 sizeof(A) + sizeof(int) * 10 ,而不是直接写字节个数,这样写不仅不用计算结构体的大小(结构体存在内存对齐现象,计算起来比较麻烦),而且比较直观,后面的 sizeof(int) * 10 就是为柔性数组成员开辟的空间。
4) 柔性数组的优势
其实上述柔性数组的功能也可以通过在结构体里添加一个指针变量来达到,如:
#include<stdio.h>
#include<stdlib.h>typedef struct A
{int x;int* pi;
}A;int main()
{A* pa = (A*)malloc(sizeof(A));if (pa == NULL){perror("malloc1 fail!\n");exit(1);}pa->x = 10;pa->pi = (int*)malloc(sizeof(int) * 10);if (pa->pi == NULL){perror("malloc2 fail!\n");exit(2);}for (int i = 9;i >= 0;i--){pa->pi[i] = i;}printf("%d ", pa->x);for (int i = 0;i < 10;i++){printf("%d ", pa->pi[i]);}//一定要先释放结构体里开辟的数组空间free(pa->pi);//再释放开辟的结构体空间free(pa);pa = NULL;return 0;
}
运行结果为:
可以看到在结构体里添加一个指针变量同样可以达到类似于柔性数组的功能,那么柔性数组相比于用指针来实现有什么优势呢?
1 | 柔性数组容易进行内存释放:通过上述两种实现方式,我们可以看到,通过柔性数组实现只需要进行一次free释放,而使用指针实现需要进行两次free释放,所以用柔性数组实现更容易进行内存释放,不容易出现内存泄露情况 |
2 | 柔性数组有利于提高访问速度:在使用柔性数组实现时,只进行了一次malloc动态开辟空间,所以开辟的是一块连续的内存空间;而在使用指针实现时,进行了两次malloc动态开辟空间,使其内存不一定是连续的。所以使用柔性数组可以提高访问速度,而且会有利于减少内存碎片。 |
7 C/C++在内存中的区域划分
C语言或者C++语言共将内存空间划分为以下几个区域:
区域 | 存放内容 |
---|---|
栈区(Stack) | 主要是用来进行函数栈帧的创建,还用来存放一些局部变量、函数参数、返回数据与返回地址等,在堆上开辟的空间是在函数运行完后被销毁。 |
堆区(Heap) | 向malloc、calloc、realloc函数动态开辟的空间一般都在堆区上开辟,在堆区上开辟的空间要么是由程序员主动释放,要么是在程序运行结束时,由操作系统自动收回。 |
数据段(静态区) | 主要用来存放全局变量和由static关键字修饰的静态变量,在静态区开辟的空间是在程序运行结束时由系统自动释放。 |
代码段 | 用来存放函数体的二进制代码。 |
相关文章:

【C】动态内存管理
所谓动态内存管理,就是使得内存可以动态开辟,想使用的时候就开辟空间,使用完之后可以销毁,将内存的使用权还给操作系统,那么动态开辟内存有什么用呢? 假设有这么一种情况,你在一家公司中工作&am…...

lec5-传输层原理与技术
lec5-传输层原理与技术 1. 传输层概述 1.1. 关键职责 flow control,流量控制reliability,可靠性 1.2. TCP与UDP对比 面向连接 / 不能连接对数据校验 / 不校验数据丢失重传 / 不会重传有确认机制 / 没有确认滑动窗口流量控制 / 不会流量控制 1.3. 关…...

【C语言】_指针运算
目录 1. 指针-整数 2. 指针-指针 2.1 指针-指针含义 2.2 指针-指针运算应用:实现my_strlen函数 3. 指针的关系运算(大小比较) 1. 指针-整数 联系关于指针变量类型关于指针类型和指针-整数相关知识: 原文链接如下࿱…...

“AI智慧教学系统:开启个性化教育新时代
大家好,我是老王,一个在产品圈摸爬滚打多年的资深产品经理。今天,我想和大家聊聊一个最近特别火的概念——AI智慧教学系统。这东西听起来好像很高大上,但其实和我们每个人都息息相关,因为它关系到我们下一代的教育。 一…...

商用车自动驾驶,迎来大规模量产「临界点」?
商用车自动驾驶,正迎来新的行业拐点。 今年初,交通部公开发布AEB系统运营车辆标配征求意见稿,首次将法规限制条件全面放开,有望推动商用车AEB全面标配,为开放场景的商用车智能驾驶市场加了一把火。 另外,…...

CSS 学习之正确看待 CSS 世界里的 margin 合并
一、什么是 margin 合并 块级元素的上外边距(margin-top)与下外边距(margin-bottom)有时会合并为单个外边距,这样的现象称为“margin 合并”。从此定义上,我们可以捕获两点重要的信息。 块级元素,但不包括浮动和绝对定位元素,尽…...

杰发科技——使用ATCLinkTool解除读保护
0. 原因 在jlink供电电压不稳定的情况下,概率性出现读保护问题,量产时候可以通过离线烧录工具避免。代码中开了读保护,但是没有通过can/uart/lin/gpio控制等方式进行关闭,导致无法关闭读保护。杰发所有芯片都可以用本方式解除读保…...

uni-app深度解码:跨平台APP开发的核心引擎与创新实践
在当今数字化浪潮中,移动应用市场呈现出爆炸式增长。为了满足不同用户群体在不同操作系统上的需求,跨平台 APP 开发成为众多开发者的首选策略。uni-app 作为一款领先的跨平台开发框架,以其独特的优势和创新的实践在众多同类产品中脱颖而出。它…...

unity团结云下载项目
今天开plastic scm发现它云服务好像停了哈,在hub里下载云端项目也不会出现在项目列表里,之前也有发邮件说让提前迁移到团结云。打开云仓库会弹这个,大概就是plastic scm无法解析域名地址吧 研究了一下团结云咋使,官方手册看半天也…...

Jmeter进阶篇(31)解决java.net.BindException: Address already in use: connect报错
📚前言 近期雪雪妹妹在使用Jmeter执行压测的时候,发现了一个非常让她头疼的问题,她使用20并发跑,正确率可以达到100%,但是一旦使用200并发,就会出现大量的报错,报错内容如下: java.net.BindException: Address already in use: connectat java.net.DualStackPlainSo…...

商米电子秤服务插件
概述 SunmiScaleUTS封装商米电子秤服务模块,支持商米旗下S2, S2CC, S2L CC等设备,设备应用于超市、菜市场、水果店等,用于测量商品的重量,帮助实现快捷、准确、公正的交易等一系列商业场景。 功能说明 SDK插件下载 一. 电子秤参数 型号:S2, S2CC, …...

华为ensp-BGP路由过滤
学习新思想,争做新青年,今天学习的是BGP路由过滤 实验目的: 掌握利用BGP路由属性AS_Path进行路由过滤的方法 掌握利用BGP路由属性Community进行路由过滤的方法 掌握利用BGP路由属性Next_Hop进行路由过滤的方法 实验内容: 本实…...

Sigrity System SI SerialLink模式进行Pcie3协议仿真分析操作指导-pcie3_client_single_post
Sigrity System SI SerialLink模式进行Pcie3协议仿真分析操作指导-pcie3_client_single_post Sigrity System SI SerialLink模式提供了10个协议合规性检查工具模板,用户可以将根据实际应用替换模板中的SPICE文件,然后进行协议仿真分析,同时软件还提供了目标结果的模板MASK以…...

Python提取目标Json键值:包含子嵌套列表和字典
目标:取json中所有的Name、Age字典 思路:递归处理字典中直接包含子字典的情况, import jsondef find_targ_dicts(data,key1,key2):result {}if isinstance(data, dict):if key1 in data and key2 in data: # 第一层字典中包含key1和key2re…...

分享6个对象数组去重的方法
大家好,关于对象数组去重的业务场景,想必大家都遇到过类似的需求吧,针对这样的需求,你是怎么做的呢。 下面我就先和大家讨论下基于对象的某个属性如何去重。 方法一:使用 .filter() 和 .findIndex() 相结合的方法 使…...

Formality:官方Tutorial(一)
相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 本文是对Synopsys Formality User Guide Tutorial中第一个实验的翻译(有删改),Lab文件可以从以下链接获取。 Formality官方Tu…...

力扣28找出字符串中第一个匹配项的下标
class Solution:def strStr(self, haystack: str, needle: str) -> int:# 特殊情况处理if not needle:return 0# 获取 haystack 和 needle 的长度a len(needle)b len(haystack)# 遍历 haystack,检查每个子字符串是否与 needle 匹配for i in range(b - a 1):if…...

【JAVA】java中将一个list进行拆解重新组装
一、使用场景 1、当需要对一个list中的元素属性进行重新赋值,比如一个list中存储了订单数据,我们需要改变list中每个订单的id,然后再重新输出订单list if(CollectionUtils.isNotEmpty(orderList)){ orderList.forEach(p->{ …...

在 Windows 上使用 SSH 密钥访问 Linux 服务器
本章目录: 前言1. 准备工作2. 生成 SSH 密钥对步骤 1:打开命令行步骤 2:运行 ssh-keygen 命令步骤 3:选择密钥保存位置步骤 4:设置密钥密码(可选)步骤 5:生成密钥对 3. 查看生成的密钥文件4. 将…...

小白0基础centos8安装docker
总述:博主作为0基础小白将详细记录第一次从centos8的下载到安装docker的过程,包括中间出现的问题和解决方案 1Centos8下载 参见博文CentOS 8 的安装(官方安装、清华大学开源软件镜像站、阿里云镜像、网易镜像下的安装步骤)_cent…...

机器学习之逻辑回归算法、数据标准化处理及数据预测和数据的分类结果报告
逻辑回归算法、数据标准化处理及数据预测和数据的分类结果报告 目录 逻辑回归算法、数据标准化处理及数据预测和数据的分类结果报告1 逻辑回归算法1.1 概念理解1.2 算法导入1.3 算法优缺点 2 LogisticRegression理解2.1查看参数定义2.2 参数理解2.3 方法2.4基本格式 3 数据标准…...

openGauss连接是报org.opengauss.util.PSQLException: 尝试连线已失败
安装好高斯数据库后然后用java连接时报如下错误: 解决方法: 在openGauss数据库的安装路径下/opt/opengauss/data/single_node(这个路径根据自己实际情况变化)有个pg_hba.conf文件,修改里面host内容如下,我这里设置的是所有ip都能…...

详细的一条SQL语句的执行流程
SQL 语句的执行流程会因数据库管理系统的不同而略有差异,但一般来说,主要包括以下几个阶段: 查询解析 词法分析:数据库系统首先将输入的 SQL 语句按字符流进行扫描,依据词法规则把它分割成一个个的单词,如…...

适用于小白的程序报错提问 AI 模板
#工作记录 程序报错提问 AI 模板 1、你现在将扮演python专家,请保持连续对话,请基于你的专业知识修改代码并回答! 2、可以向我询问任何有利于你分析问题的信息。 3、你修改的程序代码,运行后报错,报错信息我放在最后…...

web实操9——session
概念 数据保存在服务器HttpSession对象里。 session也是域对象,有setAttribute和getAttribute方法 快速入门 代码 获取session和塞入数据: 获取session获取数据: 请求存储: 请求获取: 数据正常打印:…...

OFDM学习-(二)长短序列和PPDU整体数据处理流程
OFDM学习 (二)长短序列和PPDU整体数据处理流程 OFDM学习前言一、短序列短序列的作用 二、长序列三、PLCP/SIGNAL/DATA数据处理流程三、fpga实现STS模块LTS模块训练序列模块仿真波形 总结 前言 根据框图可以知道发射机这部分信号在DA转换之前,…...

.NET周刊【12月第4期 2024-12-22】
国内文章 dotnet 简单使用 ICU 库进行分词和分行 https://www.cnblogs.com/lindexi/p/18622917 本文将和大家介绍如何使用 ICU 库进行文本的分词和分行。 dotnet 简单聊聊 Skia 里的 SKFontMetrics 的各项属性作用 https://www.cnblogs.com/lindexi/p/18621674 本文将和大…...

Conda 安装 Jupyter Notebook
文章目录 1. 安装 Conda下载与安装步骤: 2. 创建虚拟环境3. 安装 Jupyter Notebook4. 启动 Jupyter Notebook5. 安装扩展功能(可选)6. 更新与维护7. 总结 Jupyter Notebook 是一款非常流行的交互式开发工具,尤其适合数据科学、机器…...

鸿蒙开发:实现键值数据库存储
前言 鸿蒙当中数据持久化存储,为我们提供了多种的实现方式,比如用户首选项方式,关系型数据库方式,键值型数据库方式,文件存储方式等等,对于数据量比较的小的,我们直接选择轻量级的用户首选项方式…...

WebGL 实践(一)开发环境搭建
WebGL 是一种基于 JavaScript 的 API,用于在浏览器中渲染 2D和3D 图形,很多场景都能用,例如游戏开发、数据可视化、在线教育和虚拟现实等应用领域。 零、基础知识 相关基础知识很重要,如果会html、JavaScript等技术掌握起来会更…...