C++中函数调用的整个过程内存堆栈分配详解

news2025/7/28 5:57:01

函数调用过程中:实参将值拷贝给函数的形参,而函数的形参相当于一个生存周期位于函数
内部的局部变量,函数内部的内存操作也只是将拷贝到形参的值进行操作,形参在函数结束
后会被栈自动回收释放(形参在栈中分配),这就是为什么值传递后在形参中改变不了实参的值
了,因为实参只是把值拷贝给了形参,而实参是在另一个堆栈地址中。
int swap(int x, int y)//实参x和y的值并没有交换
{
int temp;
temp = x; x = y; y = temp;
return temp;
}
所以只有引用传递,即相当于
把一个值类型的变量的地址传给形参,在形参里操作这个地址(即指针地址)才能改变实参对应的地址中的
值,如下就是引用传递,形参为指针类型。
int swap(int *x, int *y)//这里形参x和y是在栈中分配的一块内存并拷贝了实参的指针值,即x和y中的内容分别=实参的指针变量的值。
{
int temp;
temp = *x; *x = *y; *y = temp;
return temp;
}
main()
{
int a=8;
int b=9;
int* aa=&a;
int* bb=&b
swap(aa,bb)//这里形参是在栈中分配的一块内存并拷贝了实参aa,bb的指针值,即形参中的内容分别=实参的指针变量的值。
}

另外,如果在函数内部声明定义一个变量并且是在栈中分配内存的,那么这个变量是局部变量将会在函数运行结束自动被栈回收,如果是在堆中分配内存的那么这个变量一般都是指针类型即指针变量,这个指针变量本身也是在栈中分配的函数结束会自动被栈回收,但是它指向的内存地址实在堆中分配需要手动释放才行。

func()

{

int* a=new int(0);//a是局部变量是在栈中分配的一个存储指针地址的变量(32位程序的话就是4个字节大小,会在函数结束后被栈回收释放),而a所指向的地址(new int(0)是在堆中分配内存的一种方式,其他的还有 *p = (char *)malloc(num)等方式)是在堆中分配需要手动释放(函数结束后系统不会自动处理堆中的内存)。

}

 

