Rust之错误处理(二):带结果信息的可恢复错误

news2024/11/18 19:32:35

开发环境

  • Windows 10
  • Rust 1.67.1

 

  •  VS Code 1.75.1

   项目工程

这里继续沿用上次工程rust-demo

带结果信息的可恢复错误

大多数错误并没有严重到需要程序完全停止的程度。有时,当一个函数失败时,它的原因是你可以很容易地解释和应对的。例如,如果你试图打开一个文件,但由于该文件不存在而导致操作失败,你可能想创建该文件,而不是终止该进程。

回顾前面章节中的 "用结果处理潜在的失败",结果枚举被定义为有两个变体,OkErr,如下例所示。

enum Result<T, E> {
    Ok(T),
    Err(E),
}

TE是通用类型参数:我们将在后面章节中详细讨论通用类型。你现在需要知道的是,T代表在Ok变量中成功情况下将被返回的值的类型,E代表在Err变量中失败情况下将被返回的错误类型。因为Result有这些通用的类型参数,我们可以在许多不同的情况下使用Result类型和定义在它上面的函数,我们想返回的成功值和错误值可能不同。

让我们调用一个返回结果值的函数,因为这个函数可能会失败。在下例3中,我们尝试打开一个文件。

例3:文件名: src/main.rs

use std::fs::File;

fn main() {
    let greeting_file_result = File::open("hello.txt");  // hello.txt是否存在
    println!("greeting_file_result = {:?}", greeting_file_result )
}

编译运行

cargo run

 File::open 的返回类型是一个 Result<T, E>。通用参数T已经被File::open的实现填入了成功值的类型,即std::fs::File,它是一个文件句柄。错误值中使用的E的类型是std::io::Error。这个返回类型意味着对 File::open 的调用可能会成功,并返回一个我们可以读取或写入的文件句柄。该函数的调用也可能失败:例如,该文件可能不存在,或者我们可能没有访问该文件的权限。File::open函数需要有一种方法来告诉我们它是成功还是失败,同时给我们提供文件柄或错误信息。这些信息正是Result枚举所传达的。

File::open成功的情况下,变量greeting_file_result中的值将是一个Ok的实例,包含一个文件柄。在失败的情况下,greeting_file_result中的值将是一个Err的实例,包含关于发生的错误类型的更多信息。

我们需要在上例2中的代码中添加一些内容,以便根据File::open返回的值采取不同的行动。下例3显示了一种使用基本工具处理结果的方法,即我们在之前章节讨论过的match表达式。

例4:文件名: src/main.rs

use std::fs::File;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {      // match表达式
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error),
    };
}

请注意,和Option枚举一样,Result枚举和它的变体已经被前奏带入了范围,所以我们不需要在匹配类中的OkErr变体之前指定Result::

当结果为Ok时,该代码将从Ok变量中返回内部文件值,然后我们将该文件句柄值赋给变量greeting_filematch后,我们可以使用文件句柄来读或写。

match的另一方面处理从File::open获取一个Err值的情况。在这个例子中,我们选择了panic!宏观。如果在我们的当前目录中没有名为hello.txt的文件,并且我们运行了这段代码,我们将会看到下面的输出panic!宏:

编译运行

cargo run

像往常一样,这个输出告诉我们到底哪里出错了。

不同错误的匹配

上例中的代码会panic!不管为什么File::open失败。但是,我们希望针对不同的失败原因采取不同的操作:如果File::open因为文件不存在而失败,我们希望创建该文件并将句柄返回给新文件。如果File::open由于任何其他原因失败—例如,因为我们没有打开文件的权限—我们仍然希望代码panic!就像上例中一样。为此,我们添加了一个内部match表达式,如下例5所示。 

例5:文件名: src/main.rs

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => match error.kind() {    // match错误类型
            ErrorKind::NotFound => match File::create("hello.txt") {   // 创建hello,ext
                Ok(fc) => fc,
                Err(e) => panic!("Problem creating the file: {:?}", e),
            },
            other_error => {
                panic!("Problem opening the file: {:?}", other_error);
            }
        },
    };
}

编译运行

cargo run

 File::openErr内部返回值的类型是io::Error,这是标准库提供的一个结构体。这个结构有一个方法kind,我们可以调用它来获取io::ErrorKind值。枚举io::ErrorKind由标准库提供,它具有代表io操作可能导致的不同类型错误的变量。我们要使用的变量是ErrorKind::NotFound,这表明我们试图打开的文件尚不存在。所以我们在greeting_file_result上进行匹配,但是在error.kind()上也有一个内部匹配。

