Rust学习-构建命令行程序

Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择
本文以实现一个minigrep为例,展开对之前学习的回归

初版

接收命令行参数并打印文件内容

// 当所需函数嵌套了多于一层模块时,通常将父模块引入作用域
// std::env::args 在其任何参数包含无效 Unicode 字符时会 panic
// 如果需要接受包含无效 Unicode 字符的参数,使用 std::env::args_os
// 它返回 OsString 值,且OsString 值每个平台都不一样
use std::env;
use std::fs;fn main() {// env::args()返回一个传递给程序的命令行参数的 迭代器(iterator)let args: Vec<String> = env::args().collect();// 程序的名称占据了 vector 的第一个值 args[0],和C的命令行参数相同let query = &args[1];let filename = &args[2];println!("Searching for {}", query);println!("In file {}", filename);let contents = fs::read_to_string(filename).expect("Something went wrong reading the file");println!("With text:\n{}", contents);
}

问题

(1)main 进行了两个任务,函数功能不单一
(2)query 和 filename 是程序中的配置变量,和代码中其他变量混到一起
(3)打开文件失败使用 expect 来打印出错误信息,没有得到失败原因
(4)使用 expect 来处理不同的错误,如果用户没有指定足够的参数来运行程序,则展示的错误依旧无法让使用者阅读

解决方式-关注分离

main的职责:
(1)使用参数值调用命令行解析逻辑
(2)设置任何其他的配置
(3)调用 lib.rs 中的 run 函数
(4)如果 run 返回错误,则处理这个错误

main.rs 处理程序运行
lib.rs 处理所有的真正的任务逻辑
因为不能直接测试 main 函数,这个结构通过将所有的程序逻辑移动到 lib.rs 的函数中使得可以测试他们

重构

重构参数读取

方案一

use std::env;
use std::fs;fn main() {let args: Vec<String> = env::args().collect();let config = parse_config(&args);println!("Searching for {}", config.query);println!("In file {}", config.filename);let contents = fs::read_to_string(config.filename).expect("Something went wrong reading the file");println!("With text:\n{}", contents);
}struct Config {query: String,filename: String,
}// 定义 Config 来包含拥有所有权的 String 值
// main 中的 args 变量是参数值的所有者并只允许 parse_config 函数借用他们
// 意味着如果 Config 尝试获取 args 中值的所有权将违反 Rust 的借用规则
fn parse_config(args: &[String]) -> Config {// 由于其运行时消耗,尽量避免使用 clone 来解决所有权问题let query = args[1].clone();let filename = args[2].clone();Config { query, filename }
}

更合理的参数读取

use std::env;
use std::fs;fn main() {// args类型是:alloc::vec::Vec<alloc::string::String>let args: Vec<String> = env::args().collect();let config = Config::new(&args);println!("Searching for {}", config.query);println!("In file {}", config.filename);let contents = fs::read_to_string(config.filename).expect("Something went wrong reading the file");println!("With text:\n{}", contents);
}struct Config {query: String,filename: String,
}impl Config {// args类型是 &[alloc::string::String]// 使用这个方式也可以:fn new(args: &Vec<String>) -> Config// 此时argos类型为:&alloc::vec::Vec<alloc::string::String>// 这种转换有待后续深挖fn new(args: &[String]) -> Config {let query = args[1].clone();let filename = args[2].clone();Config { query, filename }}
}

改善错误信息

执行 cargo run test,直接panic

thread 'main' panicked at 'index out of bounds: the len is 2 but the index is 2', src/main.rs:31:24
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

糟糕的方案一