/***************************************************

一、 函数参数传递机制的基本理论 
  函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题。基本的参数传递机制有两种:值传递和引用传递。以下讨论称调用其他函数的函数为主调函数,被调用的函数为被调函数。
  值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
  引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

二、 C语言中的函数参数传递机制
  在C语言中,值传递是唯一可用的参数传递机制。但是据笔者所知,由于受指针变量作为函数参数的影响,有许多朋友还认为这种情况是引用传递。这是错误的。请看下面的代码:
int swap(int *x, int *y)
{
int temp;
temp = *x; *x = *y; *y = temp;
return temp;
}
void main()
{
int a = 1, b = 2;
int *p1 = &a;
int *p2 = &b;
swap(p1, p2)
}
  函数swap以两个指针变量作为参数,当main()调用swap时,是以值传递的方式将指针变量p1、p2的值(也就是变量a、b的地址)放在了swap在堆栈中为形式参数x、y开辟的内存单元中。这一点从以下的汇编代码可以看出(注释是笔者加的):
22: void main()
23: {
……
……
13: int a = 1, b = 2;
00401088 mov dword ptr [ebp-4],1
0040108F mov dword ptr [ebp-8],2
14: int *p1 = &a;
00401096 lea eax,[ebp-4]
00401099 mov dword ptr [ebp-0Ch],eax
15: int *p2 = &b;
0040109C lea ecx,[ebp-8]
0040109F mov dword ptr [ebp-10h],ecx
16: swap(p1, p2);
004010A2 mov edx,dword ptr [ebp-10h] ;参数p2的值进栈
004010A5 push edx
004010A6 mov eax,dword ptr [ebp-0Ch] ;参数p1的值进栈
004010A9 push eax
004010AA call @ILT+15(swap) (00401014) ;调用swap函数
004010AF add esp,8 ;清理堆栈中的参数
17: }
  阅读上述代码要注意,INTEL80x86系列的CPU对堆栈的处理是向下生成,即从高地址单元向低地址单元生成。从上面的汇编代码可知,main()在调用swap之前,先将实参的值按从右至左的顺序压栈,即先p2进栈,再p1进栈。调用结束之后,主调函数main()负责清理堆栈中的参数。Swap 将使用这些进入堆栈的变量值。下面是swap函数的汇编代码:
14: void swap(int *x, int *y)
15: {
00401030 push ebp
00401031 mov ebp,esp ;ebp指向栈顶
……
……
16: int temp;
17: temp = *x;
4: int temp;
5: temp = *x;
00401048 mov eax,dword ptr [ebp+8] ;操作已存放在堆栈中的p1,将p1置入eax
0040104B mov ecx,dword ptr [eax] ;通过寄存器间址将*p1置入ecx
0040104D mov dword ptr [ebp-4],ecx;经由ecx将*p1置入temp变量的内存单元。以下类似
6: *x = *y;
00401050 mov edx,dword ptr [ebp+8]
00401053 mov eax,dword ptr [ebp+0Ch]
00401056 mov ecx,dword ptr [eax]
00401058 mov dword ptr [edx],ecx
7: *y = temp;
0040105A mov edx,dword ptr [ebp+0Ch]
0040105D mov eax,dword ptr [ebp-4]
00401060 mov dword ptr [edx],eax
8: return temp;
00401062 mov eax,dword ptr [ebp-4]
9: }
由上述汇编代码基本上说明了C语言中值传递的原理,只不过传递的是指针的值而已。本文后面还要论述使用引用传递的swap函数。从这些汇编代码分析,这里我们可以得到以下几点:
  1. 进程的堆栈存储区是主调函数和被调函数进行通信的主要区域。
  2. C语言中参数是从右向左进栈的。
  3. 被调函数使用的堆栈区域结构为:
    局部变量(如temp)
    返回地址
    函数参数
    低地址 
    高地址
  4. 由主调函数在调用后清理堆栈。
  5. 函数的返回值一般是放在寄存器中的。
  这里尚需补充说明几点:一是参数进栈的方式。对于内部类型,由于编译器知道各类型变量使用的内存大小故直接使用push指令;对于自定义的类型(如structure),采用从源地址向目的(堆栈区)地址进行字节传送的方式入栈。二是函数返回值为什么一般放在寄存器中,这主要是为了支持中断;如果放在堆栈中有可能因为中断而被覆盖。三是函数的返回值如果很大,则从堆栈向存放返回值的地址单元(由主调函数在调用前将此地址压栈提供给被调函数)进行字节传送,以达到返回的目的。对于第二和第三点,《Thinking in C++》一书在第10章有比较好的阐述。四是一个显而易见的结论,如果在被调函数中返回局部变量的地址是毫无意义的;因为局部变量存于堆栈中,调用结束后堆栈将被清理,这些地址就变得无效了。

三、 C++语言中的函数参数传递机制
  C++既有C的值传递又有引用传递。在值传递上与C一致,这里着重说明引用传递。如本文前面所述,引用传递就是传递变量的地址到被调函数使用的堆栈中。在C++中声明引用传递要使用"&"符号,而调用时则不用。下面的代码是使用引用传递的swap2函数和main函数:
int& swap2(int& x, int& y) 
{
int temp;
temp = x;
x = y;
y = temp;
return x;
}

void main()
{
int a = 1, b = 2;
swap2(a, b);
}
  此时函数swap2将接受两个整型变量的地址,同时返回一个其中的一个。而从main函数中对swap2的调用swap2(a, b)则看不出是否使用引用传递,是否使用引用传递,是由swap2函数的定义决定的。以下是main函数的汇编代码:
11: void main()
12: {
……
……
13: int a = 1, b = 2;
00401088 mov dword ptr [ebp-4],1 ;变量a
0040108F mov dword ptr [ebp-8],2 ;变量b
14: swap2(a, b);
00401096 lea eax,[ebp-8] ;将b的偏移地址送入eax
00401099 push eax ;b的偏移地址压栈
0040109A lea ecx,[ebp-4] ;将a的偏移地址送入ecx

0040109D push ecx ;将a的偏移地址压栈
0040109E call @ILT+20(swap2) (00401019) ;调用swap函数
004010A3 add esp,8 ;清理堆栈中的参数
15: }
可以看出,main函数在调用swap2之前,按照从右至左的顺序将b和a的偏移地
址压栈,这就是在传递变量的地址。此时swap2函数的汇编代码是:
2: int& swap2(int& x, int& y)
3: {
00401030 push ebp
00401031 mov ebp,esp
……
……
4: int temp;
5: temp = x;
00401048 mov eax,dword ptr [ebp+8]
0040104B mov ecx,dword ptr [eax]
0040104D mov dword ptr [ebp-4],ecx
6: x = y;
00401050 mov edx,dword ptr [ebp+8]
00401053 mov eax,dword ptr [ebp+0Ch]
00401056 mov ecx,dword ptr [eax]
00401058 mov dword ptr [edx],ecx
7: y = temp;
0040105A mov edx,dword ptr [ebp+0Ch]
0040105D mov eax,dword ptr [ebp-4]
00401060 mov dword ptr [edx],eax
8: return x;
00401062 mov eax,dword ptr [ebp+8] ;返回x,由于x是外部变量的偏移地
;址,故返回是合法的
9: }
  可以看出,swap2与前面的swap函数的汇编代码是一样的。这是因为前面的swap函数接受指针变量,而指针变量的值正是地址。所以,对于这里的swap2和前面的swap来讲,堆栈中的函数参数存放的都是地址,在函数中操作的方式是一致的。但是,对swap2来说这个地址是主调函数通过将实参变量的偏移地址压栈而传递进来的--这是引用传递;而对swap来说,这个地址是主调函数通过将实参变量的值压栈而传递进来的--这是值传递,只不过由于这个实参变量是指针变量所以其值是地址而已。
  这里的关键点在于,同样是地址,一个是引用传递中的变量地址,一个是值传递中的指针变量的值。我想若能明确这一点,就不至于将C语言中的以指针变量作为函数参数的值传递情况混淆为引用传递了。
  虽然x是一个局部变量,但是由于其值是主调函数中的实参变量的地址,故在swap2中返回这个地址是合法的。
  c++ 中经常使用的是常量引用,如将swap2改为:
    Swap2(const int& x; const int& y)
  这时将不能在函数中修改引用地址所指向的内容,具体来说,x和y将不能出现在"="的左边。

四、 结束语
   本文论述了在 C 和 c++ 中函数调用的参数传递机制;同时附带说明了函数返回值的一些问题。本文示例使用的是VC++6.0。

可见值传递是传输了要传递的变量的一个副本,所以改变这个副本不会对调用函数造成影响,但是这个被调用函数一般有一个有用的返回值,也就是你用某个东西,在使用过程中,也许改变了它,但是时候后,你又保持原样给了人家。比如给你一个打好节的丝巾,你使用时换了另一种样式,照了像,还别人的时候,又按照人家的借你的样子还给人家,而这个照片就是需要得到的东西(类似返回值)。

而引用,就是将要传递的变量的地址传到了被调用函数中,如果在被调用函数中改变,那么就会在调用函数中改变。比如你借了人家布,如果你剪裁了不同的样式,那么还人家的样子就是你剪裁后的样子。一般c++可以使用值传递和引用传递,后者更多。因为这样不用另外在堆栈中开辟空间,而值传递就需要另外的开辟空间,对内存有一定的浪费。一般c中只使用值传递。

另外关于存储数据方面,一般是将局部变量,函数返回地址,函数参数放到堆栈中,而函数返回值一般放到寄存器中,为的是方便中断,如果有零时中断就可以直接从寄存器中处理,不用再进行压栈出栈操作。

/*******************************

指针做形参做局部变量以及内存分配

一级指针做形参:首先一定要明白形参和你传递参数的那个实参是两个不同的变量,即使同名也还依然不同。指针传递的是一个变量或者一个值的地址,但是它本身还是采用值传递的方式。即你不能使它指向另外一块地址,但是你可以改变它指向的空间里存的值。

二级指针做形参:二级指针也是传值,但是他指向的地址是个一维指针,所以可以改变二维指针指向的地址空间里的内容也就是要申请空间的一维指针,不能改变二维指针本身的值,即不能让他指向一个新的一维指针。所以二维指针传递的是一个一维指针。

具体看下面这个程序以及输出:

#include<stdio.h>
#include <malloc.h>
#include <string.h>

void GetMemory1(char *p)//
{
    //该函数的形参为字符指针,在函数内部修改形参并不能真正改变传入形参的实参值。
 //因此在主函数中执行完下面两句之后
 //char *str1 = NULL; 
 //GetMemory1(str1);
 //str1仍然为NULL,因此会报内存错误,没有输出结果。
    p = (char *)malloc(100);

    //要记得使用指针变量时,每次分配空间后要判断是否分配成功。而且在主函数中使用之后记得释放内存,并置空
    if (p == NULL)
    {
        printf("error memory");
    }
}
/*但是上面的函数参数变为*char *&p就可以在主函数中正常输出了。
指针做形参也是采用值传递的方式,也就是会把指针的值-地址传过去,所以可以改变这个地址里的内容,
但是你不能改变指针的值,也就是指向的地址。但是引用就是变量的别名,所以可以改变指针的值,
所以就可以在函数里申请空间*/