我们要在内部匹配中检查的条件是error.kind()返回的值是否是ErrorKind枚举的NotFound变量。如果是,我们尝试用File::create创建文件。然而,因为File::create也可能失败,所以我们需要在内部match表达式中增加一个分支。当无法创建文件时,会打印一条不同的错误消息。外部match的第二个分支保持不变,因此除了丢失文件错误之外,程序还会对任何错误产生恐慌。

使用match Result< T,E >的替代方法

match的多了去了!match表达式非常有用,但也非常原始。在后面章节中,你将学习闭包,它与Result<T,E >上定义的许多方法一起使用。在代码中处理Result<T,E >值时,这些方法比使用match更简洁。

例如,这里有另一种方法来编写如上例中所示的逻辑,这次使用闭包和unwrap_or_else方法:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Problem creating the file: {:?}", error);
            })
        } else {
            panic!("Problem opening the file: {:?}", error);
        }
    });
}

尽管这段代码的行为与上例相同,但它不包含任何match表达式,读起来更清晰。在你读完后面章节后回到这个例子,在标准库文档中查找unwrap_or_else方法。当您处理错误时,更多的这些方法可以清理大量嵌套的match表达式。

出错时死机的快捷方式:unwrap和expect

使用match可以很好地工作,但是它可能有点冗长,并且不总是很好地传达意图。Result<T,E >类型定义了许多方法来完成各种更具体的任务。unwrap方法是一个快捷方法,就像我们在上例中编写的match表达式一样。如果Result值是Ok变量,unwrap将返回Ok变量中的值。如果ResultErr变量,unwrap将调用panic!宏观对我们来说。下面是一个实际展开的例子:

文件名:src/main.rs

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap();
    println!("greeting_file = {:?}", greeting_file);
}

运行

cargo run

 如果我们在没有hello.txt文件的情况下运行这段代码,我们将会看到一条来自panic!的错误消息!unwrap方法进行的调用:

同样,expect方法也让我们选择panic!错误消息。使用expect而不是unwrap并提供良好的错误消息可以传达您的意图,并使跟踪死机的根源变得更容易。expect的语法如下例所示:

文件名:src/main.rs

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt")
        .expect("hello.txt should be included in this project");
    println!("greeting_file = {:?}", greeting_file);
}

运行

cargo run

 我们使用expect的方式和unwrap一样:返回文件句柄或者调用panic!宏。expect在调用panic!时使用的错误消息!将是我们传递给expect的参数,而不是默认的panic!unwrap使用的消息。它看起来是这样的:

 在生产质量的代码中,大多数Rust程序员选择expect而不是unwrap,并给出更多关于为什么操作预期总是成功的上下文。这样,如果您的假设被证明是错误的,您就有更多的信息用于调试。

传播误差

当函数的实现调用可能失败的东西时,您可以将错误返回给调用代码,以便它可以决定做什么,而不是在函数本身中处理错误。这就是所谓的传播错误,并给予调用代码更多的控制,其中可能有比代码上下文中可用的更多的信息或逻辑来指示应该如何处理错误。

下例7显示了一个从文件中读取用户名的函数。如果该文件不存在或无法读取,该函数将把这些错误返回给调用该函数的代码。

例6:文件名:src/main.rs

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let username_file_result = File::open("hello.txt");

    let mut username_file = match username_file_result {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut username = String::new();

    match username_file.read_to_string(&mut username) {
        Ok(_) => Ok(username),
        Err(e) => Err(e),
    }
}

这个函数可以用更短的方式编写,但是为了探索错误处理,我们将从手工做大量的工作开始;最后,我们将展示更短的方法。我们先来看看函数的返回类型:Result<String,io::Error >。这意味着该函数正在返回类型Result< T,E >的值,其中泛型参数T已经用具体类型String填充,泛型类型E已经用具体类型io::Error填充。

如果这个函数成功,没有任何问题,调用这个函数的代码将收到一个Ok值,该值包含一个String—这个函数从文件中读取的用户名。如果这个函数遇到任何问题,调用代码将收到一个Err值,该值包含io::Error的一个实例,该实例包含有关问题的更多信息。我们选择io::Error作为这个函数的返回类型,因为这恰好是我们在这个函数体中调用的可能失败的两个操作返回的错误值的类型:File::open函数和read_to_string方法。

