【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念

news2024/11/16 11:49:50

系列文章目录

【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念

文章目录

  • 系列文章目录
  • 前言
  • 一、所有权(Ownership)
    • 1.1.、所有权(Ownership)
    • 1.2、栈(Stack)和堆(Heap)
    • 1.3、所有权规则(Ownership Rules)
    • 1.4、变量作用域(Variable Scope)
    • 1.5、字符串类型(String Type)
      • 1.5.1、字符串切片引用(&str 类型)
      • 1.5.2、String类型
        • 1.5.2.1、 String 字符串介绍
        • 1.5.2.2、 创建 String 字符串
        • 1.5.2.2、 追加字符串
        • 1.5.2.3、 插入字符串
        • 1.5.2.4、字符串替换
        • 1.5.2.4、字符串删除
        • 1.5.2.5、字符串连接
        • 1.5.2.6、使用 format!连接字符串
        • 1.5.2.7、转义的方式 `\`
        • 1.5.2.7、字符串行连接
        • 1.5.2.7、原始字符串
        • 1.5.2.8、字符串带双引号问题
        • 1.5.2.9、字符数组
    • 1.6、内存(Memory)和分配(Allocation)
    • 1.7、变量与数据交互的方式
      • 1.7.1、移动(Move)
      • 1.7.3、拷贝(copy)
      • 1.7.2、克隆(clone)
    • 1.8、涉及函数的所有权机制
    • 1.9、函数返回值的所有权机制
  • 二、引用(Reference)和租借(Borrowing)
    • 2.1、引用(Reference)
    • 2.3、租借(Borrowing)
      • 2.3.1、租借
      • 2.3.2、Mutable References
    • 2.4、垂悬引用(Dangling Reference)
  • 三、切片(Slice Type)
    • 3.1、字符串切片(String Slice)
    • 3.2、数组切片
  • 总结


前言

本章节将讲解 Rust 独有的概念(所有权)。所有权是 Rust 最独特的特性,它使得 Rust 能够在不需要垃圾收集器的情况下保证内存安全。因此理解所有权是如何工作很重要,本章节将讲解所有权相关的特性:借用、切片以及 Rust 如何在内存中布局数据。

主要教材参考 《The Rust Programming Language》


一、所有权(Ownership)

所有权是 Rust 最独特的特性,它使得 Rust 能够在不需要垃圾收集器的情况下保证内存安全。

1.1.、所有权(Ownership)

在 Java 等编程语言中存在垃圾回收机制,在程序运行时定期查找不再使用的内存,在C/C++中,程序员必须显式地分配和释放内存。

在 Rust 之中使用了第三种方法:内存通过一个所有权系统进行管理,该系统拥有一组编译器检查的规则,如果违反了任何规则,程序将无法编译。所有权的任何特性都不会再程序运行时减慢它的速度。

1.2、栈(Stack)和堆(Heap)

许多语言不需要经常考虑堆和栈,但是在Rust 这样的系统编程语言中,值存在栈还是堆上都会影响语言的行为,以及你要做出什么样的处理。

堆栈都是都可以在运行时使用的内粗部分,但是它们的结构方式不同。栈按照获取值的顺序存储值,并按照相反的顺序删除值。这被称为后进先出。添加数据称为压栈(push),删除数据称为出栈(pop)。

存储在栈上的所有数据必须具有已知的固定大小。在编译时大小未知或大小可能改变的数据必须存储在堆中。

堆的组织较差:当你数据放在堆上,您请求一定数量的空间。内存分配器在堆中找到一个足够大的空间,将其标记为正在使用,并返回一个指针,该指针是该位置的地址,这个过程叫做在堆上分配,由于指向堆堆指针是已知的固定大小,因此可以将指针存储在栈上。

1.3、所有权规则(Ownership Rules)

所有权有如下三条规则

  • Rust 中的每个值都有一个变量,称为其所有者;
  • 一次只能有一个所有者;
  • 当所有者不再程序运行范围时,该值将会被删除;

这三条规则时所有权概念的基础;

1.4、变量作用域(Variable Scope)

通过理解下面的代码实例,可以理解变量作用范围。

fn main() {
    let s1 = "hello";
    {                        // s2 is not valid here, it’s not yet declared
        let s2 = "hello";    // s2 is valid from this point forward
        // do stuff with s2
    }                        // this scope is now over, and s is no longer valid
}

1.5、字符串类型(String Type)

1.5.1、字符串切片引用(&str 类型)

使用字符串字面初始化的字符串类型是 &str 类型的字符串。此种类型是已知长度,存储在可执行程序的只读内存段中(rodata)。通过 &str 可以引用过 rodata 中的字符串。

let s:&str = "hello";

如果想直接使用 str 类型 是不可以的,只能通过 Box<str> 来使用。

1.5.2、String类型

1.5.2.1、 String 字符串介绍

Rust 在语言级别,只有一种字符串类型: str,它通常是以引用类型出现 &str,也就是上文提到的字符串切片引用。虽然语言级别只有上述的 str 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 String 类型。

Rust 中的字符串是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的。

字符串字面量值被硬编码到程序中,它们是不可变的。但是实际上并不是所有的字符串值都是已知的。如果我们想要获取用户输入并存储他,Rust 提供了第二种字符串类型。这种类型管理在堆上分配的数据。

fn main() {
    let s1:&str = "hello";
    let s2:String = s1.to_string();
    let s3:&String = &s2;
    let s4:&str = &s2[0..3];
    let s5:String = String::from(s1);
}

需要注意,rust 要求索引必须是 usize 类型。如果起始索引是0,可以简称 &s[..3] ,同样可以终止索引 是 String 的最后一个字节,那么可以简写为 &s[1..],如果要引用整个 String 可以简写为 &s[..]

字符串切片引用索引必须在字符之间的边界未知,但是由于 rust 的字符串是 utf-8 编码,因此必须小心。

1.5.2.2、 创建 String 字符串

let s = "Hello".to_string();
let s = String::from("world");
let s: String = "also this".into();

1.5.2.2、 追加字符串

fn main() {
    let mut s = String::from("Hello ");
    s.push('r');
    println!("追加字符 push() -> {}", s);

    s.push_str("ust!");
    println!("追加字符串 push_str() -> {}", s);
}

1.5.2.3、 插入字符串

fn main() {
    let mut s = String::from("Hello rust!");
    s.insert(5, ',');
    println!("插入字符 insert() -> {}", s);
    s.insert_str(6, " I like");
    println!("插入字符串 insert_str() -> {}", s);
}

1.5.2.4、字符串替换

1、replace 方法使用 两种类型的 字符串;

fn main() {
    let string_replace = String::from("I like rust. Learning rust is my favorite!");
    let new_string_replace = string_replace.replace("rust", "RUST");
    dbg!(new_string_replace); // 调试使用宏
    let s = "12345";
    let new_s = s.replace("3", "t");
    dbg!(new_s); 
}

2、replacen 方法使用 两种类型的 字符串;

fn main() {
    let string_replace = "I like rust. Learning rust is my favorite!";
    let new_string_replacen = string_replace.replacen("rust", "RUST", 1);
    dbg!(new_string_replacen);
}

3、replace_range 只使用 String 类型

fn main() {
    let mut string_replace_range = String::from("I like rust!");
    string_replace_range.replace_range(7..8, "R");
    dbg!(string_replace_range);
}

1.5.2.4、字符串删除

1、pop

fn main() {
    let mut string_pop = String::from("rust pop 中文!");
    let p1 = string_pop.pop();
    let p2 = string_pop.pop();
    dbg!(p1);
    dbg!(p2);
    dbg!(string_pop);
}

2、remove

fn main() {
    let mut string_remove = String::from("测试remove方法");
    println!(
        "string_remove 占 {} 个字节",
        std::mem::size_of_val(string_remove.as_str())
    );
    // 删除第一个汉字
    string_remove.remove(0);
    // 下面代码会发生错误
    // string_remove.remove(1);
    // 直接删除第二个汉字
    // string_remove.remove(3);
    dbg!(string_remove);
}

3、truncate

fn main() {
    let mut string_truncate = String::from("测试truncate");
    string_truncate.truncate(3);
    dbg!(string_truncate);
}

4、clear

fn main() {
    let mut string_clear = String::from("string clear");
    string_clear.clear(); // 相当于string_clear.truncate(0)
    dbg!(string_clear);
}

1.5.2.5、字符串连接

字符串连接 使用 + 或 += 操作符,要求右边的参数必须是字符串的切片引。使用 + 相当于使用 std::string 标准库中的 add 方法

fn main() {
    let string_append = String::from("hello ");
    let string_rust = String::from("rust");
    // // &string_rust会自动解引用为&str,这是因为deref coercing特性。这个特性能够允许把传进来的&String,在API执行之前转成&str。
    let result = string_append + &string_rust;
    let mut result = result + "!";
    result += "!!!";

    println!("连接字符串 + -> {}", result);
}

1.5.2.6、使用 format!连接字符串

这种方式适用于 String 和 &str,和C/C++提供的sprintf函数类似

fn main() {
    let s1 = "hello";
    let s2 = String::from("rust");
    let s = format!("{} {}!", s1, s2);
    println!("{}", s);
}

1.5.2.7、转义的方式 \

fn main() {
    // 通过 \ + 字符的十六进制表示,转义输出一个字符
    let byte_escape = "I'm writing \x52\x75\x73\x74!";
    println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);

    // \u 可以输出一个 unicode 字符
    let unicode_codepoint = "\u{211D}";
    let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";

    println!(
        "Unicode character {} (U+211D) is called {}",
        unicode_codepoint, character_name
    );
}

1.5.2.7、字符串行连接

fn main() {
    let long_string = "String literals
                    can span multiple lines.
                    The linebreak and indentation here \	
                    can be escaped too!";
    println!("{}", long_string);
}

1.5.2.7、原始字符串

使用 r 开头的字符串不会被转义

fn main() {
    println!("{}", "hello \x52\x75\x73\x74");           // 输出hello Rust
    let raw_str = r"Escapes don't work here: \x3F \u{211D}";    // 原始字符串
    println!("{}", raw_str);        // 输出Escapes don't work here: \x3F \u{211D}
}

1.5.2.8、字符串带双引号问题

rust 提供来 r# 方式来避免引号嵌套的问题。

fn main() {
    // 如果字符串包含双引号,可以在开头和结尾加 #
    let quotes = r#"And then I said: "There is no escape!""#;
    println!("{}", quotes);

    // 如果还是有歧义,可以继续增加#,没有限制
    let longer_delimiter = r###"A string with "# in it. And even "##!"###;
    println!("{}", longer_delimiter);
}

1.5.2.9、字符数组

由于 rust 的字符串是 utf-8 编码的,而String 类型不允许以字符为单位进行索引。所以 String 提供了 chars() 遍历字符和 bytes() 方法遍历字节。

但是需要从 String 中获取子串是比较困难的,标准库中没有提供相关的方法。

1.6、内存(Memory)和分配(Allocation)

字符出字面量快速高效,是因为硬编码到最终可执行文件之中。这些字符出是不可变的。但是我们不能为每个在编译时大小未知且运行程序时可能改变的文本放入二进制文件的内存块中。

String 类型为了支持可变、可增长的文本片段,我们需要在堆上分配一定数量的内存来保存内容,这就意味着内存必须在运行时从内存分配器中请求,我们需要一种方法,在使用完 String 后将这些内存返回给分配器。

第一部分:当调用String::from时,它的实现请求它所需的内存。
第二部分:在带有垃圾回收器(GC)的语言, GC 会清理不在使用的内存,我们不需要考虑他。在没有GC的语言中我们有责任识别内存不再使用,并且调用代码显式释放它。

Rust 采用不同的方式:一旦拥有的内存的变量超出作用域,内存就会自动返回(Rust 会为我们调用一个特殊的函数叫做 drop)。
在c++中 这种项目生命周期结束时释放资源的模式有时候被称为 资源获取即初始化(RALL)。

1.7、变量与数据交互的方式

1.7.1、移动(Move)

1、赋值

将一个变量赋值给另一个变量会将所有权转移。

    let s1 = String::from("hello");
    let s2 = s1;

2、参数传递或函数返回

赋值并不是唯一涉及移动的操作,值在作为参数传递或从函数返回时也会被移动。

3、赋给结构体或 enum

1.7.3、拷贝(copy)

在编译是已知大小的整数类型完全存在栈中,因此可以快速直接复制实际值。

    let x = 5;
    let y = x;

    println!("x = {}, y = {}", x, y);

在 Rust 中有一个特殊的注解 叫做 Copy trait ,我们可以把它放在存储在栈上的类型,就像整数一样,如果一个类型实现了 Copy 特性,那么它的变量就不会移动。

如果类型或其任何部分实现了Drop trait,Rust将不允许我们用Copy注释类型。

实现了 Copy trait的类型

  • 所有的整型类型

  • bool 类型: true、false

  • 浮点类型;f32、f64

  • 字符类型:char

  • 元组(只包含了实现了 Copy trait),例如 (i32,i32) 就是 copy,而( i32,String) 是move;

1.7.2、克隆(clone)

    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {}, s2 = {}", s1, s2);

1.8、涉及函数的所有权机制

fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    println!("s {}", s);
    let x = 5;                      // x comes into scope

    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it's okay to still
                                    // use x afterward

} // Here, x goes out of scope, then s. But because s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

1.9、函数返回值的所有权机制

fn main() {
  let s1 = gives_ownership();         // gives_ownership moves its return
                                      // value into s1

  let s2 = String::from("hello");     // s2 comes into scope

  let s3 = takes_and_gives_back(s2);  // s2 is moved into
                                      // takes_and_gives_back, which also
                                      // moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
// happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {             // gives_ownership will move its
                                           // return value into the function
                                           // that calls it

  let some_string = String::from("yours"); // some_string comes into scope

  some_string                              // some_string is returned and
                                           // moves out to the calling
                                           // function
}

// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
                                                    // scope

  a_string  // a_string is returned and moves out to the calling function
}

二、引用(Reference)和租借(Borrowing)

2.1、引用(Reference)

引用(Reference) 是 C++ 开发者较为熟悉的概念,如果你熟悉指针的概念,你可以把它当作一种指针。实质上,引用是变量的间接访问方式。

我们使用引用就可以避免所有权移动导致原先的变量不能使用的问题。

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()
}

引用的原理图
因为 s 是 String的引用,它没有所有权,当函数结束的时候,并不会drop。

2.3、租借(Borrowing)

2.3.1、租借

引用不会获得值的所有权,引用只能租借(Borrow)值的所有权。引用本身也是一种类型并具有一个值,这个值记录的是别的值所在的位置,但引用不具有所有值的所有权。

fn main() {
    let s = String::from("hello");

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:3
  |
8 |   some_string.push_str(", world");
  |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
  |
help: consider changing this to be a mutable reference
  |
7 | fn change(some_string: &mut String) {
  |                        ~~~~~~~~~~~

For more information about this error, try `rustc --explain E0596`.
error: could not compile `hello` (bin "hello") due to previous error

从上述错误提示可以知道 reference 是租借引用,只能读不能进行写操作,我们可以使用 &mut 来进行 可变引用。

2.3.2、Mutable References

使用 Mutable References 可以修改引用的内容。

fn main() {
  let mut s = String::from("hello");

  change(&mut s);
}

fn change(some_string: &mut String) {
  some_string.push_str(", world");
}

一个变量只能有一个 Mutable References。

对同一个值有不可变引用的时候,不能有可变引用。

2.4、垂悬引用(Dangling Reference)

垂悬引用 好像也叫做 野指针。

如果在有指针概念的编程语言,它指的是那种没有实际指向一个真正能访问的数据和指针(注意,不一定是空指针,还有可能是已经释放的资源),它们就像失去悬挂物体的绳子,所以叫做垂悬引用。

垂悬引用 在 Rust 语言里面不允许出现,如果有,编译器会发现它

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

很显然,伴随着 dangle 函数的结束,其局部变量的值本身没有被当作返回值,被释放了。但它的引用却被返回,这个引用所指向的值已经不能确定的存在,故不允许其出现。

error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
5 | fn dangle() -> &'static String {
  |                 +++++++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `hello` (bin "hello") due to previous error

三、切片(Slice Type)

切片(Slice) 是对数据值的部分引用。

3.1、字符串切片(String Slice)

最简单、最常用的数据切片类型是字符串切片(String Slice)。

3.2、数组切片

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);

总结

以上就是今天要讲的内容

  • 本文介绍 rust的所有权、切片、字符出类型、租借、可变引用,本章节的内容比较难以理解;

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

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

相关文章

【unity每日一记】 Camera相机+ Screen屏幕+动画机

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

Flutter开发——图片加载与缓存源码解析

在Flutter中有个图片组件&#xff1a;Image,通常会使用它的Image.network(src)、Image.file(src)、Image.asset(src)来加载图片。 下面是Image的普通构造方法&#xff1a; const Image({super.key,required this.image,this.frameBuilder,this.loadingBuilder,this.errorBuilde…

第四章 机器学习

文章目录 第四章 决策树4.1基本流程4.2划分选择4.2.1信息增益4.2.2增益率4.2.3基尼指数 4.3剪枝处理4.3.1预剪枝4.3.2后剪枝 4.4连续与缺失值4.4.1连续值处理4.4.2缺失值处理 4.5多变量决策树 第四章 决策树 4.1基本流程 决策过程&#xff1a; 基本算法&#xff1a; 4.2划…

git——使用ssh连接远程仓库

文章目录 前言一. 获取邮箱和密码1. 本地配置你的名字和邮箱2. 使用命令获取你本地的邮箱和密码 二、生成ssh公钥1.任意一个文件夹路径打开Git Bash Here并输入以下命令连按三次回车2. 根据上面红框部分的地址打开文件夹3. 打开并查看id_rsa.pub 文件 三、在GitHub上连接ssh1. …

电商API知识点整理(一)商品采集接口获取商品详情数据API

商品采集接口背景 电商商品采集接口是一种机器人软件接口&#xff0c;用于从电子商务网站上爬取商品信息。它的主要作用是将电商网站上的商品信息采集和整合&#xff0c;方便用户使用。传统的商品采集需要人工收集和整理&#xff0c;工作量大、效率低&#xff1b;而电商商品采…

Flutter的文本、图片和按钮使用

像视图数据流转机制、底层渲染方案、视图更新策略等知识&#xff0c;都是构成一个UI框架的根本&#xff0c;看似枯燥&#xff0c;却往往具有最长久的生命力。 因此&#xff0c; 只有把这些最基础的知识弄明白&#xff0c;修好内功&#xff0c;才能触类旁通&#xff0c;由点及面…

输入阻抗、输出阻抗和阻抗匹配

读者问了一个问题&#xff1a;“集总参数电路中&#xff0c;阻抗匹配&#xff08;内阻外阻&#xff09;可以使负载得到最大的功率输出”这句话怎么理解&#xff1f; 这里涉及到几个概念&#xff1a;输入阻抗、输出阻抗、阻抗匹配&#xff0c;今天简单的聊一聊。 先了解一下阻…

用Visual Studio 2022写出你第一个Windows程序(程序保证能正常运行)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天来看看如何用Visual C写出你第一个Windows程序。 与其看很多Windows的书&#xff0c;不如先自己动手写一个Windows程序。由于Windows程序的特有机制&#xff0c;不建议去写那种简单的HELLO WORLD&#x…

【计算机网络详解】——网络层(学习笔记)

&#x1f4d6; 前言&#xff1a;网络层它承担着网络间的数据传输和路由选择等核心任务&#xff0c;通过在传输层协议的基础上添加了路由和转发等功能&#xff0c;使得数据能够在全球范围内的互联网中自由流动。在这篇博客中&#xff0c;我们将深入探讨网络层的工作原理和具体实…

D. Binary String Sorting(枚举位置)

Problem - 1809D - Codeforces 给定一个仅由字符0和/或1组成的二进制字符串s。 您可以对此字符串执行几个操作&#xff08;可能为零&#xff09;。有两种类型的操作&#xff1a; 选择两个相邻的元素并交换它们。为了执行此操作&#xff0c;您需要支付1012硬币&#xff1b; 选…

网络作业10【计算机网络】

网络作业10【计算机网络】 前言推荐网络作业10一. 单选题&#xff08;共13题&#xff0c;68.2分&#xff09;二. 多选题&#xff08;共4题&#xff0c;21.2分&#xff09;三. 阅读理解&#xff08;共2题&#xff0c;10.6分&#xff09; 练习5-39 最后 前言 2023-6-23 15:35:39…

MySQL ----主从复制、分离解析

文章目录 一、MySQL 主从复制1.1服务性能扩展方式1.2 MySQL的扩展什么是读写分离&#xff1f; 1.3为什么要读写分离呢&#xff1f;1.4什么时候要读写分离&#xff1f;1.5主从复制与读写分离1.6mysql支持的复制类型1.7主从复制的工作过程1.8MySQL 读写分离原理1.9目前较为常见的…

2023年05月份青少年软件编程Scratch图形化等级考试试卷四级真题(含答案)

2023-05 Scratch四级真题 分数&#xff1a;100 题数&#xff1a;24 测试时长&#xff1a;90min 一、单选题(共10题&#xff0c;共30分) 1. 下列积木运行后的结果是&#xff1f;&#xff08;B&#xff09;&#xff08;说明&#xff1a;逗号后面无空格&#xff09;&#xff08…

考研算法29天:希尔排序 【希尔排序】

算法介绍 希尔排序 等差数列 普通版插入排序 循环数组 第一次每n/2为间隔分为4组&#xff0c;然后组内排序。 第二次每n/4为间隔分为2组。然后组内排序 第三次n/8为间隔分为一组。然后组内排序。 组内排序用插入排序来排序。 注&#xff1a;也可以第一次为n/3为间隔&am…

Elasticsearch:DSL Query

Query DSL的分类 Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括&#xff1a; 查询所有&#xff1a;查询出所有的数据&#xff0c;一般测试用&#xff0c;例如&#xff1a;match_all&#xff0c;但有分页限制&#xff0c;一次20…

知乎网友问题:Android 悬浮窗怎么让窗口响应事件的同时,也能让背后挡住的地方收到事件?---腾讯课堂千里马亲自解答

问题&#xff1a;Android 悬浮窗怎么让窗口响应事件的同时&#xff0c;也能让背后挡住的地方收到事件&#xff1f; 点击悬浮窗&#xff0c;自己能收到事件&#xff0c;背后挡住的区域也要能收到&#xff0c;怎么实现&#xff0c;return. false 无效 原生android机制不支持原因…

【C++入门第五期】类和对象(中)

这里写目录标题 类的6个默认成员函数构造函数特征 析构函数概念特性 拷贝构造特征拷贝构造如何自定义 运算符重载赋值运算符重载赋值运算符重载前置和后置重载 取地址及const取地址操作符重载 类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 class…

基于深度学习的高精度抽烟行为检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度抽烟行为检测识别系统可用于日常生活中或野外来检测与定位抽烟行为目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的抽烟行为目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5…

【五子棋实战】第6章 调用接口进行联调

【五子棋实战】第6章 调用接口进行联调 Ajax调用接口 调用五子棋接口 点击优化 尾声 更多待开发的功能 Ajax调用接口 引入Jquery&#xff0c;使用JQ封装的ajax&#xff0c;demo如下&#xff1a; <script src"jquery-3.5.0.min.js"></script> <…

无显示器玩转树莓派桌面版

title: 无显示器玩转树莓派桌面版 zhaoolee在Github开启了长篇连载《树莓派不吃灰》https://github.com/zhaoolee/pi 目前已经更新到18篇&#xff0c;主要是给树莓派刷Ubuntu当做家庭服务器用。 恰好手头还有一块闲置的树莓派4B &#xff0c;我打算深度玩一下树莓派桌面版&…