syn與quote的使用——結構體轉create語句

前言

syn和quote的簡單使用——生成結構體-CSDN博客https://blog.csdn.net/qq_63401240/article/details/150609865?spm=1001.2014.3001.5501

前面使用syn和quote,發現挺好玩的,感覺可以干很多事情,不愧是Rust中的宏。

宏分為聲明宏和過程宏,過程宏又分為函數宏、派生宏、屬性宏。

前面build_struct這個宏是過程宏,或者說更詳細點是函數宏

這篇就來使用另一個過程宏——派生宏,

Macro 宏編程 - Rust語言圣經(Rust Course)https://course.rs/advance/macro.html

需求

一個結構體變成mysql的create的語句。

比如

#[derive(Create)]
#[table_name="students"]
struct Student{#[field(pk)]id:i32,#[field(length=40,null=false)]name:String,#[field(null=false)]score: f32,#[field(null=false,default=true)]is_job:bool,
}

生成的sql語句應該是

create table students (id int auto_increment primary key,name varchar(40) not null, score float not null, is_job tinyint(1) not null default true
)
  1. 如果沒有設置表的名字,就用結構體的名字當表名。
  2. 如果沒有主鍵,就在自定義一個主鍵=結構體的名字+_id。
  3. 需要一個實現一個打印sql語句的方法

還有其他復雜的情況,不考慮,就這樣。

正文

一些簡單地介紹

定義派生宏——proc_macro_derive,簡單來說,定義Create這個派生宏的代碼如下

#[proc_macro_derive(Create, attributes(table_name, field))]
pub fn create(input: TokenStream) -> TokenStream {
}

定義派生宏使用proc_macro_derive這個屬性(Attribute)。

Create是定義的派生宏的名字。

table_name和field是派生宏Create額外識別的屬性名,

    #[field(null=false)]score: f32,