函数体通过调用File::open函数开始。然后我们用类似于之前例子中的匹配来处理Result值。如果File::open成功,模式变量file中的文件句柄将变成可变变量username_file中的值,函数将继续运行。在Err的情况下,而不是叫painc!,我们使用return关键字从函数中提前返回,并将File::open中的错误值(现在在模式变量e中)作为该函数的错误值传递回调用代码。

因此,如果我们在username_file中有一个文件句柄,该函数就会在变量username中创建新String,并在username_file中的文件句柄上调用read_to_string方法,将文件内容读入usernameread_to_string方法也返回一个Result,因为它可能会失败,即使File::open成功了。所以我们需要另一个匹配来处理这个结果:如果read_to_string成功了,那么我们的函数就成功了,我们从文件中返回username,这个文件现在包含在一个Ok中。如果read_to_string失败,我们返回错误值的方式与我们在处理File::open返回值的match中返回错误值的方式相同。但是,我们不需要显式地说return,因为这是函数中的最后一个表达式。

调用此代码的代码将处理获取包含用户名的Ok值或包含io::ErrorErr值。由调用代码决定如何处理这些值。如果调用代码得到一个Err值,它可能会调用panic!并使程序崩溃,使用默认用户名,或者从文件以外的地方查找用户名。我们没有足够的信息来了解调用代码实际试图做什么,所以我们向上传播所有的成功或错误信息,以便它进行适当的处理。

这种传播错误的模式在Rust中非常普遍,以至于Rust提供了问号运算符为了让事情变得简单

传播错误的快捷方式 :? 操作符

下例7显示了read_username_from_file的一个实现,它具有与上例相同的功能,但是这个实现使用了操作符。 

例7:文件名:src/main.rs

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = File::open("hello.txt")?;   // ? 操作符
    let mut username = String::new();
    username_file.read_to_string(&mut username)?;       // ? 操作符
    Ok(username)
}

放置在Result值定义之后,其工作方式与我们在上例中定义的处理结果值的match表达式几乎相同。如果Result的值是OkOk中的值将从这个表达式返回,程序将继续。如果值是一个ErrErr将从整个函数中返回,就像我们使用return关键字一样,因此错误值将传播到调用代码。

上例中的match表达式和? 操作符所做的事情是有区别的:被调用了? 操作符的错误值会经过标准库中的From特性中定义的from函数,该函数用于将值从一种类型转换成另一种类型。当? 操作符调用from函数时,收到的错误类型被转换为当前函数的返回类型中定义的错误类型。当一个函数返回一个错误类型时,这很有用,它代表了一个函数可能失败的所有方式,即使部分函数可能因为许多不同的原因而失败。

例如,我们可以改变上例7中的 read_username_from_file 函数来返回一个我们定义的名为 OurError 的自定义错误类型。如果我们也为OurError定义 impl From<io::Error> for io::Error构造一个OurError的实例,那么read_username_from_file正文中的? 操作符调用将调用from并转换错误类型,而不需要在函数中添加任何代码。

在上例的上下文中,File::open调用的结尾处的? 将返回Ok内的值到变量username_file。如果发生错误,? 操作符将提前从整个函数中返回,并将任何Err值交给调用代码。同样的事情也适用于read_to_string调用结束时的?

? 运算符消除了大量的模板,使这个函数的实现更加简单。我们甚至可以通过在"? "后面紧跟的方法调用链来进一步缩短这段代码,如下例8中所示。

例8:文件名:src/main.rs

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username = String::new();

    File::open("hello.txt")?.read_to_string(&mut username)?;

    Ok(username)
}

我们把创建用username中的新String移到了函数的开头;这部分没有改变。我们没有创建一个变量username_file,而是将对read_to_string的调用直接链接到File::open("hello.txt")的结果上。我们在 read_to_string 调用的最后仍然有一个 ? ,而且当 File::openread_to_string 都成功时,我们仍然返回一个包含用户名的 Ok 值,而不是返回错误。其功能与例6和例7中的相同;这只是一种不同的、更符合人体工程学的写法而已。

例9显示了一种使用fs::read_to_string使其更短的方法。

例9:文件名:src/main.rs

use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
    fs::read_to_string("hello.txt")
}

将文件读入字符串是一个相当常见的操作,所以标准库提供了方便的fs::read_to_string函数,该函数打开文件,创建一个新的String,读取文件的内容,将内容放入该String中,并返回它。当然,使用fs::read_to_string并没有给我们解释所有错误处理的机会,所以我们先用较长的方法来做。