fn new(args: &[String]) -> Config {if args.len() < 3 {panic!("not enough arguments"); // 不友好}......// panic信息如下
// thread 'main' panicked at 'not enough arguments', src/main.rs:26:13
}

方案二

返回一个 Result
成功时带有一个 Config 实例
出错时带有一个 &'static str:字符串字面量

use std::env;
use std::fs;
use std::process;struct Config {query: String,filename: String,
}impl Config {fn new(args: &[String]) -> Result<Config, &'static str> {if args.len() < 3 {return Err("not enough arguments");}let query = args[1].clone();let filename = args[2].clone();Ok(Config { query, filename })}
}fn main() {let args: Vec<String> = env::args().collect();// unwrap_or_else 定义于标准库的 Result<T, E> // 使用它可以进行一些自定义的非 panic! 的错误处理// 当其值是 Err 时,该方法会调用一个 闭包(closure),即匿名函数// unwrap_or_else 会将 Err 的内部值,即静态字符串传递给|err|let config = Config::new(&args).unwrap_or_else(|err| {println!("Problem parsing arguments: {}", err);// process::exit 会立即停止程序并将传递给它的数字作为退出状态码process::exit(1);});println!("Searching for {}", config.query);println!("In file {}", config.filename);let contents = fs::read_to_string(config.filename).expect("Something went wrong reading the file");println!("With text:\n{}", contents);
}// 打印信息如下
// Problem parsing arguments: not enough arguments

main 函数处理 new 函数返回的 Result 值
并在出现错误的情况更明确的结束进程

精简main

main的修改

fn main() {// --snip--println!("Searching for {}", config.query);println!("In file {}", config.filename);// 其他处理逻辑全部放入run函数// if let 来检查 run 是否返回一个 Err 值// run 并不返回像 Config::new 返回的 Config 实例那样需要 unwrap 的值// 因为 run 在成功时返回 ()// 而只关心检测错误,所以并不需要 unwrap_or_else 来返回未封装的值// 因为它只会是 ()if let Err(e) = run(config) {println!("Application error: {}", e);process::exit(1);}
}

run的处理

// 引入 trait 对象 Box<dyn Error>的路径
use std::error::Error;
// --snip--// unit 类型 ():作为 Ok 时的返回值类型
// trait 对象 Box<dyn Error>:
// 返回实现了 Error trait 的类型,无需指定具体将会返回的值的类型
// 因为在不同的错误场景可能有不同类型的错误返回值
fn run(config: Config) -> Result<(), Box<dyn Error>> {// 不同于遇到错误就 panic!// ? 会从函数中返回错误值并让调用者来处理它let contents = fs::read_to_string(config.filename)?;println!("With text:\n{}", contents);// 调用 run 函数只是为了它的副作用;函数并没有返回什么有意义的值Ok(())
}

拆模块

拥有可以测试的公有 API 的库 crate
逻辑提取到了 src/lib.rs
所有的参数解析和错误处理留在了 src/main.rs

直接使用多种参数调用函数并检查返回值,无需从命令行运行二进制文件

// src/lib.rs
use std::error::Error;
use std::fs;pub struct Config {pub query: String,pub filename: String,
}impl Config {pub fn new(args: &[String]) -> Result<Config, &'static str> {if args.len() < 3 {return Err("not enough arguments");}let query = args[1].clone();let filename = args[2].clone();Ok(Config { query, filename })}
}pub fn run(config: Config) -> Result<(), Box<dyn Error>> {println!("Searching for {}", config.query);println!("In file {}", config.filename);// 不同于遇到错误就 panic!// ? 会从函数中返回错误值并让调用者来处理它let contents = fs::read_to_string(config.filename)?;println!("With text:\n{}", contents);// 调用 run 函数只是为了它的副作用;函数并没有返回什么有意义的值Ok(())
}
// main.rs
use std::env;
use std::process;use rust_minigrep::Config;fn main() {let args: Vec<String> = env::args().collect();// unwrap_or_else 定义于标准库的 Result<T, E>// 使用它可以进行一些自定义的非 panic! 的错误处理// 当其值是 Err 时,该方法会调用一个 闭包(closure),即匿名函数// unwrap_or_else 会将 Err 的内部值,即静态字符串传递给|err|let config = Config::new(&args).unwrap_or_else(|err| {println!("Problem parsing arguments: {}", err);// process::exit 会立即停止程序并将传递给它的数字作为退出状态码process::exit(1);});if let Err(e) = rust_minigrep::run(config) {println!("Application error: {}", e);process::exit(1);}
}

TDD开发搜索功能

测试驱动开发(Test Driven Development, TDD)

use std::error::Error;
use std::fs;pub struct Config {pub query: String,pub filename: String,
}impl Config {pub fn new(args: &[String]) -> Result<Config, &'static str> {if args.len() < 3 {return Err("not enough arguments");}let query = args[1].clone();let filename = args[2].clone();Ok(Config { query, filename })}
}pub fn run(config: Config) -> Result<(), Box<dyn Error>> {println!("Searching for {}", config.query);println!("In file {}", config.filename);// 不同于遇到错误就 panic!// ? 会从函数中返回错误值并让调用者来处理它let contents = fs::read_to_string(config.filename)?;println!("With text:\n{}", contents);// 调用 run 函数只是为了它的副作用;函数并没有返回什么有意义的值Ok(())
}// 在 search 的签名中定义一个显式生命周期 'a 并用于 contents 参数和返回值
// 告诉 Rust 函数 search 返回的数据将与 search 函数中的参数 contents 的数据存在的一样久
// 为了使这个引用有效那么 被 slice 引用的数据也需要保持有效
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {vec![]
}// 如下则编译失败
// Rust 不可能知道需要的是哪一个参数,所以需要明确告诉它
// 参数 contents 包含了所有的文本而且希望返回匹配的那部分文本,所以contents 应该要使用生命周期语法来与返回值相关联的参数
// 其他语言中并不需要你在函数签名中将参数与返回值相关联
// pub fn search(query: &str, contents: &str) -> Vec<&str> {// 先编写测试用例
#[cfg(test)]
mod tests {use super::*;#[test]fn one_result() {let query = "duct";let contents = "\
Rust:
safe, fast, productive.
Pick three.";assert_eq!(vec!["safe, fast, productive."],search(query, contents));}
}

上述铁定测试失败,因为没有开发search模块,所以search的功能如下:
(1)遍历内容的每一行文本
(2)查看这一行是否包含要搜索的字符串
(3)如果有,将这一行加入列表返回值中
(4)如果没有,什么也不做。
(5)返回匹配到的结果列表

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {let mut results = Vec::new();// contents.lines 返回一个迭代器for line in contents.lines() {// 字符串的contains方法检查包含操作if line.contains(query) {results.push(line);}}results
}

最终lib.rs内容如下

use std::error::Error;
use std::fs;pub struct Config {pub query: String,pub filename: String,
}impl Config {pub fn new(args: &[String]) -> Result<Config, &'static str> {if args.len() < 3 {return Err("not enough arguments");}let query = args[1].clone();let filename = args[2].clone();Ok(Config { query, filename })}
}pub fn run(config: Config) -> Result<(), Box<dyn Error>> {println!("Searching for {}", config.query);println!("In file {}", config.filename);// 不同于遇到错误就 panic!// ? 会从函数中返回错误值并让调用者来处理它let contents = fs::read_to_string(config.filename)?;println!("With text:\n{}", contents);for line in search(&config.query, &contents) {println!("{}", line);}// 调用 run 函数只是为了它的副作用;函数并没有返回什么有意义的值Ok(())
}pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {let mut results = Vec::new();for line in contents.lines() {if line.contains(query) {results.push(line);}}results
}#[cfg(test)]
mod tests {use super::*;#[test]fn one_result() {let query = "duct";let contents = "\
Rust:
safe, fast, productive.
Pick three.";assert_eq!(vec!["safe, fast, productive."],search(query, contents));}
}

错误信息打印到标准输出流

目前为止将所有的输出都 println! 到了终端
大部分终端都提供了两种输出:
标准输出(standard output,stdout)对应一般信息
标准错误(standard error,stderr)则用于错误信息

cargo run > output.txt
shell将所有信息存储到 output.txt
结果output.txt中存储了错误信息

将错误打印到标准错误

// 标准库提供了 eprintln! 宏来打印到标准错误流eprintln!("Problem parsing arguments: {}", err);

添加区分大小写的功能

设置环境变量来设置搜索是否是大小写敏感

vi ~/.zshrc
添加如下一行
export RUST_CASE_INSENSITIVE=1env
查看是否设置成功
RUST_CASE_INSENSITIVE=1
use std::error::Error;
use std::fs;
use std::env;pub struct Config {pub query: String,pub filename: String,pub case_sensitive: bool, // 新增加控制字段
}impl Config {pub fn new(args: &[String]) -> Result<Config, &'static str> {if args.len() < 3 {return Err("not enough arguments");}let query = args[1].clone();let filename = args[2].clone();// 处理环境变量的函数位于标准库的 env 模块中let case_sensitive = env::var("RUST_CASE_INSENSITIVE").is_err();Ok(Config { query, filename, case_sensitive })}
}pub fn run(config: Config) -> Result<(), Box<dyn Error>> {let contents = fs::read_to_string(config.filename)?;let results = if config.case_sensitive {search(&config.query, &contents)} else {search_case_insensitive(&config.query, &contents)};for line in results {println!("{}", line);}Ok(())
}pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {let query = query.to_lowercase();let mut results = Vec::new();for line in contents.lines() {if line.to_lowercase().contains(&query) {results.push(line);}}results
}pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {let mut results = Vec::new();for line in contents.lines() {if line.contains(query) {results.push(line);}}results
}#[cfg(test)]
mod tests {use super::*;#[test]fn case_sensitive() {let query = "duct";let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";assert_eq!(vec!["safe, fast, productive."],search(query, contents));}#[test]fn case_insensitive() {let query = "rUsT";let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";assert_eq!(vec!["Rust:", "Trust me."],search_case_insensitive(query, contents));}
}

使用迭代器重构

// main.rs
use std::env;
use std::process;use rust_minigrep::Config;fn main() {// unwrap_or_else 定义于标准库的 Result<T, E>// 使用它可以进行一些自定义的非 panic! 的错误处理// 当其值是 Err 时,该方法会调用一个 闭包(closure),即匿名函数// unwrap_or_else 会将 Err 的内部值,即静态字符串传递给|err|let config = Config::new(env::args()).unwrap_or_else(|err| {println!("Problem parsing arguments: {}", err);// process::exit 会立即停止程序并将传递给它的数字作为退出状态码process::exit(1);});if let Err(e) = rust_minigrep::run(config) {println!("Application error: {}", e);process::exit(1);}
}
// lib.rsuse std::error::Error;
use std::fs;
use std::env;pub struct Config {pub query: String,pub filename: String,pub case_sensitive: bool, // 新增加控制字段
}impl Config {pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {args.next();let query = match args.next() {Some(arg) => arg,None => return Err("Didn't get a query string"),};let filename = match args.next() {Some(arg) => arg,None => return Err("Didn't get a file name"),};let case_sensitive = env::var("RUST_CASE_INSENSITIVE").is_err();println!("query={}", query);println!("filename={}", filename);println!("case_sensitive={}", case_sensitive);Ok(Config { query, filename, case_sensitive })}
}pub fn run(config: Config) -> Result<(), Box<dyn Error>> {let contents = fs::read_to_string(config.filename)?;let results = if config.case_sensitive {println!("run case senstive");search(&config.query, &contents)} else {println!("run case insenstive");search_case_insensitive(&config.query, &contents)};for line in results {println!("搜索结果: {}", line);}Ok(())
}pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {let query = query.to_lowercase();let mut results = Vec::new();for line in contents.lines() {if line.to_lowercase().contains(&query) {results.push(line);}}results
}pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {// 字符串数组的迭代器contents.lines()// 留下true的,去掉false的.filter(|line| line.contains(query))// 收集结果.collect()
}#[cfg(test)]
mod tests {use super::*;#[test]fn case_sensitive() {let query = "duct";let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";assert_eq!(vec!["safe, fast, productive."],search(query, contents));}#[test]fn case_insensitive() {let query = "rUsT";let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";assert_eq!(vec!["Rust:", "Trust me."],search_case_insensitive(query, contents));}
}

附录

打印类型

fn print_type_of<T>(_: &T) {println!("{}", std::any::type_name::<T>())
}

环境变量的设置

vi ~/.bash_profile
vi ~/.zshrc需要设置哪个?
echo $SHELL系统安装了哪些shell
cat /etc/shells

mac 中使用 zsh,部分因为 oh-my-zsh 配置集,兼容 bash,还能自动补全。
sh 是 unix 上的标准 shell,很多 unix 版本都配有它
bash由 gnu 组织开发,保持了对 sh shell 的兼容性,是各种 Linux 发行版默认配置的 shell。

bash 兼容 sh:
针对 sh 编写的 shell 代码可以不加修改地在 bash 中运行

bash 和 sh 不同:
bash 扩展了一些命令和参数
bash 并不完全和 sh 兼容,它们有些行为并不一致

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

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

相关文章

[JVM] 2. 类加载子系统(1)-- 内存结构、类加载子系统概述

一、内存结构 类加载子系统的职责是&#xff1a;加载class文件到内存中。 完整的内存结构如下&#xff1a; 二、类加载过程 类加载过程总体分为Loading&#xff08;加载&#xff09;、Linking&#xff08;链接&#xff09;、Initialization&#xff08;初始化&#xff09;三…

Mars3d采用ellipsoid球实现模拟地球旋转效果

1.Mars3d采用ellipsoid球实现模拟地球旋转效果 2.开始自选装之后&#xff0c;模型一直闪烁 http://mars3d.cn/editor-vue.html?idgraphic/entity/ellipsoid 3.相关代码&#xff1a; import * as mars3d from "mars3d"export let map // mars3d.Map三维地图对象 …

深入浅出如何通过API瞬间搭建亿万商品外贸代购系统PHP系统

什么是淘宝代购 淘宝代购是近年兴起的一种购物模式&#xff0c;是帮国外客户购买中国商品。主要是通过万邦 科技的外贸代购系统&#xff0c;把淘宝、天猫等电商平台的全站商品通过API 接入到你的网站 上&#xff0c;瞬间就可以架设一个有数亿产品的大型网上商城&#xff0c;而…

2023年测试之路,从功能测试进阶测试开发工程师,突破内卷...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 测试开发工程师到…

Redis可视化工具(Redis Desktop Manager)

redis是我们平时开发工作中经常用到的非关系型数据库&#xff0c;常用于做数据缓存&#xff0c;分布式锁等。 为了更方便的使用redi&#xff0c;这里给大家推荐一款可视化工具&#xff1a;Redis Desktop Manager。 1.下载与安装 直接到gihub下载&#xff0c;地址 Release 0.…

剑指 Offer 59 - I. 滑动窗口的最大值

题目介绍 给定一个数组 nums 和滑动窗口的大小 k&#xff0c;请找出所有滑动窗口里的最大值。 示例: 输入: nums [1,3,-1,-3,5,3,6,7], 和 k 3 输出: [3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 …

uni-app的H5版本下载跨域问题

前端能正常访问图片&#xff0c;但无法下载 因为路径不经过业务代码&#xff0c;所以需要在nginx配置跨域 代码&#xff1a; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods GET, POST, OPTIONS; add_header Access-Control-Allow-H…

基于simulink的DPLL仿真笔记

该笔记主要用于本人思路整理与记录 本设计运用的是电荷泵一阶环路滤波器&#xff0c;二阶三阶则在此基础上举一反三&#xff0c;以后如有机会会慢慢补全 文章目录 一.仿真模型PS&#xff08;题外话&#xff09; 二.仿真结果三.环路滤波器分析1. 环路滤波器对比LPF2. 环路滤波器…

Shikra:新一代多模态大语言模型,理解指向,说出坐标

“ Shikra&#xff1a;解锁多模态语言模型参考对话的魔法” Shikra和用户的对话案例 在人类的日常交流中&#xff0c;经常会关注场景中的不同区域或物体&#xff0c;双方都可以通过说话并指向这些区域来进行高效的信息交换。我们将这种对话模式称为参考对话&#xff08;Referen…

等保协议概要

一、等级划分 《信息安全等级保护管理办法》将信息系统的安全保护等级分为以下五级: 第一级,信息系统受到破坏后,会对公民、法人和其他组织的合法权益造成损害,但不损害国家安全、社会秩序和公共利益。 第一级信息系统运营、使用单位应当依据国家有关管理规范和技术标准进…

uniapp自定义头部,计算状态栏和导航栏高度超简单三步

效果图 1.pages.json 页面给要自定义头部的页面加入一行代码 "navigationStyle":"custom" {"path": "pages/index/index","style": {"navigationBarTitleText": "","navigationStyle":"…

Gin+Gorm练手小项目bubble清单企业级结构剖析

概述 本项目来源于Qimi老师的小清单项目——基于gingorm开发的练手小项目&#xff0c;通过该项目可初识go web开发该有的姿势。笔者对代码有些许修改&#xff0c;以下是项目成功运行的截图&#xff0c;主要功能有添加&#xff0c;删除&#xff0c;确认&#xff0c;查看待办事项…

05 信号与槽机制

信号&#xff08;Signal&#xff09;&#xff1a; 信号的本质是事件&#xff0c;例如鼠标点击&#xff0c;窗口刷新&#xff0c;键盘输入等 槽&#xff08;Slot&#xff09;&#xff1a; 槽的本质是信号对应的函数&#xff0c;可以有参&#xff0c;可以发生重载 信号与槽机制…

有哪些记事本app可以用来整理个人笔记?

我总是在思考一个问题&#xff0c;为什么现在越来越多的人选择使用记事本app&#xff1f;它们相比传统笔记本&#xff0c;又有什么吸引人的地方呢&#xff1f;这其实并不难理解。因为&#xff0c;记事本app不仅可以让我们及时记录重要信息&#xff0c;还能对这些信息进行系统、…

Django实现接口自动化平台(九)环境envs序列化器及视图【持续更新中】

相关文章&#xff1a; Django实现接口自动化平台&#xff08;八&#xff09;测试报告reports序列化器及视图【持续更新中】_做测试的喵酱的博客-CSDN博客 本章是项目的一个分解&#xff0c;查看本章内容时&#xff0c;要结合整体项目代码来看&#xff1a; python django vue …

mac 下 geoserver 安装

一、去官网下载geoserver https://geoserver.org/ 选择一个版本&#xff0c;然后点进去 二、需要配置java环境和设置geoserver 环境变量 1&#xff09;、java 环境安装 Java Downloads | Oracle 中国 2&#xff09;、环境变量设置 1.打开终端&#xff1a;command 空格键 2…

Python自动获取字母站视频

如果有疑问的话可以在我的谈论群&#xff1a;706128290 来找我 目录 前言 二、编写代码 1.引入库 2.编写主类 3. 自动获取cookies值和生成headers 4.获取命令行参数 运行效果 前言 browser_cookie3 第三方模块 browser_cookie3是browser_cookie模块的分支&#xff0c;…

jupyter notebook更换虚拟环境(内核)

jupyter notebook更换虚拟环境&#xff08;内核&#xff09; 创建一个新的虚拟环境 # stk_env 虚拟环境的名字&#xff0c;任取。 conda create -n stkenv python3.9激活虚拟环境 conda activate stkenv安装ipykernel # 为该虚拟环境&#xff0c;安装内核。 conda install -c a…

基于C语言设计的足球信息查询系统

完整资料进入【数字空间】查看——baidu搜索"writebug" 需求分析与概要设计 2.1 项目说明 我们小组的选题主要是面向足球爱好者&#xff0c;在普通社交软件的基础之上&#xff0c;围绕足球的主题展开设计&#xff0c;以便于他们能够更好的交流相关的话题&#xff…

LangChain(2)提示工程 Prompt Engineering

提示一般包含如下部分&#xff1a; Instructions&#xff1a;整体结构&#xff0c;模型的人设 Instructions tell the model what to do, how to use external information if provided, what to do with the query, and how to construct the output. External information&a…