目录

一、为什么存在动态内存分配

二、动态内存函数的介绍

1、1 malloc

1、2 free

1) 动态开辟多少个字节的内存空间,返回该空间的起始地址;且开辟的空间使用方法,类似于数组,是一块连续的空间,使用时如数组一样

2)内存申请是有限制的,当开辟空间过大,内存不够时,会返回空指针

3、calloc

4、realloc

关于realloc函数开辟空间的几种情况:

三、常见的动态内存错误

1.对NULL指针的解引用操作

2.对动态开辟空间的越界访问

3.对非动态开辟内存使用free释放

4.使用free释放一块动态开辟内存的一部分

5.同一块动态内存多次释放

6.内存泄露

堆上的内存释放只有两种可能:1.free释放 2.程序结束否则会出现 内存泄露

可以结合一个故事来看:

四、几个经典的笔试题

1.

2.

3.

4.

5.


一、为什么存在动态内存分配

我们已经掌握的内存开辟方式有:
int val = 20 ; // 在栈空间上开辟四个字节
char arr [ 10 ] = { 0 }; // 在栈空间上开辟 10 个字节的连续空间

但是上述的开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了。
这时候就只能试试动态存开辟了。

二、动态内存函数的介绍

1、1 malloc

1、2 free

1) 动态开辟多少个字节的内存空间,返回该空间的起始地址;且开辟的空间使用方法,类似于数组,是一块连续的空间,使用时如数组一样


//**********  malloc - 动态开辟多少个字节的内存空间,返回该空间的起始地址
//且开辟的空间类似于数组,是一块连续的空间,使用时如数组一样
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main()
{//申请开辟空间int* p = (int*)malloc(20);//动态开辟20个字节的内存空间,返回该空间的起始地址,用p保存下来if (p == NULL)//如果开辟空间失败会返回空指针,所以要判断一下{printf("%s", strerror(errno));return 1;}//使用空间int i = 0;for (i = 0; i < 5; i++){*(p + i) = i + 1;//p[i] = i + 1; //都可以表示printf("%d ", p[i]);}//1 2 3 4 5//释放空间:主动申请,主动释放,释放后置为空指针free(p);//如果不释放,则为野指针,后期存在隐患p = NULL;return 0;
}

2)内存申请是有限制的,当开辟空间过大,内存不够时,会返回空指针

//内存申请是有限制的,当开辟空间过大,内存不够时,会返回空指针
int main()
{//申请开辟空间int* p = (int*)malloc(2000000000);//动态开辟2000000000个字节的内存空间,空间不够返回空指针if (p == NULL)//如果开辟空间失败会返回空指针,所以要判断一下{printf("%s", strerror(errno));//此时返回报错信息:Not enough spacereturn 1;}//使用空间//释放空间:主动申请,主动释放,释放后置为空指针free(p);p = NULL;//如果不置空,则为野指针,后期存在隐患return 0;
}

3、calloc

//*********  calloc - 开辟多少个什么类型的空间,并且初始化为0
int main()
{//申请开辟空间int* p = (int*)calloc(10, sizeof(int));//动态开辟10个整型类型的内存空间,返回该空间的起始地址,用p保存if (p == NULL)//同样要判断一下:如果开辟空间失败会返回空指针{printf("calloc-->%s", strerror(errno));return 1;}//使用空间int i = 0;for (i = 0; i < 10; i++){printf("%d ", p[i]);}//没有赋值,打印发现,默认初始化为0了//0 0 0 0 0 0 0 0 0 0//释放空间:主动申请,主动释放,释放后置为空指针free(p);p = NULL;//如果不置空,则为野指针,后期存在隐患return 0;
}

//malloc 和 calloc 的区别:
/*
1.参数不一样1)malooc(要开辟的字节数)2)calloc(开辟几个,数据类型的大小)
2.虽然都是再堆区申请内存空间,但是malloc不初始化,而calloc会初始化为01)如果需要初始化,就使用calloc2)如果不需要初始化,就使用malloc
*/

4、realloc

realloc 函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。
函数原型如下:
ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。

realloc在调整内存空间的是存在两种情况:

情况 1
当是情况 1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况 2
当是情况 2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小
的连续空间来使用。这样函数返回的是一个新的内存地址。
由于上述的两种情况, realloc 函数的使用就要注意一些。
举个例子:

1.realloc重新调整动态开辟的空间的大小