哪些地方可以使用? "操作符

? 操作符只能在返回类型与操作符所使用的值兼容的函数中使用。这是因为?操作符被定义为执行从函数中提前返回一个值,其方式与我们在例6 中定义的match表达式相同。在清例6中,match使用的是一个Result值,而提前返回了一个Err(e)值。函数的返回类型必须是一个 Result,这样才能与这个Return兼容。

在例10中,让我们看看如果我们在一个main函数中使用? 操作符,其返回类型与我们使用的值的类型不兼容,我们会得到什么错误。

例10:文件名:src/main.rs

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt")?;
}

这段代码打开了一个文件,这可能会失败。? 操作符跟随File::open返回的结果值,但这个main函数的返回类型是(),而不是Result。当我们编译这段代码时,我们会得到以下错误信息。

这个错误指出,我们只允许在返回 ResultOption 或其他实现 FromResidual 的类型的函数中使用 ? 操作符。

要解决这个错误,你有两个选择。一种选择是改变你的函数的返回类型,使之与你使用的? 运算符的值兼容,只要你没有限制阻止这样做。另一种技术是使用matchResult<T, E>方法之一,以任何适当的方式处理Result<T, E>

该错误信息还提到,? 也可以用于 Option<T>值。与在Result上使用 ? 一样,你只能在一个返回 Option 的函数中对 Option 使用 ? 。在 Option<T>上调用 ? 操作符的行为与在 Result<T, E>上调用的行为类似:如果值是 NoneNone将在此时从函数中提前返回。如果值是SomeSome里面的值就是表达式的结果值,函数继续。例11中有一个函数的例子,它可以找到给定文本中第一行的最后一个字符。

例11

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}

这个函数返回Option<char>,因为那里有可能有一个字符,但也有可能没有。这段代码接收了文text字符串的切片参数,并对其调用lines方法,该方法返回字符串中的行的迭代器。因为这个函数想检查第一行,所以它在迭代器上调用next,从迭代器中获得第一个值。如果text是空字符串,对next的调用将返回None,在这种情况下,我们用? 来停止,并从last_char_of_first_line返回None。如果text不是空字符串,next将返回一个Some值,包含text中第一行的字符串切片。

? 提取了字符串片段,我们可以在该字符串片段上调用chars来获得其字符的迭代器。我们对这第一行的最后一个字符感兴趣,所以我们调用last来返回迭代器中的最后一项。这是一个Option,因为第一行有可能是空字符串,例如,如果text以空行开始,但在其他行有字符,如"\nhi"。然而,如果第一行有最后一个字符,它将在Some变体中被返回。中间的? 操作符给了我们一个简洁的方式来表达这个逻辑,使我们可以在一行中实现这个函数。如果我们不能在 Option 上使用 ? 操作符,我们就必须使用更多的方法调用或match表达式来实现这个逻辑。 

请注意,你可以在一个返回结果的函数中对一个Result使用 ? 操作符,也可以在一个返回 Option 的函数中对一个 Option 使用 ? 操作符,但是你不能混合匹配。运算符不会自动将一个Result转换为一个Option ,反之亦然;在这种情况下,你可以使用像Resultok方法或Option ok_or方法来进行明确的转换。

到目前为止,我们所使用的所有main函数都返回()main函数很特别,因为它是可执行程序的进入和退出点,它的返回类型是有限制的,以使程序的行为符合预期。

幸运的是,mainmain返回一个Result<(), E>。清例12中有例子10的代码,但我们将main的返回类型改为Result<(), Box<dyn Error>>,并在结尾处添加了一个返回值Ok(())。这段代码现在可以编译了。

例12

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let greeting_file = File::open("hello.txt")?;

    Ok(())
}

 Box<dyn Error>类型是一个特质对象,我们将在后面章节的 "使用允许不同类型的值的特质对象 "一节中讨论这个问题。现在,你可以把Box<dyn Error>理解为 "任何类型的错误"。在错误类型为Box<dyn Error>main函数中对Result值使用? 是允许的,因为它允许任何Err值被提前返回。即使这个main函数的主体只返回std::io::Error类型的错误,通过指定Box<dyn Error>,即使有更多返回其他错误的代码被添加到main的主体中,这个签名仍然是正确的。

