设计网站大全湖南岚鸿设计/推广计划怎么做推广是什么
第四章认识所有权
所有权(系统)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全。因此,理解 Rust 中所有权如何工作是十分重要的。
4.1 所有权
所有运行的程序都必须管理其使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。
所有权规则
- Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。
- 值有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
字符串字面值不可变,这里以String为例
fn main() {println!("Hello, world!");// String在堆上// from 函数基于字符串字面值来创建 Stringlet mut str = String::from("hello");str.push_str(", world"); // push_str() 在字符串后追加字面值println!("{}", str);
}
结果
对于 String
类型,为了支持一个可变,可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容。这意味着:
- 必须在运行时向操作系统请求内存。
- 需要一个当我们处理完
String
时将内存返回给操作系统的方法。
第一部分由我们完成:当调用 String::from
时,它的实现 (implementation) 请求其所需的内存。这在编程语言中是非常通用的。
然而,第二部分实现起来就各有区别了。在有 垃圾回收(garbage collector,GC)的语言中, GC 记录并清除不再使用的内存,而我们并不需要关心它。没有 GC 的话,识别出不再使用的内存并调用代码显式释放就是我们的责任了,跟请求内存的时候一样。从历史的角度上说正确处理内存回收曾经是一个困难的编程问题。如果忘记回收了会浪费内存。如果过早回收了,将会出现无效变量。如果重复回收,这也是个 bug。我们需要精确的为一个
allocate
配对一个free
。
Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。
fn main() {{let s = String::from("hello"); // 从此处起,s 是有效的// 使用 s} // 此作用域已结束,// s 不再有效
}
注意:在 C++ 中,这种 item 在生命周期结束时释放资源的模式有时被称作 资源获取即初始化(Resource Acquisition Is Initialization (RAII))。如果你使用过 RAII 模式的话应该对 Rust 的
drop
函数并不陌生。
变量与数据交互的方式(一):移动
fn main() {let x = 5;let y = x;
}
基本变量赋值,x和y都在栈中,且值为5。
那么String类型呢?
fn main() {let s1 = String::from("hello");let s2 = s1;
}
String
由三部分组成,如图左侧所示:一个指向存放字符串内容内存的指针,一个长度,和一个容量。这一组数据存储在栈上。右侧则是堆上存放内容的内存部分。
长度表示 String
的内容当前使用了多少字节的内存。容量是 String
从操作系统总共获取了多少字节的内存。长度与容量的区别是很重要的,不过在当前上下文中并不重要,所以现在可以忽略容量。
当将 s1
赋值给 s2
,String
的数据被复制了,这意味着我们从栈上拷贝了它的指针、长度和容量。我们并没有复制指针指向的堆上数据。
像C++中的浅拷贝
如果 Rust 也拷贝了堆上的数据,那么内存看起来就是这样的。如果 Rust 这么做了,那么操作 s2 = s1
在堆上数据比较大的时候会对运行时性能造成非常大的影响。
(浅拷贝)这就有了一个问题:当 s2
和 s1
离开作用域,他们都会尝试释放相同的内存。这是一个叫做 二次释放(double free)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
为了确保内存安全,这种场景下 Rust 的处理有另一个细节值得注意。与其尝试拷贝被分配的内存,Rust 则认为 s1
不再有效,因此 Rust 不需要在 s1
离开作用域后清理任何东西。看看在 s2
被创建之后尝试使用 s1
会发生什么;这段代码不能运行:
fn main() {let s1 = String::from("hello");let s2 = s1;println!("{}, world!", s1);}
结果:
如果你在其他语言中听说过术语 浅拷贝(shallow copy)和 深拷贝(deep copy),那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效了,这个操作被称为 移动(move),而不是浅拷贝。上面的例子可以解读为 s1
被 移动 到了 s2
中。那么具体发生了什么,如图
这样就解决了之前的问题!因为只有 s2
是有效的,当其离开作用域,它就释放自己的内存。
另外,这里还隐含了一个设计选择:Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何 自动 的复制可以被认为对运行时性能影响较小。
变量与数据交互的方式(二):克隆
如果 确实 需要深度复制 String
中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone
的通用函数。
fn main() {let s1 = String::from("hello");let s2 = s1.clone();println!("{}, world!", s1);println!("{}, world!", s2);
}
这段代码的实际结果就是如下图
只在栈上的数据:拷贝
但这段代码似乎与我们刚刚学到的内容相矛盾:没有调用 clone
,不过 x
依然有效且没有被移动到 y
中。
fn main() {let x = 5;let y = x;println!("x = {}, y = {}", x, y);
}
原因是像整型这样的在编译时已知大小的类型被整个存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 y
后使 x
无效。换句话说,这里没有深浅拷贝的区别,所以这里调用 clone
并不会与通常的浅拷贝有什么不同,可以不用管它。
所有权与函数
将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制,就像赋值语句一样。
简单的案例,见注释
fn main() {let s = String::from("hello"); // s 进入作用域takes_ownership(s); // s 的值移动到函数里 ...// ... 所以到这里不再有效// println!("{}", s); // 报错let x = 5; // x 进入作用域makes_copy(x); // x 应该移动函数里,// 但 i32 是 Copy 的,所以在后面可继续使用 x} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,// 所以不会有特殊操作fn takes_ownership(some_string: String) { // some_string 进入作用域println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放fn makes_copy(some_integer: i32) { // some_integer 进入作用域println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作
复杂一点的
fn main() {let s = String::from("hello"); // s 进入作用域takes_ownership(s); // s 的值移动到函数里 ...// ... 所以到这里不再有效// println!("{}", s); // 报错
}
fn takes_ownership(some_string: String) { // some_string 进入作用域let str = String::from(some_string);println!("{}", some_string); // 报错
}
返回值与作用域
返回值也可以转移所有权
见注释
fn main() {let s1 = gives_ownership(); // gives_ownership 将返回值// 移给 s1let s2 = String::from("hello"); // s2 进入作用域let s3 = takes_and_gives_back(s2); // s2 被移动到// takes_and_gives_back 中, // 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,// 所以什么也不会发生。s1 移出作用域并被丢弃fn gives_ownership() -> String { // gives_ownership 将返回值移动给// 调用它的函数let some_string = String::from("hello"); // some_string 进入作用域.some_string // 返回 some_string 并移出给调用的函数
}// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域a_string // 返回 a_string 并移出给调用的函数
}
变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop
被清理掉,除非数据被移动为另一个变量所有。
在每一个函数中都获取所有权并接着返回所有权有些啰嗦。如果想要函数使用一个值但不获取所有权该怎么办呢?如果还要接着使用它的话,每次都传进去再返回来就有点烦人了,除此之外,也可能想返回函数体中产生的一些数据。可以使用元组来返回多个值
fn main() {let s1 = String::from("hello");let (s2, len) = calculate_length(s1);println!("The length of '{}' is {}.", s2, len);
}fn calculate_length(s: String) -> (String, usize) {let length = s.len(); // len() 返回字符串的长度(s, length)
}
好
返回参数的所有权
见引用部分
4.4 引用与借用
下面是如何定义并使用一个calculate_length
函数,它以一个对象的引用作为参数而不是获取值的所有权:
fn main() {let s1 = String::from("hello");let len = calculate_length(&s1);println!("The length of '{}' is {}.", s1, len);
}fn calculate_length(s: &String) -> usize {s.len()
}
& 符号就是 引用,它允许你使用值但不获取其所有权。
注意:与使用
&
引用相反的操作是 解引用(dereferencing),它使用解引用运算符,*
。
变量 s
有效的作用域与函数参数的作用域一样,不过当引用离开作用域后并不丢弃它指向的数据,因为我们没有所有权。
将获取引用作为函数参数称为 借用(borrowing)。
正如变量默认是不可变的,引用也一样(默认)不允许修改引用的值。
fn main() {let s = String::from("hello");change(&s);
}fn change(some_string: &String) {some_string.push_str(", world");
}
结果
可变引用
fn main() {// s也必须是mut的let mut s = String::from("hello");change(&mut s);
}// 注意形参的形式
fn change(some_string: &mut String) {some_string.push_str(", world");
}
不过可变引用有一个很大的限制:在特定作用域中的特定数据有且只有一个可变引用。
fn main() {let mut s = String::from("hello");let r1 = &mut s;let r2 = &mut s; // 错误println!("{}, {}", r1, r2);}
结果
这个限制允许可变性,不过是以一种受限制的方式允许。
也就是说r1、r2只能出现一个
这个限制的好处是 Rust 可以在编译时就避免数据竞争。数据竞争(data race)类似于竞态条件,它可由这三个行为造成:
- 两个或更多指针同时访问同一数据。
- 至少有一个指针被用来写入数据。
- 没有同步数据访问的机制。
数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复;Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码!
一如既往,可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用。
fn main() {let mut s = String::from("hello");// 这个可变引用在前面才可以{let r2 = &mut s;}let r1 = &mut s;println!("{}", r1);
}
类似的规则也存在于同时使用可变与不可变引用中。这些代码会导致一个错误:
fn main() {let mut s = String::from("hello");let r1 = &s; // 没问题let r2 = &s; // 没问题let r3 = &mut s; // 大问题println!("{}, {}, and {}", r1, r2, r3);}
结果
不能在拥有不可变引用的同时拥有可变引用。不可变引用的用户可不希望在他们的眼皮底下值就被意外的改变了!然而,多个不可变引用是可以的,因为没有哪个只能读取数据的人有能力影响其他人读取到的数据。
注意一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。例如,因为最后一次使用不可变引用在声明可变引用之前,所以如下代码是可以编译的:
fn main() {let mut s = String::from("hello");let r1 = &s; // 没问题let r2 = &s; // 没问题println!("{} and {}", r1, r2);// 此位置之后 r1 和 r2 不再使用let r3 = &mut s; // 没问题println!("{}", r3);}
这里是不是需要使用者主动判断,该不可变引用之后不再使用了?
悬垂引用(Dangling References)
在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针(dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。
fn main() {let reference_to_nothing = dangle();
}fn dangle() -> &String {let s = String::from("hello");&s
}
返回局部变量的引用?
错误信息引用了一个还未介绍的功能:生命周期(lifetimes)。第十章会详细介绍生命周期。不过,如果你不理会生命周期部分,错误信息中确实包含了为什么这段代码有问题的关键信息:
fn dangle() -> &String { // dangle 返回一个字符串的引用let s = String::from("hello"); // s 是一个新字符串&s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。// 危险!
因为 s
是在 dangle
函数内创建的,当 dangle
的代码执行完毕后,s
将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的 String
,这可不对!Rust 不会允许我们这么做。
这里的解决方法是直接返回 String
:
fn main() {}
fn no_dangle() -> String {let s = String::from("hello");s
}
引用的规则
- 在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用。
- 引用必须总是有效的。
4.3 Slice
另一个没有所有权的数据类型是 slice。slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。
这里有一个编程小习题:编写一个函数,该函数接收一个字符串,并返回在该字符串中找到的第一个单词。如果函数在该字符串中并未找到空格,则整个字符串就是一个单词,所以应该返回整个字符串。
fn first_word(s : &String) -> ?
first_word
函数有一个参数 &String
。因为不需要所有权,所以这没有问题。不过应该返回什么呢?并没有一个真正获取 部分 字符串的办法。不过,可以返回单词结尾的索引。
fn main() {let str = String::from("hello world");let count = first_word(&str);println!("{}", count);}fn first_word(s : &String) ->usize {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return i;}}s.len()
}
(1)first_word
函数返回 String
参数的一个字节索引值;
(2)因为需要逐个元素的检查 String
中的值是否为空格,需要用 as_bytes
方法将 String
转化为字节数组:
let bytes = s.as_bytes();
(3)接下来,使用 iter
方法在字节数组上创建一个迭代器;
for (i, &item) in bytes.iter().enumerate() {
只需知道 iter
方法返回集合中的每一个元素,而 enumerate
包装了 iter
的结果,将这些元素作为元组的一部分来返回;enumerate
返回的元组中,第一个元素是索引,第二个元素是集合中元素的引用。
因为 enumerate
方法返回一个元组,可以使用模式来解构,就像 Rust 中其他任何地方所做的一样。所以在 for
循环中,指定了一个模式,其中元组中的 i
是索引而元组中的 &item
是单个字节。因为从 .iter().enumerate()
中获取了集合元素的引用,所以模式中使用了 &
。
在 for
循环中,通过字节的字面值语法来寻找代表空格的字节。如果找到了一个空格,返回它的位置。否则,使用 s.len()
返回字符串的长度:
if item == b' ' {return i;}
}s.len()
现在有了一个找到字符串中第一个单词结尾索引的方法,不过这有一个问题。返回了一个独立的 usize
,不过它只在 &String
的上下文中才是一个有意义的数字。换句话说,因为它是一个与 String
相分离的值,无法保证将来它仍然有效。
fn first_word(s: &String) -> usize {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return i;}}s.len()
}fn main() {let mut s = String::from("hello world");let word = first_word(&s); // word 的值为 5s.clear(); // 这清空了字符串,使其等于 ""// word 在此处的值仍然是 5,// 但是没有更多的字符串让我们可以有效地应用数值 5。word 的值现在完全无效!println!("{}", word);
}
这个程序编译时没有任何错误,而且在调用 s.clear()
之后使用 word
也不会出错。因为 word
与 s
状态完全没有联系,所以 word
仍然包含值 5
。可以尝试用值 5
来提取变量 s
的第一个单词,不过这是有 bug 的,因为在我们将 5
保存到 word
之后 s
的内容已经改变。
还可以这样声明函数:
fn second_word(s: &String) -> (usize, usize) {
这样也容易受String的影响。引出字符串 slice。
字符串slice
字符串 slice(string slice)是 String
中一部分值的引用,它看起来像这样:
fn main() {let s = String::from("hello world");let hello = &s[0..5];let world = &s[6..11];
}
它不是对整个 String 的引用,而是对部分 String 的引用。
可以使用一个由中括号中的 [starting_index..ending_index]
指定的 range 创建一个 slice,其中 starting_index
是 slice 的第一个位置,ending_index
则是 slice 最后一个位置的后一个值。在其内部,slice 的数据结构存储了 slice 的开始位置和长度,长度对应于 ending_index
减去 starting_index
的值。所以对于 let world = &s[6..11];
的情况,world
将是一个包含指向 s
第 7 个字节(从 1 开始)的指针和长度值 5 的 slice。
对于 Rust 的 ..
range 语法,如果想要从第一个索引(0)开始,可以不写两个点号之前的值。换句话说,如下两个语句是相同的:
fn main() {let s = String::from("hello");let slice1: &str = &s[0..2];let slice2 = &s[..2];println!("{}, {}, {}", s, slice1, slice2);
}
依此类推,如果 slice 包含 String
的最后一个字节,也可以舍弃尾部的数字。这意味着如下也是相同的:
fn main() {let s = String::from("hello");let len = s.len();let slice1 = &s[3..len];let slice2 = &s[3..];println!("{}, {}, {}", s, slice1, slice2);
}
也可以同时舍弃这两个值来获取整个字符串的 slice。所以如下亦是相同的:
fn main() {let s = String::from("hello");let len = s.len();let slice1 = &s[0..len];let slice2 = &s[..];println!("{}, {}, {}", s, slice1, slice2);
}
越界编译时检查不出来。
重新实现本小节开始的案例
fn main() {let s = String::from("hello world");println!("{}", first_word(&s));
}fn first_word(s : &String) -> &str {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return &s[0..i] // 这里返回不了的话,就返回整个字符串}}&s[..]
}
测试
原来的案例,即清除了s,再输出
fn first_word(s: &String) -> &str {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return &s[0..i];}}&s[..]
}fn main() {let mut s = String::from("hello world");let word = first_word(&s); // word 的值为 5s.clear(); // 这清空了字符串,使其等于 ""// word 在此处的值仍然是 5,// 但是没有更多的字符串让我们可以有效地应用数值 5。word 的值现在完全无效!println!("{}", word);
}
结果
回忆一下借用规则,当拥有某值的不可变引用时,就不能再获取一个可变引用。因为 clear
需要清空 String
,它尝试获取一个可变引用。Rust不允许这样做,因而编译失败。
字符串字面值就是 slice
它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串字面值是不可变的;&str
是一个不可变引用。
字符串 slice 作为参数
在知道了能够获取字面值和 String
的 slice 后,我们对 first_word
做了改进,这是它的签名:
fn first_word(s: &String) -> &str {
而更有经验的 Rustacean 会编写出示例 4-9 中的签名,因为它使得可以对 String
值和 &str
值使用相同的函数:
fn first_word(s: &str) -> &str {
如果有一个字符串 slice,可以直接传递它。如果有一个 String
,则可以传递整个 String
的 slice。定义一个获取字符串 slice 而不是 String
引用的函数使得我们的 API 更加通用并且不会丢失任何功能:
fn first_word(s: &str) -> &str {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return &s[0..i];}}&s[..]
}
fn main() {let my_string = String::from("hello world");// first_word 中传入 `String` 的 slicelet word = first_word(&my_string[..]);let my_string_literal = "hello world";// first_word 中传入字符串字面值的 slicelet word = first_word(&my_string_literal[..]);// 因为字符串字面值 **就是** 字符串 slice,// 这样写也可以,即不使用 slice 语法!let word = first_word(my_string_literal);
}
其他类型的 slice
字符串 slice,正如你想象的那样,是针对字符串的。不过也有更通用的 slice 类型。考虑一下这个数组:
fn main() {let a = [1, 2, 3, 4, 5];
}
就跟我们想要获取字符串的一部分那样,我们也会想要引用数组的一部分。我们可以这样做:
fn main() {let a = [1, 2, 3, 4, 5];let slice = &a[1..3];for i in slice {println!("{} ", i);}}
总结
所有权、借用和 slice 这些概念让 Rust 程序在编译时确保内存安全。Rust 语言提供了跟其他系统编程语言相同的方式来控制你使用的内存,但拥有数据所有者在离开作用域后自动清除其数据的功能意味着你无须额外编写和调试相关的控制代码。
参考:认识所有权 - Rust 程序设计语言 简体中文版 (bootcss.com)
相关文章:

【Rust】Rust学习 第四章认识所有权
第四章认识所有权 所有权(系统)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全。因此,理解 Rust 中所有权如何工作是十分重要的。 4.1 所有权 所有运行的程序都…...

学习C语言第三天 :关系操作符、逻辑操作符
1.关系操作符 C语言用于比较的表达式,称为“关系表达式”里面使用的运算符就称(relationalexpression),为“关系运算符” (relationaloperator) ,主要有下面6个。 > 大于运算符 < 小于运算符 > 大于等于运算符 < 小于等…...

Jenkins自动化打包脚本
一、背景 jenkins可以设置定时任务打包,也已手动点按钮打包,还可以通过执行http请求打包,今天我们就通过shell脚本,通过curl命令进行jenkins打包。 二、步骤 2.1 在jenkins上构建项目 设置触发器 2.2 通过shell脚本触发远程构…...

一百五十、Kettle——Kettle官网下载地址
一、官网地址 Home - Hitachi VantaraThe site home pagehttps://community.hitachivantara.com/docs/DOC-1009855 二、下载地址 Pentaho from Hitachi Vantara download | SourceForge.netDownload Pentaho from Hitachi Vantara for free. End to end data integration and…...
使用 Visual Studio Code 调试 CMake 脚本
之前被引入到 Visual Studio 中的 CMake 调试器,现已在 Visual Studio Code 中可用。 也就是说,现在你可以通过在 VS Code 中安装 CMake 工具扩展,来调试你的 CMakeLists.txt 脚本了。是不是很棒? 背景知识 Visual C 开发团队和 CMake 的维…...

【云原生】Docker 详解(二):Docker 架构及工作原理
Docker 详解(二):Docker 架构及工作原理 Docker 在运行时分为 Docker 引擎(服务端守护进程) 和 客户端工具,我们日常使用各种 docker 命令,其实就是在使用 客户端工具 与 Docker 引擎 进行交互。…...

微服务 云原生:基于 Gogs + Drone 实现 CI/CD 自动化
一般构建部署 以一个简单的前后端项目来说,分别编写前后端的 Dockerfile 文件并构建镜像,然后编写 docker-compose.yml 构建部署,启动运行。每次代码变更后都需重新手动打包、构建、推送。 一个简单的例子: 前端: 项…...

ADO.NET之SQL Server
ADO.NET是.NET平台上的一组用于访问和操作关系型数据库的API。它提供了一种以统一的方式连接到各种数据库系统并执行数据库操作的方法。现在有很多的ORM框架都是基于ADO.NET进行数据访问(比如:Entity Framework (EF)、Dapper、NHibernate 、FluentNHiber…...

Nginx负载均衡(重点)
正向代理 部署正向代理 server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; proxy_pass http://20.0.0.60:80…...

第一章 SpringBoot入门
1.SpringBoot简介 1.1.简介 Spring Boot来简化spring应用开发,约定大于配置去繁从简,just run就能创建一个独立的,产品级别的应用。 背景:J2EE笨重开发,繁多的配置、低下开发效率、复杂的部署流程、第三方技…...

JavaScript Es6_2笔记 (深入对象 + 内置构造函数 + 包装类型)+包含实例方法
JavaScript 进阶 文章目录 JavaScript 进阶深入对象构造函数实例成员静态成员 内置构造函数ObjectArray包装类型StringNumber 了解面向对象编程的基础概念及构造函数的作用,体会 JavaScript 一切皆对象的语言特征,掌握常见的对象属性和方法的使用。 了解…...
尼科彻斯定理
目录 1.题目概述 2.题解 思路分析 具体实现 1.题目概述 验证尼科彻斯定理,即:任何一个整数m的立方都可以写成m个连续奇数之和。 例如: 1^31 2^335 3^37911 4^313151719 输入一个正整数m(m≤100),将…...

主数据管理案例-中国外运
1、 背景介绍及难点分析 作为世界领先的物流行业整合商、端到端的全程供应链解决方案和一站式物流服务提供商,中国外运非常重视信息化建设,先后投资建设了 300多个信息系统,为中国外运的内部管理和业务运作提供 IT 支持和保障。 由于缺乏统一…...

改进DevSecOps框架的 5 大关键技术
Markets and Markets的一项研究显示,全球DevOps的市场规模从2017年的29亿美元增加到2023年的103.1亿美元,预测期的年复合增长率(CAGR)为24.7%。人们对DevOps越来越感兴趣,因为DevOps不仅能够压缩软件的交付周期,还能提高交付的速度…...

uni-app之app上传pdf类型文件
通过阅读官方文档发现,uni.chooseFile在app端不支持非媒体文件上传; 可以使用这个插件,验证过可以上传pdf;具体使用可以去看文档 插件地址 就是还是会出现相机,这个可能需要自己解决下 实现功能:上传只能上…...

bash: sudo: command not found的解决方法 | 安装sudo
-bash: sudo: command not found的解决方法 https://www.cnblogs.com/pengpengboshi/p/16159443.html 报错 安装apt-get update报错由于没有公钥,无法验证下列签名: NO_PUBKEY A4B469963BF863CC解决办法是手动加入 (sudo可去掉)…...

电脑合上盖子无线网络不会断开
控制面板\硬件和声音\电源选项\系统设置 最终选择不会采取任何操作 选择不会采取任何操作...

【从零开始学习JAVA | 第四十篇】了解线程池
目录 前言: 线程池: 线程池的工作流程: 代码实现线程池: 任务拒绝策略: 线程池多大才算合适? 总结: 前言: 在Java编程中,线程池是一个强大的工具,它能…...

axios如何取消请求,其原理是什么?
axios 可以通过创建一个 CancelToken 来取消一个请求,基本原理是: 创建一个 CancelToken 的实例,它有一个 executor 函数,可以通过调用 executor 参数中的 cancel 函数来取消请求。在 axios 请求配置中指定 cancelToken 属性,将 CancelToken 实例传递进去。当我们需要取消请求…...

消息中间件 Asio (C++)
折腾了一上午,看到这个结果的时候泪目了兄弟闷,讲真。我的asio客户端成功收到服务端发来的消息了。虽然这确实是极其智障又简单的入门哈哈 下载独立版本 asio网络通信库新建cmake工程,CMakeLists.txt加载asioasio最简单的服务端和客户端代码…...

3.4 网络安全管理设备
数据参考:CISP官方 目录 IDS (入侵检测系统)网络安全审计漏洞扫描系统VPN(虚拟专网)堡垒主机安全管理平台 一、IDS (入侵检测系统) 入侵检测系统(IDS)是一种网络安全设备,用于监测和检测网络中的入侵行…...

前端高级面试题-JS
1. 原型 / 构造函数 / 实例 原型( prototype ): ⼀个简单的对象,⽤于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个 JavaScript 对象中都包含⼀个__proto__ (⾮标准)的属性指向它爹(该对象的原型),可 obj.p…...

AcWing 1564:哈希 ← 只具有正增量的二次探测法
【题目来源】https://www.acwing.com/problem/content/1566/【题目描述】 将一个由若干个不同正整数构成的整数序列插入到一个哈希表中,然后输出输入数字的位置。 哈希函数定义为 H(key)key%TSize,其中 TSize 是哈希表的最大大小。 利用只具有正增量的二…...
什么是媒体代发布?媒体代发布注意事项
传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 媒体代发布是指将新闻稿或其他宣传内容委托给专业的媒体代理机构或公司进行发布和推广的活动。这些机构通常拥有丰富的媒体资源、人脉和经验,能够更好地将信息传递给目标受众…...

docker版jxTMS使用指南:使用jxTMS采集数据之二
本文是如何用jxTMS进行数据采集的第二部分,整个系列的文章请查看:docker版jxTMS使用指南:4.4版升级内容 docker版本的使用,请查看:docker版jxTMS使用指南 4.0版jxTMS的说明,请查看:4.0版升级内…...

系列六、Springboot操作RocketMQ
一、同步消息 1.1、发送&接收简单消息 1.1.1、发送简单消息 /*** 测试发送简单消息*/ Test public void sendSimpleMessage() {SendResult result rocketMQTemplate.syncSend("BOOT_TOPIC_SIMPLE", "我是一个简单消息");// 往[BOOT_TOPIC_SIMPLE]主…...

【jupyter异常错误】Kernel started:No module named ipykernel_launcher
尝试过的方案 pip install ipykernel 执行之后提示已经安装,但是执行代码依然报错 解决方案 python -m pip install ipykernel -U --force-reinstall 相当于是强制重新安装 安装成功后没有报错 注:根本原因应该是原来安装的包存在问题,虽然检测出来已经存在…...

使用langchain与你自己的数据对话(五):聊天机器人
之前我已经完成了使用langchain与你自己的数据对话的前四篇博客,还没有阅读这四篇博客的朋友可以先阅读一下: 使用langchain与你自己的数据对话(一):文档加载与切割使用langchain与你自己的数据对话(二):向量存储与嵌入使用langc…...

爬虫与搜索引擎优化:通过Python爬虫提升网站搜索排名
作为一名专业的爬虫程序员,我深知网站的搜索排名对于业务的重要性。在如今竞争激烈的网络世界中,如何让自己的网站在搜索引擎结果中脱颖而出,成为关键。今天,和大家分享一些关于如何通过Python爬虫来提升网站的搜索排名的技巧和实…...

2024软考系统架构设计师论文写作要点
一、写作注意事项 系统架构设计师的论文题目对于考生来说,是相对较难的题目。一方面,考生需要掌握论文题目中的系统架构设计的专业知识;另一方面,论文的撰写需要结合考生自身的项目经历。因此,如何将自己的项目经历和专业知识有机…...