int main()
{//开辟20字节空间int* p = (int*)malloc(20);if (p == NULL){printf("%s\n", strerror(errno));return 1;}//使用int i = 0;for (i = 0; i < 5; i++){p[i] = i + 1;printf("%d ", p[i]);}//1 2 3 4 5//realloc//20个字节已经使用完了,如果此时,还想继续使用这一块空间,需要扩容,这时realloc可以发挥作用了int* str=(int*)realloc(p, 400000);//从动态开辟空间的起始位置,往后重新调整为40个字节的空间//int* str=(int*)realloc(p, 400000);//换成400000观察,开辟前后的地址变化if (str != NULL){p = str;}else{printf("%s\n", strerror(errno));return 1;}//继续使用该空间for (i = 5; i < 10; i++){p[i] = i + 1;printf("%d ", p[i]);}//6 7 8 9 10//释放空间free(p);p = NULL;return 0;
}

关于realloc函数开辟空间的几种情况:

可能存在新开辟空间的地址与原有的地址不同的情况
一、开辟空间成功:
    1.第一种:如果原有的空间后面有足够的空间,可以直接接着开辟新的空间,然后返回原有的空间的地址。
(返回原有空间的地址)
    2.第二种:如果原有的空间后面没有足够的空间,那么会再内存里找一块更大的空间,新开辟这块空间,
然后将原有空间的数据拷贝到这块新空间里,释放原有的空间,并返回这个新空间的地址。
(返回新空间的地址)
二:开辟空间失败
    返回空指针

注意:新开辟的空间返回的指针,不能直接使用原始的指针p来接收,必须先用一个临时的指针接收看看是否为空指针。
    因为:如果开辟新空间失败,那么会返回NULL空指针,p原本指向一块空间,此时被置为空,那么原来空间里的数据就没人能找到了,数据就丢失了。
(开辟新空间不成,还把原有空间的内容搞丢了,俗话叫赔了夫人又折兵)
    所以,realloc之后,要判断一下指针是否为空指针,如果不为空,则再用p接收
        int* str=(int*)realloc(p, 40);
    if (str != NULL)
    {
        p = str;
    }
    else
    {
        printf("%s\n", strerror(errno));
        return -1;
    }

2.realloc也可以像malloc一样申请空间

int main()
{//malloc//calloc//realloc - 调整申请的堆区内存的大小的//补充:realloc也可以像malloc一样申请空间int* p = (int*)realloc(NULL, 20);//malloc(20);int i = 0;for (i = 0; i < 5; i++){p[i] = i + 1;printf("%d ", p[i]);}free(p);p = NULL;return 0;
}

三、常见的动态内存错误

1.对NULL指针的解引用操作


int main()
{int* p = (int*)malloc(20);//如果p的值是NULL,就会有问题int i = 0;for (i = 0; i < 5; i++){p[i] = i + 1;}free(p);p = NULL;
}

修改:

//修改:
int main()
{int* p = (int*)malloc(20);//对p做一个空指针NULL的判断if (p == NULL){printf("%s\n", strerror(errno));return 1;}int i = 0;for (i = 0; i < 5; i++){p[i] = i + 1;}free(p);p = NULL;
}

2.对动态开辟空间的越界访问

//2.对动态开辟空间的越界访问
int main()
{int* p = (int*)malloc(20);//对p做一个空指针NULL的判断if (p == NULL){printf("%s\n", strerror(errno));return 1;}//越界访问 - 程序崩溃了int i = 0;for (i = 0; i < 10; i++)//原本20个字节,只能访问5个整型,现在越界访问(从i=5开始越界){p[i] = i + 1;}free(p);p = NULL;
}

修改:

int main()
{int* p = (int*)malloc(20);//对p做一个空指针NULL的判断if (p == NULL){printf("%s\n", strerror(errno));return 1;}//越界20字节以内的int i = 0;for (i = 0; i < 5; i++)//只访问5个整型{p[i] = i + 1;}free(p);p = NULL;
}

3.对非动态开辟内存使用free释放

//3.对非动态开辟内存使用free释放
int main()
{//非动态开辟内存 - 不在堆上int arr[10] = { 1,2,3,4,5 };int* p = arr;//对非动态开辟内存使用free释放 - 程序崩溃了free(p);p = NULL;return 0;
}

4.使用free释放一块动态开辟内存的一部分

//4.使用free释放一块动态开辟内存的一部分
int main()
{int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 1;}//使用 [1] [2] [3] [4] [5] [] [] [] [] []int i = 0;for (i = 0; i < 5; i++){*p = i + 1;p++;}//此时p不再指向内存的起始位置,p指向i=5的位置;//释放内存的一部分,程序崩溃free(p);p = NULL;return 0;
}

