Rust语法:所有权引用生命周期
文章目录
- 所有权
- 垃圾回收管理内存
- 手动管理内存
- Rust的所有权
- 所有权转移
- 函数所有权传递
- 引用与借用
- 可变与不可变引用
- 生命周期
- 悬垂引用
- 函数生命周期声明
- 结构体的生命周期声明
- Rust生命周期的自行推断
- 生命周期约束
- 静态生命周期
所有权
垃圾回收管理内存
Python,Java这类语言在管理内存时引用了一种叫做垃圾回收的技术,这种技术会为每个变量设置一个引用计数器(reference counter),来统计每个对象的引用次数。
一旦某个对象的引用数为0,垃圾回收器就会择取一个时机将其所占用的空间回收。
以Python为例子
x = [1, 2] # 列表对象[1, 2]被x引用,引用计数为1
y = x # 列表对象[1, 2]被y引用,引用计数为2
del x # 列表对象[1, 2]的引用x被删除,此时并不删除对象,引用计数为1
del y # 列表对象[1, 2]的引用y被删除,此时并不立即删除对象,引用计数为0
# 但此对象将会在下一次垃圾回收时被清理
这种垃圾回收的方式很好的避免了开发者去管理内存,但是也造成了一些列问题。也就是:
- 这种垃圾回收不会立刻进行,而是会选取某个时机进行,这无疑在这段时间内这段内存会被一直占用。
- 垃圾回收对性能的消耗比较大,因为编译器/解释器需要不停的追踪每个对象的引用计数来决定某个变量是否会被回收。
手动管理内存
C/C++就采取了手动管理内存的方式,也就是自己申请内存,自己需要去释放,如果不释放就可能导致内存占用的堆积,这种方式的好处在于可控性和高性能,坏处在于很容易出现各种错误。例如二次释放。
void test(int arr[]){free(arr); //此时该数组已经被释放
}int main(){int *arr = (int *)malloc(sizeof(int) * 10);test(arr);free(arr); //这里又进行了第二次释放return 0;
}
上述代码可能你觉着不会写的这么蠢,但是如果当代码逻辑复杂起来,你很难保证你不会犯这样的错误。
二次释放的危害很大,因为当一块内存被释放时可能会被新的对象再次占用,而地二次释放则会破坏新的对象的存储从而造成一系列严重的错误。
Rust的所有权
Rust采取了一种非传统的做法,即采用了一种叫做所有权的机制,其规则如下:
- Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
- 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
- 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)
来看下面的例子
fn main() {let x = 4; //x的作用域开始位置{//y的作用域开始位置let y = 10;}//y的作用域结束位置,作用域结束,y被丢弃
}//x的作用域结束位置,作用域结束,x被丢弃
此时就需要提一下堆和栈这两种内存了,其主要的区别如下:
栈是一种后进先出的内存,其内部高度组织化,并且栈内存的数据需要实现知道其大小,堆栈内存的存取速度很快
与栈内存不同,堆可以存储结果(指不能在编译时就确定大小)未知大小的数据,在存取时速度比较慢,OS会寻找一块可以存的下的内存然后分配给对应的进程。对于这块堆内存来说,会有一个指针指向这块,而这个指针是要存在栈中的。
| 堆 | 栈 | |
|---|---|---|
| 存取速度 | 慢 | 快 |
| 分配内存大小 | 可不固定 | 必须固定 |
在Rust中,有以下类型都是存储在栈内存上的,这些类型都实现了一个叫做copy的trait