char *GetMemory2(void)
{
    char p[] = "hello world";
    return p;    //p[]数组为函数内部局部变量,在函数返回后,内存已经被释放了,
 //所以在主函数中调用该函数str2 = GetMemory2();输出的可能为乱码
}

void GetMemory3(char **p,int num)
{
    *p = (char *)malloc(num);

    //要记得使用指针变量时,每次分配空间后要判断是否分配成功。而且在主函数中使用之后记得释放内存,并置空
    if (*p == NULL)
    {
        printf("error memory");
    }
}
void main()
{
    int n=0;
    char *str1 = NULL;    
    char *str2 = NULL;
    char *str3 = NULL;

    //GetMemory1(str1);
 //strcpy(str1,"Hello world");
 //printf("%s\n",str1);

    str2 = GetMemory2();
    printf("%s\n",str2);//输出乱码

    GetMemory3(&str3,100);
    strcpy(str3,"hello world");
    printf("%s\n",str3);//输出hello world

    char *str4 = (char *)malloc(100);
    if (str4 == NULL)
    {
        printf("error memory");
    }
    else
    {
        strcpy(str4,"Hello");
        free(str4);
        str4 == NULL;//free后要置空,否则str可能变成“野指针”
    }
    if (str4 != NULL)
    {
        strcpy(str4,"world");
        printf("%s\n",str4);//输出world
    }
    scanf("%d",&n);
}