5.同一块动态内存多次释放

//5.同一块动态内存多次释放
int main()
{int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 1;}//释放一次free(p);//又释放一次,会崩溃free(p);//此时p是一块未初始化的指针,释放会出问题p = NULL;return 0;
}

修改:

int main()
{int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 1;}//释放一次free(p);p = NULL;//第一次释放时,置空,后面再释放没问题//又释放一次free(p);//此时p是NULL空指针,释放没有任何作用,不会报错p = NULL;return 0;
}

6.内存泄露

堆上的内存释放只有两种可能:1.free释放 2.程序结束
否则会出现 内存泄露

//6.堆上的内存释放只有两种可能:1.free释放 2.程序结束
//否则会出现 内存泄露
test()
{int* p = (int*)malloc(20);//此时,函数调用使用栈空间,p临时变量也是再栈空间,调用结束回收栈空间//而malloc开辟的空间时堆空间,不会因为函数结束而释放//所以该函数回收之后,指针p被回收,malloc开辟空间还在,而此时没有人知道该空间的位置了//造成了内存泄露
}
int main()
{test();return 0;
}

可以结合一个故事来看:

假设test函数时一个案子,要破这个案子,派卧底p监视malloc的情况,只有p知道这个malloc的位置。然后这个案子结束的时候,p死了,那么malloc的线索在这里
就断了,谁也不知道malloc的具体情况了,这个test回收之后,malloc还没被抓出来,没被释放一直占据着内存,内存泄露。

所以malloc使用时:自己申请,尽量自己释放。自己不释放,告诉别人要别人释放。

内存泄露是一个很可怕的事情:
    假如有一个程序运行在服务器里,这个服务器时24小时运行的,程序永远不会结束,然后在里面运用了malloc开辟空间,却又不释放,然后这个malloc就会一直不断占内存,吃资源,直到内存没了程序崩溃,服务器卡死。

(一直占着内存,又不用,又不释放,程序又不结束。其他想用内存的人用不了,程序最终会崩溃。俗话:占着茅坑不拉屎,又不把坑让出来)

四、几个经典的笔试题

1.

//1.请问运行Test 函数会有什么样的结果?
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;
}

结果:无法打印。
问题:
1.GetMemory(str),传过去的参数是值,值传递(传过去的是指针变量,指针变量,也是变量也是值传递)p存放的malloc开辟空间的地址,不会影响到str,str依然是空指针NULL。
函数销毁之后,p没了,str为NULL,strcpy(str, "hello world")调用失败,原因是对NULL的解引用时出错,程序崩溃。
2.在GetMemory函数内malloc开辟的空间,GetMemory销毁之后,malloc空间依然存在,但是没有释放,内存泄露。

可以理解为:卧底p死了,没人知道malloc的信息

修改:

//方法1.函数里返回指针,主函数接收指针保存
char* GetMemory()
{char* p = (char*)malloc(100);return p;
}
void Test(void)
{char* str = NULL;str=GetMemory();strcpy(str, "hello world");printf(str);free(str);str = NULL;
}
int main()
{Test();return 0;
}//方法2.对传过去的指针取地址(二级指针),这样可以改变到实参
void GetMemory(char** p)
{*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);free(str);str = NULL;
}
int main()
{Test();return 0;
}

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;
}

结果:烫烫烫烫烫烫烫烫?(无法打印想要的结果)
问题:
    GetMemory函数内部创建的数组是临时的,虽然返回了数组的起始地址,而且str接收了GetMemory函数返回的字符串"hello world"的起始地址,但是由于数组作为临时函数GetMemory的临时变量,出了函数就被回收了销毁了,内容已经不存在了。如果使用str,str就是野指针,打印出来就是个随机值。此时这里printf(str)是非法访问。

相当于:
    卧底p知道罪犯的地址"hello world",它告诉了str,但是str正准备抓犯人的时候,这个地址的罪犯人间蒸发了,不见了,找不到了。这个地址现在也不知道住了哪些人。硬是要抓里面的人,就是随机的。

也可以理解为:
    a定了酒店当天的房间203,(此时地址p相当于203,a为"hello world",p指向了"hello world"的起始地址;203指向了a的地址),a告诉b房间地址203,让他当天(在函数内)来见a。(str相等与b,str也保存了"hello world"的起始地址),但是b去找a的时候,a退房了(出了函数,被回收了),此时203房间该地址不指向a了(str指向的内容没了),里面可能住了别的客人。然后b(str现在为野指针),硬要访问此时203房间的人(非法访问),见到的就是随机的人。(随机值)