main函数返回一个Result<(), E>时,如果main返回Ok(()),可执行程序将以0的值退出,如果main返回Err值,则以非零值退出。用C语言编写的可执行文件在退出时返回整数:成功退出的程序返回整数0,出错的程序返回0以外的某个整数。

main函数可以返回任何实现std::process::Termination trait的类型,它包含一个返回ExitCode的函数report。关于为你自己的类型实现Termination特性的更多信息,请查阅标准库文档。

现在我们已经讨论了调用panic!或返回Result的细节,让我们回到如何决定在什么情况下使用哪种方法是合适的话题。

本章重点

  • 带结果的可恢复错误概念和使用
  • 不同错误的匹配
  • 程序死掉的方式:unwrap和expect
  • 传播误差的概念
  • ?操作符的使用
  • ?操作符的使用场景

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

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

相关文章

LearnOpenGL - 如何理解 VAO 与 VBO 之间的关系

系列文章目录 LearnOpenGL 笔记 - 入门 01 OpenGLLearnOpenGL 笔记 - 入门 02 创建窗口LearnOpenGL 笔记 - 入门 03 你好&#xff0c;窗口LearnOpenGL 笔记 - 入门 04 你好&#xff0c;三角形 文章目录系列文章目录1. 前言2. 渲染管线的入口 - 顶点着色器2.1 顶点着色器处理过…

Day891.一主多从的切换正确性 -MySQL实战

一主多从的切换正确性 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于一主多从的切换正确性的内容。 在切换任务的时候&#xff0c;要先主动跳过这些错误&#xff0c;通过主动跳过一个事务或者直接设置跳过指定的错误&#xff0c;用GTID解决找同步位点的问题 大多…

oracle查找各PDB密码过期账户

连接oracle的时候&#xff0c;又报 ORA-12516: TNS: 监听程序找不到符合协议堆栈要求的可用处理程序 的错误。这种现象之前遇到不少&#xff0c;猜测可能又是某个sde账号密码过期或快过期&#xff0c;导致arcgis不停地连数据库&#xff0c;因而连接耗尽了。详见拙作&#xff1a…

电脑c盘满了怎么清理,c盘空间清理

电脑c盘满了怎么清理&#xff1f;电脑C盘满了可能是因为您的操作系统、程序文件、下载文件、临时文件、垃圾文件等占用了太多的存储空间。所以&#xff0c;我们就需要进行一些操作和清理。 一.清理电脑C盘的方法 清理临时文件和垃圾文件。在Windows上&#xff0c;您可以使用系…

windows10 安装DOSbox_32 debug.exe

windows10 安装DOSbox_32 debug.exe1.下载2. 安装DOSBox0.74-3-win32-installer.exe3. 配置DOSBox3. 启动DOSBox.exe4. 测试执行debug命令1.下载 DOSBox0.74-3-win32-installer.exe安装包debug.exe 2. 安装DOSBox0.74-3-win32-installer.exe 解压 双击DOSBox0.74-3-win32-insta…

QT+OPenGL模型加载 - Assimp

QTOPenGL模型加载 - Assimp 本篇完整工程见gitee:QtOpenGL 对应点的tag&#xff0c;由turbolove提供技术支持&#xff0c;您可以关注博主或者私信博主 模型加载 先来张图&#xff1a; 我们不大可能手工定义房子、汽车或者人形角色这种复杂形状所有的顶点、法线和纹理坐标。我…

【surfaceflinger源码分析】surface与surfaceflinger之间的关系

本篇文章带着以下问题继续分析surfaceflinger的源码: 什么是surface ? surface与图形数据之间是什么关系&#xff1f;surface和surfaceflinger之间是什么关系&#xff1f; Surface定义 先看看Surface这个类的定义&#xff0c;主要是定义了很多与GraphicBuffer相关的操作。 …

k8s(存储)数据卷与数据持久卷

为什么需要数据卷&#xff1f; 容器中的文件在磁盘上是临时存放的&#xff0c;这给容器中运行比较重要的应用程序带来一些问题问题1&#xff1a;当容器升级或者崩溃时&#xff0c;kubelet会重建容器&#xff0c;容器内文件会丢失问题2&#xff1a;一个Pod中运行多个容器并需要共…

创邻科技荣获人行旗下《金融电子化》年度大奖

近日&#xff0c;创邻科技收到由中国人民银行旗下《金融电子化》杂志社寄来的奖牌。 在《金融电子化》杂志社主办的第十三届金融科技应用创新奖中&#xff0c;创邻科技凭借“原生分布式图数据库Galaxybase解决方案”&#xff0c;从近400个参报案例中脱颖而出&#xff0c;荣获“…