指针做局部变量:如果你申请了空间(用new等,赋值不算)又没有delete,那么这个空间在你程序运行结束之前不会释放,只要你知道这个空间的地址,就可以访问。这里的赋值不算是指,比如,你先定义一个数组,然后把数组名赋值指针。但是char *d = "ZET";这种形式相当于new了4个空间。

下面是中兴通讯2012校招笔试题目,问输出什么?

当时答错(狂汗),现在搞明白,在函数里写了注释:

#include <stdio.h>

//此函数中d也是个局部变量,函数执行完自动销毁,但是指针分配的空间不会被自动回收,除非程序员delete掉。
//所以这个可以正常输出。
char *a()
{
    char *d = "ZET";//这个初始化的一种形式,相当于分配了四个空间
    return d;
}

//但是第二个数组空间是系统维护的,函数执行完自动销毁 
char *b()
{
    char p[10] = {"3G平台"};
    return p;
}


//参数是值传递方式,改变形参的地址,传递的实参的地址确不会因此改变 
void c(int n,char *pName)
{
    char *a[4] = {"aaa","bbb","ccc","ddd"};
    pName = a[n];
}

void main()
{
    int n=0;
    char *pName = "DB";
    printf("%s\n",a());//输出ZET
    printf("%s\n",b());//随机输出乱码

    c(2,pName);
    printf("%s\n",pName);    //输出DB,因为char *pName = "DB";已经使得pName指向了DB,但c(2,pName);并不能改变pName指向的地址。
 //形象点说就是:我有一个箱子给你用,你可以在里面装东西,但是你不能把我的箱子换成另外一个给我。
 //在这里指的是不能函数调用不能使pName变成函数中的二维数组a。
    
    scanf("%d",&n);
}