同理:这题也存在问题

//同理:这题也存在问题
test()
{int a = 10;return &a;
}
int main()
{int* p = test();printf("hehe\n");//printf("%d\n", *p);return 0;
}
/*
问题:test函数里的整型a是临时的,虽然返回了a的地址,但是出了函数,a的内容就回收了
p(野指针)现在指向的位置是一个未知的值,打印出来是随机值。
打印会发现的确是10啊,这个场景下是巧合,刚好test回收的时候,a的地址还没被其他的栈帧空间
覆盖。如果在中间加入其他的东西,就不一样了。printf("hehe\n");//就覆盖了a的空间,导致a的地址存放的值变了。
结果:hehe5*/

3.

int* f2(void)
{int* ptr;//指针未初始化,没有任何指向,野指针,里面是个随机值。*ptr = 10;//野指针解引用,非法访问return ptr;
}

4.

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;
}

问题:
    malloc空间没有释放。内存泄露

修改:

void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);//释放空间free(str);str = NULL;
}
int main()
{Test();return 0;
}

5.

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;
}

问题:
    释放了malloc开辟的空间,但是str没置空,然后依然是非空指针,
    strcpy(str, "world");就非法访问了。

修改:

void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);str = NULL;//str要置为空if (str != NULL){strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}

五、柔性数组(结构体成员)

1 柔性数组的特点:
  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

1.柔性数组的定义

柔性数组的[ ]里面,可以写0,也可以空着不写。

这两种都可以

2.柔性数组的使用

struct S
{int n;char c;int arr[0];//柔性数组成员
};
int main()
{//malloc开辟空间(结构体类型)struct S* ps = (struct S*) malloc(sizeof(struct S) + 10 * sizeof(int));//                       结构体struct S的大小 + 自己定义柔性数组的大小(10个整型大小)//判断开辟之后空间的首地址if (ps == NULL){perror("malloc->\n");return 1;}//使用ps->n = 6;ps->c = 'z';int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i + 1;printf("%d ", ps->arr[i]);}//假设空间不足,要扩大空间(柔性数组增加10个整型,原来的10个增加到20个整型)struct S* ptr = realloc(ps, sizeof(struct S) + 20 * sizeof(int));//判断ptrif (ptr == NULL){perror("realloc->\n");return 1;}else{ps = ptr;}//再次使用扩容后的空间for (i = 10; i < 20; i++){ps->arr[i] = i + 1;printf("%d ", ps->arr[i]);}//释放空间free(ps);ps = NULL;return 0;
}

3.柔性数组的优势

实现同样的功能,可以不使用柔性数组,通过指针

两个方法比较:
上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处:

第一个好处是:方便内存释放
    如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

第二个好处是:这样有利于访问速度.
    连续的内存有益于提高访问速度,也有益于减少内存碎片。

struct S
{int n;char c;int* arr;
};
int main()
{//malloc开辟空间struct S* ps = (struct S*)malloc(sizeof(struct S));//判断空指针if (ps == NULL){perror("malloc1(结构体空间)->\n");return 1;}//malloc开辟整型数组空间int* ptr = (int*)malloc(10 * sizeof(int));//判断空指针if (ptr == NULL){perror("malloc2(数组空间)->\n");return 1;}else{ps->arr = ptr;}//使用结构体空间ps->n = 100;ps->c = 'a';int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i + 1;printf("%d ", ps->arr[i]);}//假如空间不足,扩容ptr = (int*)realloc(ps->arr,20 * sizeof(int));//判断空指针if (ptr == NULL){perror("realloc(数组)->\n");return 1;}else{ps->arr = ptr;}//使用扩容的空间for (i = 10; i < 20; i++){ps->arr[i] = i + 1;printf("%d ", ps->arr[i]);}//先释放里面的空间(数组),再释放外面的空间(结构体)free(ptr);ptr = NULL;free(ps);ps = NULL;return 0;
}

