【逐步剖C】-第十一章-动态内存管理
一、为什么要有动态内存管理
从我们平常的学习经历来看,所开辟的数组一般都为固定长度大小的数组;但从很多现实需求来看需要我们开辟一个长度“可变”的数组,即这个数组的大小不能在建立数组时就指定,需要根据某个变量作为标准。
如比较常见的就是一些编程题中,输入一个变量n来作为数组的长度等(PS:虽C99支持变长数组,但我们这里主要讨论数组共性的标准);可能还有一种情况就是在往数组中放数据时,由于一开始空间大小指定不合适,出现了空间不足的情况,此时就需要进行“扩容”操作。
由此一来,动态内存管理应运而生。
二、动态内存函数
1、malloc和free
(1)malloc函数介绍
- 函数的声明为:
void* malloc (size_t size);
- 函数的作用:
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针,通过相应类型的指针变量接收返回的指针后,即可通过该指针变量使用已开辟的内存空间 - 函数的特性:
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL
指针,因此malloc的返回值一定要做检查。
返回值的类型是void*
,因为malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数size
为0,malloc的行为是标准是未定义的,取决于编译器
使用示例:
int n = 0;scanf("%d", &n);int* a = (int*)malloc(sizeof(int) * n);//开辟大小为n个整型的空间if (a != NULL) //判断空间是否开辟成功{a[0] = 1; //对空间进行使用//...}
可以看到,我们在使用时需要通过强制类型转换“告诉”编译器我们开辟空间的类型(返回指针的类型)(PS:其实空间并没有所谓的“类型”的概念,仅是通过指针的不同类型而有不同的看待空间的视角)
(2)free函数介绍:
- 函数声明为:
void free (void* ptr);
- 函数的作用:
释放动态开辟的内存 - 函数的特性:
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果是动态开辟的,则该函数会将ptr
指向空间的使用权进行归还,其中有两部分,大部分归还给操作系统(真正释放),另一部分未归还操作系统的已释放内存被恢复到空闲池(free pool)中,并可再次进行分配
如果参数ptr
是NULL
指针,则函数什么事都不做。
使用示例:
int n = 0;scanf("%d", &n);int* a = (int*)malloc(sizeof(int) * n);//开辟大小为n个整型的空间if (a != NULL) //判断空间是否开辟成功{a[0] = 1; //对空间进行使用//...}free(a); //释放动态开辟的内存
使用注意事项:
从函数的特性中我们可以看出,在free一块动态开辟的内存后,那块空间按理来说已经不能再访问了,虽然的确可以故意去访问,当访问到的是空闲池中的内存时可能不会有什么大问题,但若访问到了操作系统的内存,程序就会崩溃;又因为free仅负责将动态开辟的空间的使用权进行归还,并不对用于接收这块空间起始地址的指针进行处理(示例中a再free之后仍指向那块空间),故为了内存访问的安全,在free之后需对相应的指针进行置空操作。(示例中最后还需加上a = NULL
)
2、calloc
(1) 函数的声明为:
void* calloc (size_t num, size_t size);
(2)函数的作用:
作用和malloc一样,唯一不同的是它可以将动态开辟好的空间进行初始化,即将开辟好的num
个空间的值都初始化为0。
(3)函数的特性:
同malloc,但多了一个初始化功能。
使用示例:
int n = 0;scanf("%d", &n);int* a = (int*)calloc(n,sizeof(int));//开辟大小为n个整型的空间
3、realloc
(1) 函数的声明为:
void* realloc (void* ptr, size_t size);
(2)函数的作用:
对动态开辟内存大小进行调整,一般用于给已开辟的空间进行扩容。
(3)函数的特性:
参数ptr
是要调整的内存地址,size
是调整之后的新大小(一般即为新空间的大小为原空间大小+需要扩展的空间的大小)。
若参数size为0或空间开辟失败时,函数返回NULL
。
若参数ptr
为NULL
,该函数等价于malloc
,如:
int*p = NULL;
p = realloc(ptr, 1000);
空间开辟成功会分为两种情况:
-
原开辟空间后有足够空间可以扩容,那么函数直接在原有内存之后直接追加空间,原来空间的数据不发生变化,返回旧的起始地址(
ptr
的值) -
原开辟空间空间无法满足扩容需求,函数会先在堆空间上另找一个新的合适大小的连续空间来使用,接着把原空间的数据拷贝至新空间前面的位置,并把原空间释放,最后返回一个新空间的内存地址
从特性中我们得到使用时的重要一点:
我们在对原空间进行扩容时,需用一个同类型的新指针来接收扩容之后返回的指针,以防分配失败而造成原有数据的丢失,如:
int *ptr = (int*)malloc(100);
ptr = (int*)realloc(ptr, 1000);//如上代码中,若realloc分配失败放回空指针,ptr所指向的原空间数据丢失
//正确应写为:int *ptr = (int*)malloc(100);
int *tmp = (int*)realloc(ptr, 1000);
if(tmp != NULL)
{ptr = tmp;
}
补充一点:
realloc函数一般仅用于对内存进行扩展,也就是扩容;很少会用于缩容,并且缩容会存在一些问题,并且可能不能达到我们预期的效果。如下通过VS2022下的调试说明一下:
用于调试的代码:
int main()
{int* p = (int*)malloc(10 * sizeof(int));p[5] = 1;p = (int*)realloc(p, 5*sizeof(int));p[5] = 2;return 0;
}
分配10个整型的空间后,将第6个整型空间的值改为1,没问题;接下来将p的空间缩减为5个整型空间:
可以看到,后5个整型空间好像确实是回收给系统了,那么接下来执行a[5] = 2;
应为越界访问的行为,系统按理来说会报错,但实际是:
仍完成了对原空间第6个整型空间的值的更改,这就与我们的预期效果大相径庭了。故一般不用realloc来进行缩容。
三、常见动态内存错误
1、对NULL指针的解引用操作
若对动态开辟返回的指针不做检查就可能发生,如:
int *p = (int *)malloc(INT_MAX/4);
*p = 20; //如果p的值是NULL,就会有问题
free(p);
2、对动态开辟的内存空间进行了越界访问
和数组越界访问的问题类型:
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{exit(-1);
}
for(i=0; i<=10; i++)
{*(p+i) = i; //当i是10的时候越界访问
}
free(p);
3、对非动态开辟内存使用free释放
int a = 10;
int *p = &a;
free(p);
如上代码运行后系统崩溃:
4、 使用free释放一块动态开辟内存的一部分
int *p = (int *)malloc(100);
p++; //p不再指向动态内存的起始位置
free(p);
如上代码运行后系统崩溃:
5、对同一块动态内存多次释放
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
如上代码运行后系统崩溃:
6、使用完动态开辟的内存后忘记释放
若使用完动态开辟的空间后没有通过free函数进行内存释放,就会造成恐怖的内存泄露问题,体现在我们的程序中可能没什么大问题(程序会结束或关闭,顺带着内存就会回收);但若体现在一些长时间不停机服务器中,就会造成服务器越用越卡直至死机的严重后果。
四、关于内存管理的经典题目
了解完动态内存管理的基础知识后,可以看看一些关于内存管理的经典题目来趁热打铁,请看:
1、运行Test 函数的结果是?
void GetMemory(char *p)
{p = (char *)malloc(100);
}
void Test(void)
{char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}
结果:
程序会崩溃。
原因:
解引用了空指针。在GetMemory函数中动态分配内存后返回的指针赋值给了p
,但由于形参对实参的临时拷贝,故改变了p
并不影响str
,故在GetMemory函数调用结束后,str
的值仍为NULL
,在进行strcpy时发生了错误。
2、运行Test 函数的结果是?
char *GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char *str = NULL;str = GetMemory();printf(str);
}
结果:
程序会打印“烫烫烫烫烫烫…”的乱码
原因:
返回栈空间地址问题。GetMemory中p
为局部变量,其在出了GetMemory函数作用域后会销毁,销毁后其原指向的空间的值就为随机值,也就是说用str
接收的指针所指向的空间随机值,故再以打印字符串的方式去打印str的内容时就会乱码。
3、运行Test 函数的结果是?
void GetMemory(char **p, int num)
{*p = (char *)malloc(num);
}
void Test(void)
{char *str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}
结果:
正常输出hello
原因:
其实这才是相对于题目1的正确写法,由于实参本身就是一个指针,故需要一个二级指针来实现改变形参而改变实参。
4、运行Test 函数的结果是?
void Test(void)
{char *str = (char *) malloc(100);strcpy(str, "hello");free(str);if(str != NULL){strcpy(str, "world");printf(str);}
}
结果:
看似正常输出了world
原因:
在介绍free函数时有说到,free仅将动态开辟的空间的使用权还给系统,但并不改变用于“接收”(指向)这块空间的指针变量的值。也就是说,代码中的str
在free后的值仍未原空间的起始地址,不为空,但此时对原空间已没有使用权,故在进行strcpy时本质上已经是非法访问了内存空间,只是可能访问到的是空闲池而程序没有崩溃。
五、C/C++内存区域划分
C/C++内存区域主要划分为如下几个区域:
1、栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
2、 堆区(heap):一般由程序员申请分配与释放, 若程序员不释放,程序结束时可能由操作系统回收 。
3、数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。
4、代码段:存放函数体(类成员函数和全局函数)的二进制代码
六、拓展:柔性数组
1、定义
(1)概念:C99 中,结构中的最后一个元素允许是未知大小的数组,该数组就称为柔性数组成员
(2)定义方式:
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;
或
typedef struct st_type
{int i;int a[];//柔性数组成员
}type_a;
2、柔性数组的特点
其实包含柔性数组成员的结构体的特点,主要有如下三点:
(1)结构中的柔性数组成员前面必须至少一个其他成员
(2)sizeof 返回的这种结构大小不包括柔性数组的内存
(3)包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
3、柔性数组的使用及优势
(1)柔性数组的使用
柔性数组的使用主要在于对需要开辟的空间大小的把握,空间正确开辟后,和正常作为结构体成员的数组一样使用即可。下面是使用示例,请看:
type_a *p = (type_a*)malloc(sizeof(type_a)+10*sizeof(int));
//这里的柔性数组成员相当于获得了10个连续的整型空间//和正常数组一样使用即可
for(int i=0; i<10; i++)
{p->a[i] = i;
}free(p);
(2)与指针成员相比的优势
如上的结构体type_a
也可设计为:
typedef struct st_type
{int i;int* p_a;
}type_a;
这样在分配和释放空间时就会相对麻烦一些:
type_a *p = (type_a *)malloc(sizeof(type_a));
//先为整个结构体分配空间p->p_a = (int *)malloc(p->10 *sizeof(int));
//才能为里面的指针成员p分配空间for(int i=0; i<10; i++)
{p->p_a[i] = i;
}//要先释放指针成员的空间
free(p->p_a);
p->p_a = NULL;
//再释放整个结构体的空间
free(p);
p = NULL;
相比之下,我们可以得到柔性数组成员的两个优势:
(1)方便内存释放
有柔性数组成员的结构体在释放空间时仅需将为整个结构体分配的内存释放即可;而有指针成员的结构体在释放空间时需先释放指针为指针成员开辟的空间,才能释放为整个结构体开的空间,否则就会造成内存泄漏。
此时若是我们自己写的代码可能知道要先释放结构体指针成员的空间,但如果写的代码是给别人用时,用户可能就想着释放结构体就行,而不再会去在意结构体里有什么。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉,降低了内存泄漏的风险。
(2)一定程度上提高了内存访问速度
柔性数组成员的地址空间相对于整个结构体成员的空间地址是连续的,而指针成员则是碎片化的;如下示意图:
- 柔性数组成员:
- 指针成员:
连续的内存有益于提高访问速度。
本章完。
看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或看不懂的地方或有可优化的部分还恳请朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹
相关文章:
【逐步剖C】-第十一章-动态内存管理
一、为什么要有动态内存管理 从我们平常的学习经历来看,所开辟的数组一般都为固定长度大小的数组;但从很多现实需求来看需要我们开辟一个长度“可变”的数组,即这个数组的大小不能在建立数组时就指定,需要根据某个变量作为标准。…...
【树】树的直径和重心
目录 一.树的直径 (1)定义 (2)思路 (3)例题 (4)std(第一小问) 二.树的重心 (1)介绍 (2)求重心 (3)例…...
《Attention Is All You Need》论文笔记
下面是对《Attention Is All You Need》这篇论文的浅读。 参考文献: 李沐论文带读 HarvardNLP 《哈工大基于预训练模型的方法》 下面是对这篇论文的初步概览: 对Seq2Seq模型、Transformer的概括: 下面是蒟蒻在阅读完这篇论文后做的一…...
C++笔记之不同buffer数量下的生产者-消费者机制
C笔记之不同buffer数量下的生产者-消费者机制 文章目录 C笔记之不同buffer数量下的生产者-消费者机制0.在不同的缓冲区数量下,生产者-消费者机制的实现方式和行为的区别1.最简单的生产者-消费者实现:抄自 https://mp.weixin.qq.com/s/G1lHNcbYU1lUlfugXn…...
编码文字使用整数xyz 三个坐标 并使用
导航 说明原始描述AI理解的实现代码说明 原始描述 而后期的,相同的s,前缀差距 和 自身权重 要对应的上,或者说 假设每个序列都是三维空间上的点集合,使用最小的空间表达这些信息,整个数据集才是重点。这些点的集合可以 是空间直线或者是曲线 整体的思路是 一个集合能在任…...
创建vue3工程
一、新建工程目录E:\vue\projectCode\npm-demo用Visual Studio Code 打开目录 二、点击新建文件夹按钮,新建vue3-01-core文件夹 三、右键vue3-01-core文件夹点击在集成终端中打开 四、初始化项目,输入npm init 一直敲回车直到创建成功如下图 npm init 五…...
Flutter笔记 - 用于描述Align的Alignment、AlignmentDirectional、AlignmentTween类
Flutter笔记 用于描述Align的Alignment、AlignmentDirectional、AlignmentTween类 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_…...
门面模式简介
门面模式简介 门面模式(Facade Pattern)是一种结构性设计模式,它提供了一个简化复杂系统的接口,允许客户端通过一个统一的接口与系统交互,而不需要了解系统内部的复杂性。这个模式的目标是降低客户端与系统之间的耦合…...
2023年7月工作经历二
invoke的翻译 C#的这个关键字很熟,但不知道如何翻译比较好。和网友沟通,并查阅多篇博文,觉得“同步调用(invoke)和异步调用(beginvoke)”比较好。 VS2022很爽 C#的类名,会提示命名空间。C调试的时候&…...
7.wifi开发【智能家居:终】,实践总结:智能开关,智能采集温湿,智能灯。项目运行步骤与运行细节,技术归纳与提炼,项目扩展
一。项目运行步骤与运行细节 1.项目运行步骤(一定有其他的运行方式,我这里只提供一种我现在使用的编译方式) (1)项目运行使用软件与技术: 1.Virtual linux 使用这个虚拟机进行程序的编译 2.Makefile与shl…...
学习开发一个RISC-V上的操作系统(汪辰老师) — unrecognized opcode `csrr t0,mhartid‘报错问题
前言 (1)此系列文章是跟着汪辰老师的RISC-V课程所记录的学习笔记。 (2)该课程相关代码gitee链接; (3)PLCT实验室实习生长期招聘:招聘信息链接 正文 (1)在跟着…...
【计算机网络】 心跳机制
文章目录 心跳机制应用场景什么是心跳机制心跳包的发送,通常有两种技术 心跳机制 应用场景 在长连接下,有可能很长一段时间没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故…...
文心一言 VS 讯飞星火 VS chatgpt (106)-- 算法导论10.1 4题
四、用go语言,重写 ENQUEUE 和 DEQUEUE 的代码,使之能处理队列的下溢和上溢。 文心一言: 在 Go 语言中,我们可以使用切片来实现队列。下面是如何实现 ENQUEUE 和 DEQUEUE 操作,同时处理队列的下溢和上溢的情况&#…...
进程调度算法之时间片轮转调度(RR),优先级调度以及多级反馈队列调度
1.时间片轮转调度算法(RR) round Robin 1.算法思想 公平地、轮流地为各个进程服务,让每个进程在一定时间间隔内都可以得到响应。 2.算法规则 按照各进程到达就绪队列的顺序,轮流让各个进程执行一个时间片(如100ms)。 若进程未…...
ARMv8如何读取cache line中MESI 状态以及Tag信息(tag RAM dirty RAM)并以Cortex-A55示例
Cortex-A55 MESI 状态获取 一,系统寄存器以及读写指令二,Cortex-A55 Data cache的MESI信息获取(AARCH 64)2.1 将Set/way信息写入Data Cache Tag Read Operation Register2.2 读取Data Register 1和Data Register 0数据并解码 参考…...
密码技术 (6) - 证书
一. 前言 前面介绍的公钥密码和数字签名,都无法解决一个问题,那就是判断自己获取的公钥是否期望的,不能确定公钥是否被中间攻击人掉包。所以,证书的作用是用来证明公钥是否合法的。本文介绍的证书就是解决证书的可靠性的技术。 二…...
【算法学习】-【双指针】-【盛水最多的容器】
LeetCode原题链接:盛水最多的容器 下面是题目描述: 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。…...
JAVA面经整理(8)
一)为什么要有区,段,页? 1)页是内存和磁盘之间交互的基本单位内存中的值修改之后刷到磁盘的时候还是以页为单位的索引结构给程序员提供了高效的索引实现方式,不过索引信息以及数据记录都是记录在文件上面的,确切来说是…...
【Java 进阶篇】JDBC数据库连接池Druid详解
在Java应用程序中,与数据库进行交互是一个常见的任务。为了更有效地管理数据库连接并提高性能,数据库连接池是一种常见的解决方案。Druid是一个流行的JDBC数据库连接池,它具有丰富的功能和高性能。本博客将详细介绍Druid连接池,包…...
Linux——指令初识
Linux下基本指令 前言一、 ls 指令二、 pwd命令三、cd 指令四、 touch指令五、mkdir指令六、rmdir指令 && rm 指令七、man指令八、cp指令九、mv指令十、cat指令十一、.more指令十二、less指令十三、head指令十四、tail指令总结 前言 linux的学习开始啦! 今…...
专题一:双指针【优选算法】
双指针应用场景: 数组划分、数组分块 目录 一、移动0 二、复写0 从后向前 三、快乐数 链表带环 四、盛水最多的容器 单调性双指针 五、有效三角形个数 单调性双指针 六、和为s的两个数字 七、三数之和 细节多 需再练 一、移动0 class Solution { public:void move…...
蓝桥等考Python组别十二级007
第一部分:选择题 1、Python L12 (15分) 运行下面程序,输出的结果是( )。 lis = [A, B, C, D, E, F] print(lis[0 : 3]) [A, B, C][A, B][A, B, C, D][B, C, D]正确答案:A 2...
全方位介绍工厂的MES质量检验管理系统
一、MES质量检验管理系统的定义: MES质量检验管理系统是基于制造执行系统的框架和功能,专注于产品质量的控制和管理。它通过整合和优化质量检验流程,提供实时的数据采集、分析和反馈,帮助工厂实现高效的质量管理。该系统涵盖了从…...
避免风险,亚马逊、沃尔玛、阿里国际站选择什么样的测评方式最安全?
亚马逊、沃尔玛、速卖通、阿里国际站上做测评是最有效的推广手段之一,而测评又存在很大的风险。但是测评的风险来自哪里?什么样的测评方式才安全呢? 因为平台大数据风控点很多,根据洪哥六七年的测评经验,风控包括以下…...
【C语言】语法--联合体union详解
本文参考博客: https://blog.csdn.net/m0_57180439/article/details/120417270 定义及示例: 联合是一种特殊的自定义类型,该种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间,所以联合体也被称为共用体。 #include<stdio.h> union Un//联合类型…...
接口测试复习
一。基本概念 接口概念:系统与系统之间 数据交互的通道。 接⼝测试概念:校验 预期结果 与 实际结果 是否⼀致。 特征: 测试⻚⾯测试发现不了的问题。(因为:接⼝测试 绕过前端界⾯。 ) 符合质量控制前移理…...
获取医疗器械板块的个股列表
获取医疗器械板块的个股列表,用python爬虫做到(数据网址:板块 - 医疗器械概念 - 股票行情中心 - 搜狐证券) import requests from bs4 import BeautifulSoup # 获取医疗器械概念个股列表url "https://q.stock.sohu.com/cn/…...
1026 程序运行时间
要获得一个 C 语言程序的运行时间,常用的方法是调用头文件 time.h,其中提供了 clock() 函数,可以捕捉从程序开始运行到 clock() 被调用时所耗费的时间。这个时间单位是 clock tick,即“时钟打点”。同时还有一个常数 CLK_TCK&…...
博途1200/1500 ALT指令
SMART PLC的ALT指令实现代码,请查看下面文章博客 SMART PLC如何构造ALT指令_smart200类似alt指令-CSDN博客单按钮启停这些老生常谈的问题,很多人感兴趣。这篇博文讨论下不同的实现方法,希望对大家有所帮助。指令虽然简单,但是在编程的时候合理使用对我们高效率编程帮助还是…...
11、视频分类建议
8、绩效看板与日清计划 9、大小屏分离与精细化审核 10、质量审核的设立与合并 视频分类印象深刻,因为这是我亲手做的第一个增效工具。 审核的其中一个任务是保证视频分类信息的准确性,账号本身是有一个缺省分类的,内容上传之后默认使用账号…...
哪种源码做视频网站好用/网站seo优化建议
阿里云GPU云服务器在公有云上提供的弹性GPU服务,可以帮助用户快速用上GPU加速服务,并大大简化部署和运维的复杂度。GPU云服务器多适用于AI深度学习,科学计算,视频处理,图形可视化,等应用场景,有AMD S7150&a…...
wui网站建设/恶意点击广告软件
目录 点积(dot product) 代数定义 几何定义 与内积的关系 叉积(corss product) 定义 几何意义 内积(inner product) 定义 例子 外积(outer product) 定义 与欧几里得内积对比 张量的外积 点积(dot product) 又叫标量积、数量积(scalar product)。它是两个数字序…...
全网营销推广网站建设/网站优化是什么意思
当页面使用 utf-8 编码时,<title>标签被放在<meta>标签前面。当title为中文的时(比如Blog名为中文或者文章标题为中文),在IE下会出现显示空白页的问题。由于 utf-8 使用3个字节表示一个汉字,而GB2312或BIG5使用两个字节。页面输出…...
网站设计中级/seo关键词优化指南
又是一年毕业季,还没踏出校门,就开始夹杂着各种恐惧与迷茫。迷茫自己未来的定位在哪里。尽管有着过硬的专业知识基础,但习惯了象牙塔的生活,对职场的陌生感仍然挡不住无尽的恐惧,焦虑如何在求职大军中脱颖而出。 我认为…...
哪个网站可以付费做淘宝推广/优化公司怎么优化网站的
说明:使用交互式DBMS工具重要的是,要理解联结不是物理实体。换句话说,它在实际的数据库表中并不存在。DBMS会根据需要建立联结,它在查询执行期间一直存在。 一、等值语法:SELECT 字段 FROM 表1,表2 WHERE 表1.字段表2.…...
做网站导航栏目怎么做/职业技能培训网
这道题是LeetCode里的第937道题。 题目描述: 你有一个日志数组 logs。每条日志都是以空格分隔的字串。 对于每条日志,其第一个字为字母数字标识符。然后,要么: 标识符后面的每个字将仅由小写字母组成,或;标…...