指针做形参,指针做局部变量,数组做形参,数组做局部变量之类的容易迷糊,得不断学习...

 /***************************************************

如果函数的参数是一个指针,不要指望用该指针去申请动态内存。(详见《高质量C++编程7.4节》)
    
在C编译器原理上:编译器总是要为函数的每个参数制作临时副本,指针参数str的副本是 _str,编译器使 _str = str。如果函数体内的程序修改了_str的内容,就导致参数str的内容作相应的修改。这就是指针可以用作输出参数的原因。
即上面的函数代码经过编译后成为:
    char * fun(char *str)
    {
        char *_str;
        _str = str;
        _str = (char *) malloc(100);
    }

系统分配内存给_str指针,_str指针指向了系统分配的新地址,函数体内修改的只是_str的内容,对原str所指的地址的内容没有任何影响。因此,函数的参数是一个指针时,不要在函数体内部改变指针所指的地址,那样毫无作用,需要修改的只能是指针所指向的内容。即应当把指针当作常量。

如果非要使用函数指针来申请内存空间,那么需要使用指向指针的指针
    char * fun(char **str)
    {
        *str = (char *) malloc(100);
    }
还有另外的方案,通过函数返回值传递动态内存:
   char * fun()
    {
        char *str;
        str= (char *) malloc(100);

return str;
    }
这个倒还说得过去,因为函数返回的是一个地址的值,该地址就是申请的内存块首地址。

    此时,7.4节结束前还有一句话:不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡。

这里区分一下静态内存,栈内存和动态分配的内存(堆内存)的区别:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

在此,辨析一下  char p[] = "abcdefg";

char *p = "abcdefg";

用来初始化字符数组的字符串常量"abcdefg",编译器会在栈中为字符数组分配空间,然后把字符串中的所有字符复制到数组中;

而用来初始化字符指针的字符串常量"abcdefg"会被编译器安排到只读数据存储区,但也是按字符数组的形式来存储的,我们可以通过一个字符指针读取字符串常量但不能修改它,否则会发生运行时错误。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/16453.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Security如何防止会话固定攻击

在春季安全课程的这篇文章中&#xff0c;我们将研究春季安全会话固定以及如何防止春季应用程序中的会话劫持。 春季安全会话固定 会话固定是一种非常常见且最常见的攻击类型&#xff0c;恶意攻击者可以通过访问站点来创建会话&#xff0c;然后诱使其他用户使用相同的会话登录…

副业是刚需?分享几个程序员接外包私活的网站

经常看到某某程序员接了个项目开发&#xff0c;工作之余轻轻松松赚了钱还顺带提升了技术&#xff1b;或者看到某大佬又发表了一篇程序员技术提升稿件&#xff0c;阅读点赞收藏三连发&#xff0c;这个月的零花钱又不愁了...但自己只是一名普普通通的程序员&#xff0c;能找到这样…

Golang入门笔记(10)—— 闭包 closure

先看一段代码&#xff0c;脱离代码讲闭包&#xff0c;太干了。 package mainimport "fmt"func main() {a : Adder()fmt.Println(a(1))fmt.Println(a(2))fmt.Println(a(3)) }func Adder() func(int) int { // 累加器&#xff1a;这里从10开始累加var sum int 10retu…

linux时区相关

背景&#xff1a;用linux自带的时间接口函数读取时间的时候&#xff0c;发现有时候时间与北京时间不符合&#xff0c;经过研究发现&#xff1a;时间 UTC时间时区带来的偏移。操作方法&#xff1a;timedatectl list-timezones可看支持的时区改时区方法有如下两种&#xff1a; l…

【LeetCode 每日一题】15. 三数之和

01 题目描述 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元…

【数据结构】链表LinkedList

1.ArrayList的缺陷 2.单链表的实现 3.LinkedList的使用&#xff08;模拟实现&#xff09; 我们之前介绍过ArrayList了&#xff0c;它的底层是数组&#xff0c;数组是一段连续的空间&#xff0c;当我们想要插入或者删除数据的时候&#xff0c;插入元素&#xff0c;就要让插入位置…

用树莓派PICO做一个桌面时钟超详细教程!

用树莓派PICO做一个可显示时间和温湿度的桌面时钟一、概述二、材料准备1、树莓派PICO2、DHT11温湿度传感器3、DS1302时钟模块&#xff08;选用&#xff09;4、SSD1306屏幕5、其他材料三、开始1、连线2、写程序&#xff08;1&#xff09;使用内置RTC函数实现的时钟&#xff08;2…

2.11 教你一套怎么建立自己的选题素材库的方法【玩赚小红书】

一、自身定位延伸选题库 建立选题库&#xff0c;前提先确定自身定位&#xff0c;然后发散性思维延展。如我们做母婴博主&#xff0c;接下来就要想&#xff0c;母婴用户会关注什么&#xff0c;自然会想到到宝宝吃喝玩乐、妈妈保养、产后修复、婆媳关系等等内容。若我们只做宝宝…

这才是,真彩虹预染蛋白Markers

做WB的小伙伴都知道&#xff0c;现市面上各种“多彩”Marker的产品有很多&#xff0c;但是真正拿到手上的&#xff0c;可能是各种各样的&#xff08;见图1&#xff09;&#xff0c;咱也不清楚哪个是真的... 现在小编告诉你&#xff0c;经典的彩虹Marker长这样(见图2)&#xff1…

WebDAV之葫芦儿·派盘+读出通知

读出通知 支持webdav方式连接葫芦儿派盘。 手机各种推销通知太多,如何避免那些繁琐的通知内容,做出一键就能够阅读重要通知的最佳体验,帮助您更加快速和便捷的体验到那些应用内容?推荐大家使用读出通知。 读出通知APP可以设置接收通知的app,还可以用耳机操作,操作简单…

avalanche 少量tcp长连接持续构建HTTP请求测试

最近测试项目&#xff0c;测试要求使用少量tcp长连接连接&#xff0c;持续打HTTP请求&#xff0c;到测试结束。 分别用思博伦测试仪和supernova测试仪进行实现。 思博伦测试仪实现 测试仪基本运行流程&#xff1a;Loads配置任何形式bandwidth&#xff0c;connection&#xf…

SpringBoot项目本机和Linux环境部署

文章目录一. 本机环境下打包与运行二. Linux下部署SpringBoot项目2.1 Linux环境配置2.2 配置数据库2.3 运行程序一. 本机环境下打包与运行 项目进行打包 2. 本机环境下运行SpringBoot程序 控制台进入SpringBoot项目jar包所在的文件夹&#xff0c;运行下面指令即可 java -jar […

[附源码]java毕业设计企业招标系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

JDBC技术

JDBC 一、jdbc的概述 JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口&#xff08;一组API&#xff09;&#xff0c;定义了用来访问数据库的标准Java类库&#xff0c;&#xff08;java.sql,javax.sql&#xff09;使用…

第十一周周报

学习目标&#xff1a; DDPM 学习内容&#xff1a; DDPM代码 学习时间&#xff1a; 11.13-11.18 学习产出&#xff1a; 一、DDPM 1、trainer trainer用来计算损失&#xff0c;即将图片加噪后计算损失&#xff0c;损失公式如下&#xff1a; extract()函数&#xff1a;…

基于HASM模型的土壤高精度建模matlab仿真

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 目录 一、理论基础 二、核心程序 三、测试结果 一、理论基础 土壤有机碳库是陆地生态系统中最丰富的碳库&#xff0c;其动态变化和存储分布在土壤质量评估、农田生态管理和气候变化适应与减缓等领域起着至关…

Java实现图书管理系统

作者&#xff1a;~小明学编程 文章专栏&#xff1a;JavaSE基础 格言&#xff1a;目之所及皆为回忆&#xff0c;心之所想皆为过往 今天给大家带来的是用Java实现的图书管理系统。 目录 需求 图书类 创建图书类 创建书架 Operation IOperation接口 添加图书AddOperation…

easyrecovery15最新版数据恢复类软件测评

当下如今&#xff0c;利用笔记本进行学习和办公已经是毋庸置疑的了&#xff0c;所以会需要在电脑上保存大量的数据信息&#xff0c;但是电脑在带来方便的同时&#xff0c;也存在很多的隐患。万一数据丢失了&#xff0c;该怎么办呢&#xff1f;要解决数据丢失问题&#xff0c;就…

VUE3 中实现拖拽和缩放自定义看板 vue-grid-layout

Vue Grid Layout官方文档 Vue Grid Layout中文文档 1. npm下载拖拽缩放库 npm install vue-grid-layout3.0.0-beta1 --save 2. vue3 使用 vue-grid-layout报错&#xff1a;external_commonjs_vue_commonjs2_vue_root_Vue_default.a is not a constructor 解决方案: vue3版本…

力扣刷题(代码回忆录)——数组部分

数组 数组过于简单&#xff0c;但你该了解这些&#xff01;数组&#xff1a;二分查找数组&#xff1a;移除元素数组&#xff1a;序数组的平方数组&#xff1a;长度最小的子数组数组&#xff1a;螺旋矩阵II数组&#xff1a;总结篇704. 二分查找 给定一个 n 个元素有序的&#…