(图片来自于b站杨旭)
由于这些类型都是存在栈上的,而栈的存储速度远高于堆,所以对于Rust来说,上述的数据在赋值传递时往往会直接进行拷贝,而不需要引用之类的技术。
fn main(){let x = 10;let y = x; //此时y有拷贝了一份10
}
所有权转移
由于我们知道上述的类型都是存在栈的,所以体现不出所有权,我们使用另一种存在堆上的类型String来演示所有权。
fn main(){let s = String::from("hello"); //从字符串字面值得到一个String类型
}
这里需要区分一下String和字符串字面值的区别,这里的String是一个存在堆内存的变量,其大小是可以扩充变化的。而字符串字面值(如let x = “hello”;)是固定的,在编译时就已经确定的了。
来看下面的代码:
fn main(){let s1 = String::from("hello"); //从字符串字面值得到一个String类型let s2 = s1;println!("{}", s); //会报错
}
上述代码报错了,我们来看报错信息:
error[E0382]: borrow of moved value: `s1`--> src\main.rs:4:17|
2 | let s1 = String::from("hello"); //从字符串字面值得到一个String类型| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;| - value moved here
4 | println!("{}", s1); //会报错| ^ value borrowed here after move|= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
报错的内容说,我们借用了一个已经移动了的值s。
还记着开头所说的所有权规则么:
- Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
- 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
- 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)
String::from(“hello”);这个值最开始被s所拥有,但是当让s2 = s1时,这个值就不在归s所有了,而是发生了move(移动),所有权交给了s2。
这是因为有第二条: 一个值只能同时被一个变量所拥有的缘故,所以s1不再拥有String::from(“hello”);,自然也就无法再被使用了。
我们从内存上来看是这样的(图片来自于b站杨旭),刚开始s1是一个指针指向了hello这块堆内存

后来由于s2=s1,所以指针得到了复制,得到了如下的场景

但由于一个值只能有一个所有者,所以先前的s1指针将被视为无效,于是得到了如下的场景。

所以最终看起来就好像s1的所有权移动到了s2一样,发生了所有权(对hello这个值的拥有)的移动。
这么做有一个很大的好处,那就是由于一个值在同一时刻只有一个所有者,那么在离开作用域时,对应的值就不会被反复释放
fn main(){let s1 = String::from("hello"); //s1作用域开始let s2 = s1;//s2作用域开始,s1失去所有权
}//s1,s2作用域结束,由于s1没有所有权,所以只有s2会释放这块堆内存。
这样就有效的避免了二次释放的的问题,但是同时又给开发者引入了很多新的难题。
函数所有权传递
但一个变量被当做参数传递给函数时,其所有权将会被移交到函数内部的形式参数上。看如下例子:
fn get_len(s: String) -> usize{ //此时,这里的形式参数s得到了外部s1的所有权return s.len(); //返回s的长度
} //s的作用域结束,而s又具有hello的所有权,所以hello会被释放fn main(){let s1 = String::from("hello"); //从字符串字面值得到一个String类型let len = get_len(s1); //把s1交给函数get_lenprintln!("{}", s1); //由于此时所有权已经交给了函数内部的形式参数s,所以s1没有所有权,导致报错
}
上面的注释已经说名了具体状况,其发生错误的原因就在于传递参数时会发生所有权的转移,而转移后有没有发生归还,而导致使用了没有所有权的变量。
解决方法1:
使用shadowing这个特性,将使用权再返回回去。
fn get_len(s: String) -> (usize, String){ //返回长度和字符串return (s.len(), s);
}fn main(){let s = String::from("hello"); //从字符串字面值得到一个String类型let (s, len) = get_len(s); //用Tuple拆包和shadowing的特性归还使用权println!("{}'s len is {}", s, len);
}
解决方法2:
参看下面的引用部分
如果你不想这么麻烦,你想直接复制一份hello传过去,而不是吧使用权交过去,你可以使用clone方法,这个方法会把堆上的数据克隆一份,然后给传给函数。
fn get_len(s: String) -> usize{ //返回长度和字符串return s.len();
}fn main(){let s = String::from("hello"); //从字符串字面值得到一个String类型let len = get_len(s.clone()); //把s克隆一份,此时新克隆的部分的使用权将交给形式参数sprintln!("{}'s len is {}", s, len);
}
注意,克隆意味着你要在堆上复制一份数据,这是相当耗时的。但好处在于,你可以不用为所有权而烦恼。
引用与借用
每次想要传一个值都会进行所有权转移显然十分的头疼,于是就有了借用,借用实际上就是一种引用,但是它的特殊之处在于他不会拿到所有权。 借用使用&操作符来表示。例子如下:
fn get_len(s: &String) -> usize{ //s是一个借用,而不是转交所有权return s.len();
}fn main(){let s1 = String::from("hello"); //从字符串字面值得到一个String类型let len = get_len(&s1); //传入s1的引用println!("{}'s len is {}", s1, len);
}
借用在内存上看是这个样子的:

可以看到引用s只是指向了s1,并不是指向了堆内存所在的位置。所以借用可以看做是变量的引用。因为有这个特性,所以借用并没有拿到了s1的所有权,只是暂时的借了过来。
所以在get_len函数结束之后,由于s只是借用所以无权释放hello。所有权还在s1手中。
可变与不可变引用
上述的借用很好的解决了传参的问题,但是还没完。有时我们希望修改一下对应的值,如果你直接修改就会发现报错这是因为你没加mut关键字。
fn get_len(s: &mut String) -> usize{ //可变字符串引用return s.len();
}fn main(){let mut s = String::from("hello"); //从字符串字面值得到一个String类型let len = get_len(&mut s);//传入可变字符串引用println!("{}'s len is {}", s, len);
}
问题似乎解决了,似乎皆大欢喜,但是,更大的问题随之出现了。我们来看下面的例子
fn main(){let mut s: String = String::from("hello");let mut re1: &String = &mut s;let mut re2: &String = &mut s;//此处报错println!("{} {}", re1, re2);
}
报错的理由很简单,同一作用域内不允许出现两个同一变量的可变引用。Rust这么做是为了防止数据竞争的发生。数据竞争会由一下行为引发:
1. 两个或更多的指针同时访问同一数据
2. 至少有一个指针被用来写入数据
3. 没有同步数据访问的机制
(参考来源: https://course.rs/)
同时为了保证不可变引用不会因为可变引用的修改而发生异常,于是规定,在同一作用域内不可变引用和可变引用不能同时出现。
fn main(){let mut s: String = String::from("hello");let mut re1: &String = &s;let mut re2: &String = &mut s; //不能同时出现println!("{} {}", re1, re2);
}
但是在同一作用域内,可以同时出现多个不可变引用。
如果实在需要用到多个可变引用,则可以通过大括号来创建新的作用域。
fn main(){let mut s: String = String::from("hello");{let mut re1: &String = &s;}let mut re2: &String = &mut s;println!("{}", re2); //此时已经无法调用re1,因为它的作用域在大括号内,在此处已经失效
}
生命周期
对于Rust来说每个变量都有一个自己的生命周期,也就是说,每个变量有一个有效地范围。
如果一个变量已经失效,而仍然使用他的引用,就会造成悬垂引用。而Rust的生命周期就是为了避免悬垂引用这个错误而设计的。
悬垂引用
来看下面的代码
fn main() {let result;{let tmp = String::from("abc");result = &tmp;}println!("{}", result);
}
上述代码会报错,这是因为result的生命周期在整个main内,但是tmp只是内部的一个局部变量,在print时,tmp已经失效可以认为是空的。
但是此时result仍然持有并想使用tmp的引用,因此引发了报错。来看一下报错内容:
error[E0597]: `tmp` does not live long enough--> src\main.rs:6:18|
5 | let tmp = String::from("abc");| --- binding `tmp` declared here
6 | result = &tmp;| ^^^^ borrowed value does not live long enough
7 | }| - `tmp` dropped here while still borrowed
8 | println!("{}", result);| ------ borrow later used hereFor more information about this error, try `rustc --explain E0597`.
内容说的很直白,说的是result借用了一个获得还没自己长的变量。
我们来看看两个变量的生命周期(就是存活时间)
fn main() {let result;-------------------------------+{ |<-result的生命周期let tmp = String::from("abc");-----+ |result = &tmp; tmp的生命周期-> | |}--------------------------------------+ |println!("{}", result); |
}---------------------------------------------+
从上图可以直观的看到,result的存活范围(生命周期)大于tmp的生命周期。一个大的生命周期的变量借用了一个小的,所以才会导致了错误的发生。
我们只需要修改上述代码就可以使其正确
fn main() {let result;let tmp = String::from("abc");result = &tmp;println!("{}", result);
}
此时在调用print时,tmp的生命周期还没有结束,但是此时你也能发现result的生命周期其实还是大于tmp的,也就意味着
大周期借用小周期不一定会出错。但是在很多情况下,Rust还是认为这种情况存在风险,会在编译阶段就拒绝我们。
但是小周期借用大周期确实一定不会出错的(因为此时小周期会率先失效,而不会借用到一个失效的大周期对象)
函数生命周期声明
先来看一个函数,有一个需求,要求返回两个字符串中最长的那一个的借用。此时你会想这么写代码
fn get_greater(x: &str, y: &str) -> &str {if x.len() > y.len() {x} else {y}
}
此时你觉着则个代码写得没问题,但是你却发现编译器报错了。你会发现错误如下:
error[E0106]: missing lifetime specifier--> src\main.rs:9:37|
9 | fn get_greater(x: &str, y: &str) -> &str {| ---- ---- ^ expected named lifetime parameter|= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
什么意思呢,也就是说Rust期望你手动添加一个生命周期的声明。
在Rust眼里,这个代码会返回一个字符串的引用&str。而这个引用可能来自x或者y,或者是其他的地方。
这取决于你如何写函数内部的实现,此时的Rust编译器犯迷糊了因为他不知道&str的生命周期大概是多长。
是和x一样长?还是和y一样长?还是什么?如果是和x一样长,且x的生命周期大于y的,那么返回一个y的引用就可能会出现悬空指针。
看下面例子:
fn main() {let result;let s1 = String::from("abc"); //s1生命周期大{let s2 = String::from("abcd");//s2生命周期小result = get_greater(s1.as_str(), s2.as_str()); //把小的引用赋给了大的}println!("{}", result); //此时s2已经销毁,result指针悬空
}fn get_greater(x: &str, y: &str) -> &str {if x.len() > y.len() {x} else {y //此时返回了小的那一个}
}
上面的代码就产生了这种不安全的行为,归其原因在于我们可能会返回一个小的生命周期的借用给一个大的生命周期的变量。
而此时由于Rust并不清楚你的函数干了什么操作,所以推断不出返回值的借用的生命周期到底是大的还是小的。
所以此时Rust要求你对传入的参数的引用的生命周期加以限定,以保证返回值的生命周期是可以被Rust编译器推断的(没错,生命周期标注就是为了告诉Rust编译器你的返回值的生命周期是多大,从而让Rust能够检查出潜在的悬空指针错误)
生命周期标注符号使用’作为开头,例如
'a
'b
'abc
他么需要放在函数名后的尖括号里,表明这是一个生命周期标注变量
fn get_greater<'a>(x: &str, y: &str) -> &str {if x.len() > y.len() {x} else {y }
}
然后你就需要为后续的引用标注上生命周期。其写法是在&后面加上生命周期标注变量再加类型
fn get_greater<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y }
}
我们来看上面的代码,定义了一个生命周期标注变量’a, 其中x生命周期的大小是’a, y也是。返回值也是。也就是说x,y,返回值有同样的生命周期。
你可能会疑惑这是啥意思,x和y显然可能不同。此时你可以认为‘a的大小就是x和y中最小的那一个(显然让一个小的强行拥有大的是错误的)。
所以上述代码的标注在告诉编译器两件事:
- 对函数内部,返回的引用必须要和x和y的生命周期相同,否则函数内部的实现有问题。
- 对于函数外部,返回的值是x和y生命周期中最小的那一个,所以在外部进行检查时,如果将这个小的赋给了大的,那么编译器可以直接预判报错。
看完上述的两点,你会发现加了生命周期并不改变程序的一分一毫,只是给编译器指明了一条检查你错误的道路。
我们再来看下面代码加深对这句话的理解:
fn main() {let result;let s1 = String::from("abc");{let s2 = String::from("abcd");result = get_greater(s1.as_str(), s2.as_str());}println!("{}", result);
}fn get_greater<'a>(x: &'a str, y: &str) -> &'a str {if x.len() > y.len() {x} else {y //此处会报错}
}
上述代码只给x和返回值加上了生命周期的限制,也就是说返回值和x有一样的生命周期。
但是此时Rust编译器检查你的代码,发现了你逻辑上的漏洞,因为你返回了y,而y的生命周期并不是’a。所以此时在y处报错
fn main() {let result;let s1 = String::from("abc");{let s2 = String::from("abcd");result = get_greater(s1.as_str(), s2.as_str()); //在此处报错}println!("{}", result);
}fn get_greater<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y}
}
此时我们对函数加以修正,Rust编译器会发现,此时函数内部返回值都有‘a的生命周期。所以函数内部的逻辑是正确的。
那么此时Rust编译器就会开始对外对调用者进行检查了,由于函数指明了x和y有相同的生命周期。
但是其中一个生命周期过小(s2的过小),导致’a就是过小的这个生命周期('a就是s2)的生命周期。此时编译器检查了一遍后就发现(编译器只看函数名就知道返回值的生命周期了,不需要知道函数内部的实现细节,这就是为啥要标注生命周期,就是给编译器看的),返回值的生命周期小于它要赋给的变量的生命周期。
也就是开头的大周期变量借用小周期变量。所以此时果断报错。
综上可以看出一件事,生命周期的标注就是为了让Rust能够在编译时,对函数内检查你的逻辑上的错误,对外检查调用时是否会发生错误。所以这个标注只起到检查作用(伺候好编译器老爷 )
结构体的生命周期声明
在结构体和枚举中也有可能出现引用,此时我们就需要给每个引用标注一下生命周期了。
如下:
struct Test<'a>{name: &'a String;
}
此时这个标注的含义就是,name这个生命周期至少要比结构体活得还要长。我们可以举一个反例,如下:
struct Test<'a>{name: &'a String
}fn main() {let test;{let name = String::from("abc");test = Test{name: &name //此处会报错,说name生命周期太短};}println!("{}",test.name);
}
此时Rust检查后发现了,name还没test活得长,所以果断报错。
主义在实现时实使用impl实现的时候也需要标注上生命周期,因为这个相当于是结构体名的一部分。
impl<'a> Test<'a>{fn print_hello(&self) -> (){}
}
Rust生命周期的自行推断
在某些情况下Rust可以自己推断出返回值的生命周期,主要按照如下的规则:

我们来看例子1:
fn test(s: &String) -> &String{}
上面的代码就没有报错,这是因为Rust已经推断出了返回值的生命周期。
- 根据第一条规则,每个引用类型的参数都有自己的生命周期,于是s有一个生命周期
- 根据第二条规则,只有一个输入,于是这个输入的生命周期将会给于返回值
于是Rust推断出了返回值引用的生命周期,这是因为返回值的生命周期只可能来自于输入,因为函数内部的创建的对象,在返回引用时会造成悬垂引用(因为函数一结束,被引用的对象就失效了)。
再来看另一种情况
impl<'a> Test<'a>{fn print_hello(&self, word: &String) -> &String{word}
}
上述情况也不会报错,理由如下:
- 根据第一条规则,每个引用类型的参数都有自己的生命周期,于是self和word有自己的生命周期
- 根据第三条原则,self的生命周期会被赋给word,和返回值,此时所有的参数的生命周期都已经推断出。
生命周期约束
对于生命周期标注来说,我们可以标注任意多的类别例如下面的这个式子:
fn test<'a, 'b>(s1: &'a String, s2: &'b String) -> &String{s1
}
当然,上述的代码是错误的,因为编译器无法推断出返回值的生命周期。
我们稍作修改后就得到了这个:
fn test<'a, 'b>(s1: &'a String, s2: &'b String) -> &'a String{s1
}
此时我们就得到了这个式子,就不报错了,如果我们突发奇想换一个式子呢?我们返回s2会如何
fn test<'a, 'b>(s1: &'a String, s2: &'b String) -> &'a String{s2
}
此时编译器又报错了,因为编译器不知道s1和s2到底啥关系,返回的应该是’a的周期,但是实际返回的是’b的周期。
此时我们可以通过对生命周期的关系增加约束来达到解决问题的目的:
fn test<'a, 'b>(s1: &'a String, s2: &'b String) -> &'a Stringwhere 'b: 'a //'b生命周期大于'a
{s2
}
此时又不报错了,因为此时我们返回的是’b周期,而他比’a是要更大的。所以这个返回不会造成悬垂引用(因为返回的比’a要大,'a会造成的悬垂引用,'b不一定会造成)
静态生命周期
对于一些变量它的生命周期可能是整个程序的运行期间,于是可以使用’static这个特殊的标注来声明一个整个程序期间的生命周期。
fn test(s: &'static str){}
其中,常见的字符串字面值&str采用的就是’static类型的生命周期
相关文章:
Rust语法:所有权引用生命周期
文章目录 所有权垃圾回收管理内存手动管理内存Rust的所有权所有权转移函数所有权传递 引用与借用可变与不可变引用 生命周期悬垂引用函数生命周期声明结构体的生命周期声明Rust生命周期的自行推断生命周期约束静态生命周期 所有权 垃圾回收管理内存 Python,Java这…...
办手机卡/流量卡需要问清楚啥?
网上的手机卡一搜能出现千千万,那么怎么才能避免购买到那些套路卡呢?今天就给大家分享一下,办理手机卡时需要问清楚什么? 办理流量卡需要咨询的五大问题,下面开始进入正题。 1、是否是正规号卡?正规的号…...
vim基本使用方法
VIM 1.vim介绍2.vim基本操作2.1 模式切换2.2 命令模式2.3 底行模式 1.vim介绍 vim是linux上一个有多个编辑模式的编辑器。 这里主要介绍三种模式: 命令模式(Normal mode) 执行命令的模式,主要任务就是控制光标移动、复制和删除。…...
漏洞指北-VulFocus靶场专栏-入门
漏洞指北-VulFocus靶场01-入门 VulFocus靶场前置条件:入门001 命令执行漏洞step1: 输入默认index的提示step2: 入门002 目录浏览漏洞step1:进入默认页面,找到tmp目录step2 进入tmp目录获取flag文件 VulFocus靶场前置条…...
管理类联考——逻辑——真题篇——按知识分类——汇总篇——二、论证逻辑——推论——第二节——数字推理题
文章目录 第二节 数字推理题真题(2017-31)——推论——数字推理题——数量比例模型真题(2014-33)——推论——数字推理题——数量比例模型——(1)若题干既有数量,也有比例,答案一般为数量。(2)若题干只有比例没有数量,答案一般为比例。真题(2018-44)——推论——数…...
git基础教程(24) git reflog查看引用日志
文章目录 1、`git reflog`命令说明2、`git reflog`命令显示内容3、具体的用法4、引起ref变化的操作有git reflog 命令是用来恢复本地错误操作很重要的一个命令,所以在这里对它进行一下整理。 1、git reflog命令说明 reflog翻译:Reference logs(参考日志) git reflog命令:…...
成都爱尔谭娇主任提醒孩子不停揉眼睛是因为什么
孩子总是揉眼睛, 明显眼睛不舒服, 但看着好像没什么? 可孩子不停眨眼流泪, 肯定不对…… 孩子到底怎么了? 孩子可能长了“倒睫”! 孩子出现倒睫毛就是睫毛不朝外长而向内长,是婴幼儿很容易患的一种眼病。 由于孩子的脸颊及鼻梁发…...
医疗设备管理软件哪家好?医院设备全生命周期管理要怎么做?
随着医学技术的不断进步,医疗设备变得越来越先进,越来越复杂。因此,医疗设备的管理也变得越来越重要。传统的医疗设备管理方式存在很多问题,比如设备数据难统计、报修方式难统一、巡检维保难规范等。为了解决这些问题,…...
基于PaddlePaddle实现的声纹识别系统
前言 本项目使用了EcapaTdnn、ResNetSE、ERes2Net、CAM等多种先进的声纹识别模型,不排除以后会支持更多模型,同时本项目也支持了MelSpectrogram、Spectrogram、MFCC、Fbank等多种数据预处理方法,使用了ArcFace Loss,ArcFace loss…...
使用GDB工具分析core文件的方法
引言: 在软件开发过程中,我们经常会遇到程序崩溃或异常退出的情况。这时,一个非常有用的工具就是GDB(GNU调试器),它可以帮助我们分析core文件并找出导致程序崩溃的原因。本文将介绍如何使用GDB工具来分析c…...
Maven - 统一构建规范:Maven 插件管理最佳实践
文章目录 Available Plugins开源项目中的使用插件介绍maven-jar-pluginmaven-assembly-pluginmaven-shade-pluginShade 插件 - 标签artifactSetrelocationsfilters 完整配置 Available Plugins https://maven.apache.org/plugins/index.html Maven 是一个开源的软件构建工具&…...
对接海康明眸门禁设备-删除人员信息
对接海康明眸门禁设备-删除人员信息 文中登录 退出登录 长连接和海康hCNetSDK等接口 见文章 初始SDK和登录 /*** 删除人脸 IotCommDataResult 自定义类 收集结果*/Overridepublic List<IotCommDataResult> deleteFace(IotCameraParam camera, Collection<Long> us…...
LEADTOOLS Imaging SDK Crack
LEADTOOLS Imaging SDK Crack 高级开发人员工具包包括ActiveX和WPF/XAML控件。 LEADTOOLS Imaging SDK为文件格式导入/导出、图像压缩、图像显示和效果、颜色转换、图像处理、TWAIN扫描、图像通用对话框、数据库集成、打印和互联网提供了基本和高级的彩色图像功能。 LEADTOOLS …...
2023并发之八股文——面试题
基础知识 并发编程的优缺点为什么要使用并发编程(并发编程的优点) 充分利用多核CPU的计算能力:通过并发编程的形式可以将多核CPU 的计算能力发挥到极致,性能得到提升方便进行业务拆分,提升系统并发能力和性能&#x…...
操作记录日志保存设计实现
定义一个切面类 @Aspect @Slf4j @Component @RequiredArgsConstructor public class OperateLogAopConfig {private final ISysOperateLogService sysOperateLogService;@Around("@annotation(operateLog)")public Object operateLog(ProceedingJoinPoint point, Op…...
PL 侧驱动和fpga 重加载的方法
可以解决很多的问题 时钟稳定后加载特定fpga ip (要不内核崩的一塌糊涂)fpga 稳定复位软件决定fpga ip 加载的时序 dluash load /usr/local/scripts/si5512_setup.lua usleep 30 mkdir -p /lib/firmware cp -rf /usr/local/firmare/{*.bit.bin,*.dtbo} …...
【2023最新爬虫】用python爬取知乎任意问题下的全部回答
老规矩,先上结果: 爬取了前200多页,每页5条数据,共1000多条回答。(程序设置的自动判断结束页,我是手动break的) 共爬到13个字段,包含: 问题id,页码,答主昵称,答主性别,…...
Bingchat和ChatGPT主要区别
Bing Chat由chatgpt GPT-4技术提供支持,这是流行的ChatGPT的最新语言模型。Bing Chat通过更具交互性和上下文联动的响应来优化搜索引擎。它允许用户提出问题并获得更人性化、精确化或创造力的答案。用户还可以在答案末尾查看的参考来源。该工具可以充当个人研究、计…...
Docker容器:docker镜像的创建及dockerfile
Docker容器:docker镜像的创建及dockerfile案例 一.docker镜像的三种创建方法 创建镜像有三种方法:基于现有镜像创建、基于本地模板创建及基于dockerfile创建 1.基于现有镜像创建 1.1 启动镜像 #首先启动一个镜像,在容器里做修改 docker …...
Vue3 父子组件数据传递
1、父组件向子组件传递数据 1.1、传递多个简单变量给到子组件 父组件使用 <TitleView title"标题" :name"name" :isCollect"isCollect" collect-event"collectEvent" /><script setup>const name ref(名字)const isCol…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...
JS红宝书笔记 - 3.3 变量
要定义变量,可以使用var操作符,后跟变量名 ES实现变量初始化,因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符,可以创建一个全局变量 如果需要定义…...
前端工具库lodash与lodash-es区别详解
lodash 和 lodash-es 是同一工具库的两个不同版本,核心功能完全一致,主要区别在于模块化格式和优化方式,适合不同的开发环境。以下是详细对比: 1. 模块化格式 lodash 使用 CommonJS 模块格式(require/module.exports&a…...
深入理解 React 样式方案
React 的样式方案较多,在应用开发初期,开发者需要根据项目业务具体情况选择对应样式方案。React 样式方案主要有: 1. 内联样式 2. module css 3. css in js 4. tailwind css 这些方案中,均有各自的优势和缺点。 1. 方案优劣势 1. 内联样式: 简单直观,适合动态样式和…...