Linux下zabbix_proxy实施部署

简介 zabbix proxy 可以代替 zabbix server 收集性能和可用性数据,然后把数据汇报给 zabbix server,并且在一定程度上分担了zabbix server 的压力. zabbix-agent可以指向多个proxy或者server zabbix-proxy不能指向多个server zabbix proxy 使用场景: 1&#xff0c;监控远程区…

【React全家桶】reac组件通信

&#x1f39e;️&#x1f39e;️&#x1f39e;️ 博主主页&#xff1a; 糖 &#xff0d;O&#xff0d; &#x1f449;&#x1f449;&#x1f449; react专栏&#xff1a;react全家桶 &#x1f339;&#x1f339;&#x1f339;希望各位博主多多支持&#xff01;&#xff01;&a…

自媒体市场规模由2015年的296亿元增涨至2021年的2500亿元

自媒体&#xff0c;又称“个人媒体”&#xff0c;是指大众化、自主化的传播者以图文、音频或视频内容等各类形式向公众发布信息内容。随着&#xff15;&#xff27;时代的来临和智能设备的性能逐渐提高&#xff0c;网络基础环境得到很大的提升&#xff0c;自媒体开始了新的发展…

QT的下载和安装

这里介绍的是QT官方方式下载&#xff0c;每次都让我很糊涂&#xff0c;就记载一下。先是下载QT online installerhttps://www.qt.io/download 在下方有Go Open Sourcehttps://www.qt.io/download-open-source 在下方有Download the Qt Online installerhttps://www.qt.io/downl…

C#(NET Core3.1 MVC)生成站点地图(sitemap.xml)

要做SEO的肯定绕不开站点地图sitemap.xml。这玩意其实不难我也在搞写下来备忘一下也给新人指指路。 我先把代码放出来备忘下 #region CreateSiteMapXml/// <summary>/// /// </summary>/// <returns></returns>[Route("/art/CreateSiteMapXml&qu…

Windows下安装启动nginx.exe报错

Windows下安装启动nginx.exe报错 前言&#xff1a; 问题1&#xff1a; 在安装使用nginx服务器时遇到最大的问题是windows下命令行输入start nginx后&#xff0c;或者双击nginx.exe&#xff0c;一闪而过&#xff0c;启动不了&#xff0c;怀疑是以下几个方面的问题&#xff0c;…

高效率工作之关于进入心流的方法

本文是向大家介绍高效率工作体验——心流&#xff01;心流也叫最优体验&#xff0c;是一位叫米哈里的心理学家在调查研究的基础上提出的概念。心流指的是一种将大脑注意力毫不费力地集中起来的状态&#xff0c;这种状态使人忘记时间的概念&#xff0c;忘掉自我&#xff0c;也忘…

商家必读!超店有数分享,tiktok达人营销变现如何更快一步?

近几年来&#xff0c;“粉丝经济”发展越来越迅猛&#xff0c;“网红带货”已经成为了一种营销的方式。这种方式让商家能基于达人的影响下迅速抢占自己的私域流量池。消费者会基于对达人的信任&#xff0c;购买达人推荐的产品。达人效应可以助力品牌走出营销困境。如果商家想要…

cmake 引入第三方库(头文件目录、库目录、库文件)

程序的编写需要用到头文件&#xff0c;程序的编译需要lib文件&#xff0c;程序的运行需要dll文件&#xff0c;因此cmake引入第三方库其实就是将include目录、lib目录、bin目录引入工程。 目录 1、find_package&#xff08;批量引入库文件和头文件&#xff09; 2、include_dir…

什么是禅道?禅道可以做什么?如何自动推送禅道消息?

什么是禅道&#xff1f;禅道是一款国产的开源项目管理软件。它的核心管理思想基于敏捷方法 scrum&#xff0c;内置了产品管理和项目管理&#xff0c;同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能&#xff0c;在一个软件中就可以将软件…

北京地铁口免费的大白鹅,你领了吗?

最近这几天&#xff0c;北京很多个地铁口周围涌现了许多只又白又肥的大鹅。 不过&#xff0c;此大鹅非铁锅之中的炖大鹅&#xff0c;乃是又呆又萌的大鹅玩偶。 而且&#xff0c;还是免费送的&#xff01; 所以每天晚上的下班时间&#xff0c;在一群掐着细长的大鹅脖子的地推人…