动态内存管理 - malloc、calloc、realloc、柔性数组相关推荐

  1. C语言-动态内存管理(malloc()、calloc()、realloc()、free())

    C语言 动态内存分配 文章目录 C语言 动态内存分配 前言 一.为什么存在动态内存分配? 二.动态内存函数的介绍 1.初识malloc()和free() 2.malloc()和free()的简单使用 ...

  2. 内存分布malloc/calloc/realloc/free/new/delete、内存泄露、String模板、浅拷贝与深拷贝以及模拟string类的实现

    内存分布 一.C语言中的动态内存管理方式:malloc/calloc/realloc和free 1.malloc: 从堆上获得指定字节的内存空间,函数声明:void *malloc (int n); ...

  3. C语言学习笔记10-指针(动态内存分配malloc/calloc、realloc、释放free,可变数组实现;Tips:返回指针的函数使用本地变量有风险!;最后:函数指针)

    C语言:指针 1. 指针:保存地址的变量 *p (pointer) ,这种变量的值是内存的地址.   取地址符& 只用于获取变量(有地址的东西)的地址:scanf函数-取地址符   地址的大小 ...

  4. C++---动态内存管理

    C/C++内存分布 栈:存储非静态局部变量.函数参数.返回值. 内存映射段:动态库. 堆:程序运行时动态内存分配. 数据段:存储全局变量.静态数据. 代码段:可执行代码,只读常量. C语言中动态内存管 ...

  5. C语言 --- 动态内存管理(上)+优化版通讯录+笔试题

    文章目录 前言 一.为什么存在动态内存分配 二.动态内存函数的介绍 2.1.malloc函数+free函数 2.2.calloc函数+free函数 2.3.realloc函数 三.常见的动态内存错误 ...

  6. C语言程序设计 | 动态内存管理:动态内存函数介绍,常见的动态内存错误,柔性数组

    动态内存管理目录: 动态内存函数的介绍 常见的动态内存函数的错误 柔性数组 为什么会有动态内存管理呢 我们在日常使用中,创建一个数组,一个变量时都会开辟空间 如: int a; //在栈上开辟一个四字 ...

  7. 动态内存分布——malloc,calloc,realloc,free的使用。以及关于动态内存的常见错误。

    我们知道内存的使用方式,可以在栈区,静态区,堆区,申请空间来储存变量. 但是他们这些内存区所存储的东西是不一样的. 局部变量 函数的形式参数 栈区 动态内存分配申请的空间 malloc,calloc, ...

  8. C语言动态申请内存空间之malloc(),calloc(),realloc()函数

    在C语言中用于动态申请内存空间的函数主要为malloc()函数,calloc()函数,以及realloc()函数,以下主要介绍三个函数的用法,区别以及使用时的注意事项. malloc(),calloc ...

  9. 动态内存管理:malloc和free以及new和delete的联系与区别

    动态内存管理:malloc和free以及new和delete的联系与区别 文章目录 动态内存管理:malloc和free以及new和delete的联系与区别 一. C/C++中程序内存区域划分: 二. ...

最新文章

  1. 机器学习,就用Python!五大专家详解其优势何在
  2. 【web前端】可筛选[输入搜索]的select(重写)
  3. Servlet的第一个程序HelloWorld
  4. 如何使用TCP套接字的端口来区分是哪个客户端发起的连接
  5. 学习计算机游戏编程,在线游戏学编程,游戏编程汇总
  6. bash中(),{},(()),[],[[]]的区别
  7. AjaxFileUpload.js
  8. BP神经网络(手写数字识别)
  9. win7+vs2008+windows mobile6.5.3
  10. 【Linux】ubuntu 16 启动拨号上网
  11. win10 visualBox 新建虚拟机出现 UUID 错误
  12. SQL的几种连接查询方式(内连接、外连接、全连接、联合查询)
  13. 英特尔第十代处理器为什么不支持win7_为什么7代CPU不支持WIN7,原因是什么-i7不支持win7,win7最高支持几代cpu...
  14. java中为什么要用json_Java中json的使用和解析
  15. matlab 怎麼卸載乾淨,matlab set gca用法
  16. Python制作gif动态图
  17. 2020-08-31
  18. HTML—— 超链接 行内框架 表格 知识总结。
  19. typedef的用法。
  20. 【基础算法】试除法求约数(Acwing869题)

热门文章

  1. 大数据早报:河南引入智能办税机器人 平安科技人脸识别落地南非(11.21)
  2. 恒业微晶冲刺创业板上市:计划募资8亿元,戴联平为实控人
  3. pandas条件复合筛选(多条件、与、或)
  4. 初征——淘特多店铺智能化运营的订单管理系统
  5. Go语言go modure的使用以及第三方包的依赖管理
  6. elementui中el-table实现翻页、全选
  7. remote: Permission to .git denied to user.
  8. java简介以及环境安装
  9. Traceback (most recent call last): File gtmc.py, line 3, in module ModuleNotFoundError: No mod...
  10. 罗马时钟时间格式JS