這里null=false這個整體可以稱為元數據Meta,當然元數據的定義如下

    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub enum Meta {Path(Path),/// A structured list within an attribute, like `derive(Copy, Clone)`.List(MetaList),/// A name-value pair within an attribute, like `feature = "nightly"`.NameValue(MetaNameValue),}

關于元數據有三種類型Path、List、NameValue

這三者不是獨立的,往往一起出現,比如

對于#[field(pk)],就是List+Path;

? ? #[field(null=false)]是List+NameValue。

? ? #[field]是Path

總之

沒有括號,是Path。

有括號,是List。

有等號是NameValue。

思考

整個問題其實還是很復雜的,大致問題如下:

  1. ?如何獲取結構體的名字?
  2. 如何獲取結構體字段的名字和類型?
  3. 如何把Rust類型映射到sql的數據類型?
  4. 如何解析一個字段中的全部元數據?
  5. 如何拼接sql語句?

完成這5步感覺就差不多了,整個過程其實也像玩積木游戲,先拆開再拼回去。

獲取結構體的名字

首先,傳進來的是TokenStream這個類型,需要進行類型裝換。轉化成能在派生宏中處理的數據,

代碼如下

    let input = parse_macro_input!(input as DeriveInput);

此時,這個input就變成DeriveInput類型了

看看DeriveInput 的定義

#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]pub struct DeriveInput {pub attrs: Vec<Attribute>,pub vis: Visibility,pub ident: Ident,pub generics: Generics,pub data: Data,}

發現,有5個字段,意思是顯然的,

1. pub attrs: Vec<Attribute> :包含所有附加在該類型上的屬性。

2. pub vis: Visibility :類型的可見性(visibility)。

3. pub ident: Ident 類型的標識符(名稱)。

4.?pub generics: Generics 泛型參數信息

5 pub data: Data 類型的具體數據內容(主體部分)

顯然,屬性就是指前面提到的table_name,field這兩個。

因此,獲取結構體的名字

    let name = input.ident;

獲取表的名字

不妨寫一個函數

fn get_table_name(input: &DeriveInput) -> String {}

把前面的input傳進來。

需要獲取table_name這個屬性,結合DeriveInput結構體的定義,大致流程如下

  1. 屬性肯定是在attrs中。
  2. attrs返回一個Vec<Attribute>,獲取迭代器,遍歷其中的屬性。
  3. 尋找到table_name這個屬性。
  4. 獲取屬性所對應的值,返回。
  5. 沒找到則使用結構體名字。

因此,部分代碼如下

fn get_tablename(input: &DeriveInput) -> String {input.attrs.iter().find(|attr| attr.path().is_ident("table_name")).and_then(|attr| {}).unwrap_or_else(|| input.ident.to_string().to_lowercase())
}

現在找到了table_name這個屬性,如何獲取對應的值?

考慮到attr的類型是Attribute,定義如下

    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub struct Attribute {pub pound_token: Token![#],pub style: AttrStyle,pub bracket_token: token::Bracket,pub meta: Meta,}

顯然,要找到table_name對應的值"students"需要在meta中尋找,結合前面Meta的定義,

因此,在and_then閉包中的代碼如下

 if let Meta::NameValue(meta) = &attr.meta {}

這一步是對元數據的判斷。

為什么是Meta::NameValue,因為是table_name="student"。

進一步操作,獲取"studnet",同時考慮到前面Meta::NameValue以及其中MetaNameValue的定

義,如下

    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub struct MetaNameValue {pub path: Path,pub eq_token: Token![=],pub value: Expr,}

因此,在and_then閉包中的進一步代碼如下

if let syn::Expr::Lit(expr_lit) = &meta.value {}

這一步是對字面量表達式的判斷,判斷"student"是不是字面量表達式

同理,下一步就是對字符串切片的判斷,

    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub struct ExprLit {pub attrs: Vec<Attribute>,pub lit: Lit,}

if let Lit::Str(lit_str) = &expr_lit.lit {}

三個if,如果都成功,判斷是字符串切片。

因此,返回數據。

因為是在and_then的閉包函數中

    pub fn and_then<U, F>(self, f: F) -> Option<U>whereF: FnOnce(T) -> Option<U>,

需要返回Option。

因此,返回如下

return Some(lit_str.value());

所以,關于獲取表的名字的全部代碼如下

fn get_table_name(input: &DeriveInput) -> String {input.attrs.iter().find(|attr| attr.path().is_ident("table_name")).and_then(|attr| {if let Meta::NameValue(meta) = &attr.meta {if let syn::Expr::Lit(expr_lit) = &meta.value {if let Lit::Str(lit_str) = &expr_lit.lit {return Some(lit_str.value());}}}None}).unwrap_or_else(|| input.ident.to_string().to_lowercase())
}

一層一層的深入,獲取數據。

獲取全部字段

如何獲取字段,大致操作如下:

  1. 是一個結構體
  2. 有字段的結構體——命名結構體
  3. 獲取字段

就這三步,慢慢來。

判斷是否是結構體,需要使用DeriveInput中的data,因為data是Data類型的,Data的定義如下

    #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]pub enum Data {Struct(DataStruct),Enum(DataEnum),Union(DataUnion),}

因此,代碼如下

    let fields = if let Data::Struct(s) = input.data {} else {panic!("不是結構體");};

現在代碼中的s的類型是DataStruct ,看看定義

    #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]pub struct DataStruct {pub struct_token: Token![struct],pub fields: Fields,pub semi_token: Option<Token![;]>,}

獲取field,因為field是Field類型的,其中Field定義

    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub enum Fields {/// Named fields of a struct or struct variant such as `Point { x: f64,/// y: f64 }`.Named(FieldsNamed),/// Unnamed fields of a tuple struct or tuple variant such as `Some(T)`.Unnamed(FieldsUnnamed),/// Unit struct or unit variant such as `None`.Unit,}

需要有Named,同時,FieldsNamed的定義如下

    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub struct FieldsNamed {pub brace_token: token::Brace,pub named: Punctuated<Field, Token![,]>,}

因此,獲取named,代碼如下

if let Fields::Named(n) = s.fields {n.named} else {panic!("不是命名結構體");}

因此,獲取結構體的全部字段的代碼如下

let fields = if let Data::Struct(s) = input.data {if let Fields::Named(n) = s.fields {n.named} else {panic!("不是命名結構體");}} else {panic!("不是結構體");
};

錯誤處理的不是很好,算了。

此時這個fields就是全部的字段,類型是Punctuated<Field, Token![,]>

Punctuated in syn::punctuated - Rusthttps://docs.rs/syn/latest/syn/punctuated/struct.Punctuated.html

rust類型映射到sql

創建一個函數,用來解析獲得的fields。

fn parse_fields(fields: Punctuated<Field, Comma>){
}

返回什么,先不慌。

考慮到Punctuated實現了IntoIterator 這個trait,可以遍歷。

因此,先對fields進行循環

    for field in fields.into_iter() {}

而field的類型是Field,要獲取類型和名字,很簡單

let field_name = field.ident.unwrap();        
let ty = field.ty;

現在獲取字段中的類型,定義一個函數實現類型映射,代碼如下

pub fn rust_type_to_sql_type(ty: &Type) -> String {
}

關于Type,這是一個enum,里面有各種類型的定義,

Type in syn - Rusthttps://docs.rs/syn/latest/syn/enum.Type.html因為前面定義的結構體Student全是簡單的標識符類型(不是泛型、不是引用、不是數組)。因此,這里就使用?Type::Path。

代碼如下

pub fn rust_type_to_sql_type(ty: &Type) -> String {match ty {Type::Path(type_path) if type_path.path.is_ident("i32") => "int".to_string(),Type::Path(type_path) if type_path.path.is_ident("f32") => "float".to_string(),Type::Path(type_path) if type_path.path.is_ident("String") => "varchar".to_string(),Type::Path(type_path) if type_path.path.is_ident("bool") => "tinyint(1)".to_string(),_ => "".to_string(),}
}

解析元數據前的處理——獲取元數據

解析元數據之前,先要獲取到元數據。

和前面獲取table_name類型的,只是更復雜,總體流程如下。

  1. 從attrs中獲取Vec<Attribute>,然后遍歷,找到所有屬性field,返回Attribute
  2. 判斷和獲取
 for attr in field.attrs.iter().filter(|attr| attr.path().is_ident("field"))}

前面找到table_name,使用的find,因為只有一個,而這個尋找field,使用了filter

因為

  • filter:找出所有符合條件的
  • find:只找第一個符合條件的

所以使用filter。

然后,判斷元數據類型,因為Student這個結構體中的字段的field屬性全是List,因此,第一步代碼如下

if let Meta::List(meta_list) = &attr.meta {}

以name字段為例

    #[field(length=40,null=false)]name:String,

判斷是List,然后要進一步操作,即對length=40,null=false進行操作

因為List這個元數據類型中包含一個字段List(MetaList),考慮到MetaList的定義

    #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub struct MetaList {pub path: Path,pub delimiter: MacroDelimiter,pub tokens: TokenStream,}

要進一步操作,因此,是獲取到tokens,而tokens的類型是TokenStream,這無法操作,需要變成其他類型,因此,需要使用某個類型的解析方法,即parse方法

而且,非常關鍵的是這個TokenStream是proc_macro2::TokenStream;

因為length=40,null=false是逗號分開的

因此

筆者在這里把tokens解析成前面的Punctuated。

當然,還有其他解析方法,筆者不管這么多了,代碼如下

let punctuated = Punctuated::<Meta, Token![,]>::parse_terminated;
if let Ok(nested) = punctuated.parse2(meta_list.tokens.clone()) {}

Punctuated::<Meta, Token![,]> 是定義

parse_terminated 返回解析器,允許最后一個元素后面有分隔符,還有其他方法返回解析器

fn parse2(self, tokens: TokenStream) -> Result<T> ,傳一個proc_macro2::TokenStream,返回Result<T>,這里T就是Punctuated::<Meta, Token![,]>

因此,nested就是一個Punctuated,對于length=40,null=false來說

大致是這樣的

即Meta,逗號,Meata

當然,不是很準確,沒有中括號,意思一下。

總之現在nest是個序列,因此,再次循環一下

for meta in nested {}

序列中的元素的Meta,因此,同理,判斷元數據類型。

 match meta {Meta::Path(path) => {}Meta::NameValue(nv) => {}_ => {}}

因為走到這一步,只有Path和NameValue兩種類型了,

比如對于#[field(pk)],走到這里,就是pk了,類型是Path

對于#[field(null=false)],走到這里,就是`null=false`,類型是NameValue

分別處理就可以了

處理NameValue

不妨定義一個新的函數

pub fn handle_one_nv(nv: MetaNameValue) -> String {}

參數是MetaNameValue,對于這個類型

前面說過,分別獲取name和value,對于null=true來說,name就是null,value就是true。

    let path = nv.path;let value = nv.value;

可以把這個null變成字符串字面量"null",還有對true的判斷

 match path.get_ident().unwrap().to_string().as_str() {"null" => {}_ => "".to_string(),}

判斷也很簡單,先判斷是不是字面量,然后判斷是不是bool。

代碼如下

pub fn handle_one_nv(nv: MetaNameValue) -> String {let path = nv.path;let value = nv.value;match path.get_ident().unwrap().to_string().as_str() {"null" => {if let Expr::Lit(expr_lit) = &value {if let Lit::Bool(lit_bool) = &expr_lit.lit {return if lit_bool.value {"".to_string()} else {" not null".to_string()};}}panic!("null 需要設為bool值,例如: #[null = true]");}_ => "".to_string(),}
}

一步一步進去就可以了,對于其他東西,比如default,length,也是如下,就是判斷。不細說了

生成sql和主鍵的處理

在一次循環中,把每一個字段生成一個sql屬性,包括sql對應的屬性名、類型、約束,變成一個字符串,放進一個vec中

如果沒有主鍵,循環完成后,添加一個主鍵就可以了

最后,把vec使用逗號,將屬性每一個連接起來。

和table_name一起生成create語句

這一步沒什么可說,穿插在前面的代碼中

宏的全部代碼

src/lib.rs文件

mod utils;
use proc_macro::TokenStream;
use quote::quote;
use syn::Lit;
use syn::Meta;
use syn::parse::Parser;
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{Data, Field, Fields, parse_macro_input};
use syn::{DeriveInput, Token};
use utils::{handle_length, handle_one_nv, rust_type_to_sql_type};/// 處理表名
fn get_table_name(input: &DeriveInput) -> String {input.attrs.iter().find(|attr| attr.path().is_ident("table_name")).and_then(|attr| {if let Meta::NameValue(meta) = &attr.meta {if let syn::Expr::Lit(expr_lit) = &meta.value {if let Lit::Str(lit_str) = &expr_lit.lit {return Some(lit_str.value());}}}None}).unwrap_or_else(|| input.ident.to_string().to_lowercase())
}/// 解析字段
fn parse_fields(fields: Punctuated<Field, Comma>, name: String) -> Vec<String> {let mut field_vec = Vec::new();let mut has_pk = false;for field in fields.into_iter() {let field_name = field.ident.unwrap();let ty = field.ty;let mut sql_type = rust_type_to_sql_type(&ty); // 讓 sql_type 可變let mut sql_constraint = String::new();for attr in field.attrs.iter().filter(|attr| attr.path().is_ident("field")){if let Meta::List(meta_list) = &attr.meta {let punctuated = Punctuated::<Meta, Token![,]>::parse_terminated;if let Ok(nested) = punctuated.parse2(meta_list.tokens.clone()) {for meta in nested {match meta {Meta::Path(path) => {if path.is_ident("pk") {has_pk = true;sql_constraint += " auto_increment primary key";}}Meta::NameValue(nv) => {let ident = nv.path.get_ident().unwrap().to_string();if ident == "length" {// length 應該修改類型部分,而不是約束部分let length_value = handle_length(&nv.value);sql_type = format!("{}({})", sql_type, length_value);} else {let res = handle_one_nv(nv);sql_constraint += &res;}}_ => {}}}}}}let field_sql = format!("{} {}{}", field_name, sql_type, sql_constraint);field_vec.push(field_sql);}if !has_pk {let pk_sql = format!("{}_id int auto_increment primary key", name);field_vec.push(pk_sql);}field_vec
}
#[proc_macro_derive(Create, attributes(table_name, field))]
pub fn create(input: TokenStream) -> TokenStream {let input = parse_macro_input!(input as DeriveInput);let name = &input.ident;//獲取表名let table_name = get_table_name(&input);// 獲取結構體的屬性let fields = if let Data::Struct(s) = input.data {if let Fields::Named(n) = s.fields {n.named} else {panic!("不是命名結構體");}} else {panic!("不是結構體");};// sql屬性和約束let word_vec = parse_fields(fields, table_name.clone());// 列let columns = word_vec.join(",\n ");// 拼接let output = quote! {impl #name {pub fn create_table_sql() -> String {format!("create table {} (\n{}\n);",#table_name,#columns)}}};output.into()
}

src/utils.rs文件

use syn::Lit::{Bool, Int};
use syn::MetaNameValue;
use syn::{Expr, Lit};
use syn::Type;pub fn rust_type_to_sql_type(ty: &Type) -> String {match ty {Type::Path(type_path) if type_path.path.is_ident("i32") => "int".to_string(),Type::Path(type_path) if type_path.path.is_ident("f32") => "float".to_string(),Type::Path(type_path) if type_path.path.is_ident("String") => "varchar".to_string(),Type::Path(type_path) if type_path.path.is_ident("bool") => "tinyint".to_string(),_ => "".to_string(),}
}fn handle_default(value: Expr) -> String {if let Expr::Lit(expr_lit) = &value {match &expr_lit.lit {Bool(lit_bool) => {return if lit_bool.value {" default true".to_string()} else {" default false".to_string()};}Lit::Str(lit_str) => {return format!(" default '{}'", lit_str.value());}Int(lit_int) => {return format!(" default {}", lit_int.base10_digits());}Lit::Float(lit_float) => {return format!(" default {}", lit_float.base10_digits());}_ => panic!("default 不支持該類型"),}}panic!("default 需要設為字面量值");
}
pub fn handle_length(value: &Expr) -> String {if let Expr::Lit(expr_lit) = &value {if let Int(lit_int) = &expr_lit.lit {return lit_int.base10_digits().to_string();}}panic!("需要設為整型");
}
pub fn handle_one_nv(nv: MetaNameValue) -> String {let path = nv.path;let value = nv.value;match path.get_ident().unwrap().to_string().as_str() {"null" => {if let Expr::Lit(expr_lit) = &value {if let Bool(lit_bool) = &expr_lit.lit {return if lit_bool.value {"".to_string()} else {" not null".to_string()};}}panic!("null 需要設為bool值,例如: #[null = true]");}"default" => handle_default(value),_ => "".to_string(),}
}

錯誤處理不是很好,不管那些。

簡單測試一下

新建一個binary crate,導入前面宏所在的crate,其中src/main.rs的內容如下

use macro_crate::{Create};
#[derive(Create)]
struct Student{#[field(pk)]id:i32,#[field(length=40,null=false)]name:String,#[field(null=false,default=60.0)]score: f32,#[field(null=false,default=true)]is_job:bool,
}
fn main() {let create=Student::create_table_sql();println!("{}",create);
}

生成sql語句如下

create table student (
id int auto_increment primary key,name varchar(40) not null,score float not null default 60.0,is_job tinyint not null default true
);

運行結果如下

成功,哈哈哈哈哈哈

其它復雜的東西,就不管了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/919943.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/919943.shtml
英文地址,請注明出處:http://en.pswp.cn/news/919943.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

集中式負載均衡 vs. 分布式負載均衡

集中式負載均衡 vs. 分布式負載均衡負載均衡&#xff08;Load Balancing&#xff09;是任何可伸縮系統的“交通警察”。 集中式負載均衡&#xff08;Centralized LB&#xff09;與分布式負載均衡&#xff08;Distributed LB&#xff09;代表了兩種截然不同的“指揮哲學”&#…

【機器學習】9 Generalized linear models and the exponential family

本章目錄 9 Generalized linear models and the exponential family 281 9.1 Introduction 281 9.2 The exponential family 281 9.2.1 Definition 282 9.2.2 Examples 282 9.2.3 Log partition function 284 9.2.4 MLE for the exponential family 286 9.2.5 Bayes for the e…

EndNote 2025 Mac 文獻管理工具

原文地址&#xff1a;EndNote 2025 Mac 文獻管理工具 EndNote mac版一款文獻管理工具&#xff0c;支持國際期刊的參考文獻格式有3776種&#xff0c;寫作模板幾百種&#xff0c;涵蓋各個領域的雜志。 EndNote mac不僅僅局限于投稿論文的寫作&#xff0c;對于研究生畢業論文的寫…

openEuler系統中home文件夾下huawei、HwHiAiUser、lost+found 文件夾的區別和作用

在 openEuler 系統的 /home 目錄下出現的 huawei、HwHiAiUser 和 lost+found 文件夾,分別對應不同的功能和用途,具體區別和作用如下: 1. lost+found 文件夾 通用 Linux 系統文件夾:lost+found 是所有 Linux 系統(包括 openEuler)中默認存在的文件夾,并非 openEuler 特有…

Electron 核心 API 全解析:從基礎到實戰場景

Electron 憑借豐富的 API 體系&#xff0c;讓前端開發者能輕松調用系統級能力。本文將系統梳理 Electron 核心 API 的分類、使用場景及實戰示例&#xff0c;幫你快速掌握從窗口管理到進程通信的全場景開發。 一、主進程核心 API&#xff08;Main Process&#xff09; 主進程是…

創建線程的方式有哪些?

1. 創建線程的方式有哪些?繼承Thread類實現runnable接口實現Callable接口線程池創建線程(項目中使用方式)2. runnable 和 callable 有什么區別?Runnable接口run方法沒有返回值Callable接口call方法有返回值,需要FutureTask獲取結果Callable接口的call()方法允許拋出異常;而Ru…

More Effective C++ 條款05: 謹慎定義類型轉換函數

More Effective C 條款05&#xff1a;謹慎定義類型轉換函數核心思想&#xff1a;C中的隱式類型轉換雖然方便&#xff0c;但容易導致意外的行為和維護難題。應當通過explicit關鍵字和命名轉換函數等方式嚴格控制類型轉換&#xff0c;優先使用顯式轉換而非隱式轉換。 &#x1f68…

基于springboot的理商管理平臺設計與實現、java/vue/mvc

基于springboot的理商管理平臺設計與實現、java/vue/mvc

Flask藍圖:模塊化開發的利器

藍圖為什么要使用藍圖模塊化組織&#xff1a;將應用分解為可重用的模塊&#xff08;組件&#xff09;。每個藍圖封裝了相關的視圖、靜態文件、模板等。按功能劃分&#xff1a;將大型應用按功能模塊劃分&#xff08;例如&#xff1a;用戶認證、博客、管理后臺&#xff09;&#…

設計模式詳解

1.創建類型1.1 簡單工廠startuml抽象產品接口 interface Product { Operation(): string } 具體產品A class ConcreteProductA { Operation(): string } 具體產品B class ConcreteProductB { Operation(): string } 工廠類 class Factory { CreateProduct(type: string): Produ…

前端查漏補缺

插槽默認、具名&#xff08;多個插槽&#xff09;、作用域&#xff08;接收子組件數據&#xff09;//具名 <div class"container"><header><slot name"header"></slot></header><main><slot></slot></…

網絡協議UDP、TCP

一、網絡協議 UDPUDP用戶數據報協議&#xff1a;傳輸層網絡編程模型B/S模型&#xff1a;browser/server&#xff08;瀏覽器/服務器&#xff09;客戶端是通用的客戶端&#xff08;瀏覽器&#xff09;一般只做服務器開發客戶端要加載的數據均來自服務器C/S模型&#xff1a;client…

STM32 TIM_SelectInputTrigger()函數

一、函數功能與定位?TIM_SelectInputTrigger()是STM32定時器外設的關鍵配置函數&#xff0c;用于設置從模式定時器的觸發源&#xff08;Trigger Source&#xff09;?。其核心作用是將定時器的內部事件或外部信號映射為觸發信號&#xff08;TRGI&#xff09;&#xff0c;進而控…

Lecture 6 Kernels, Triton 課程筆記

本講座&#xff1a;基準測試/分析 編寫內核 總結 編程模型&#xff08;PyTorch、Triton、PTX&#xff09;與硬件之間的差距 > 性能奧秘 理解擴展的基準測試 用于理解 PyTorch 函數內部結構的分析&#xff08;用內核觸底&#xff09; 看 PTX 匯編&#xff0c;了解 CUDA 內核…

Spring Boot 整合網易163郵箱發送郵件實現找回密碼功能

在開發用戶系統時&#xff0c;發送郵件是一項常見需求&#xff0c;例如用戶忘記密碼時&#xff0c;通過郵箱發送驗證碼來驗證身份并重置密碼。本文將結合 Spring Boot 和 163 郵箱&#xff0c;演示如何實現郵件發送功能。 一、前提條件 普通用戶的 163 郵箱可以在 Spring Boot…

如何在mac玩windows游戲?3個工具推薦,不用換電腦!

Mac電腦雖然很流暢&#xff0c;但它也存在局限性&#xff0c;其中一點游戲玩家應該深有體會&#xff0c;那就是無法直接玩Windows專屬游戲&#xff0c;只能對著琳瑯滿目的游戲望眼欲穿。別急&#xff0c;我有辦法讓你在mac玩windows游戲&#xff0c;下面就來分享我的經驗。一、…

自回歸(Auto-Regressive, AR),自回歸圖像生成過程

根據論文中“**T2I Generation via Next-Token Prediction**”一節&#xff0c;自回歸&#xff08;Auto-Regressive, AR&#xff09;文本到圖像&#xff08;T2I&#xff09;模型的圖像生成過程可分為三個主要步驟&#xff0c;其原理和損失函數如下&#xff1a;---### &#x1f…

在mysql中,modify ,change ,rename to的作用是什么

在 MySQL 中&#xff0c;MODIFY、CHANGE 和 RENAME TO 都是 ALTER TABLE 語句的一部分&#xff0c;用于修改表的結構&#xff0c;但它們的作用和使用場景有所不同。1. MODIFY作用&#xff1a;用于修改表中現有列的定義&#xff0c;但不能修改列名。你可以使用 MODIFY 來更改列的…

【JVM】JVM的內存結構是怎樣的?

JVM的內存結構是Java程序運行時內存管理的核心&#xff0c;不同區域有明確的職責。 一、整體劃分 包括兩大部分&#xff0c;分為線程私有區域(隨線程創建/銷毀&#xff0c;無需垃圾回收)和線程共享區域(所有線程共用&#xff0c;需要垃圾回收管理)。 線程私有區域&#xff1a;程…

青少年軟件編程(python五級)等級考試試卷-客觀題(2023年12月)

更多內容和歷年真題請查看網站&#xff1a;【試卷中心 -----> 電子學會 ----> 機器人技術 ----> 五級】 網站鏈接 青少年軟件編程歷年真題模擬題實時更新 青少年軟件編程&#xff08;python五級&#xff09;等級考試試卷-客觀題&#xff08;2023年12月&#xff0…