99re热视频这里只精品,久久久天堂国产精品女人,国产av一区二区三区,久久久精品成人免费看片,99久久精品免费看国产一区二区三区

宏命令

2018-08-12 22:03 更新

宏命令

現(xiàn)在你已經(jīng)了解了很多 Rust 為抽象和重用代碼提供的工具。這些代碼重用的單元有豐富的語義結(jié)構(gòu)。例如,函數(shù)有一個(gè)類型聲明,有特征約束的類型參數(shù),重載的函數(shù)必須屬于一個(gè)特定的特征。    

這種結(jié)構(gòu)意味著 Rust 的核心抽象有強(qiáng)大的編譯時(shí)正確性檢查。但這是以靈活性的減少為代價(jià)。如果你從表面上識別重復(fù)代碼的模式,你可能會發(fā)現(xiàn)像一個(gè)泛型函數(shù),特征,或者 Rust 語義中其它任何東西一樣表達(dá)模式是很困難的或者是很繁瑣的。

宏定義允許我們實(shí)現(xiàn)語法水平上的抽象。宏調(diào)用的簡單來說就是“擴(kuò)大”語法形式。這種擴(kuò)張發(fā)生在編譯早期,在任何靜態(tài)檢查之前。因此,宏可以捕獲許多代碼重用模式,這些是 Rust 的核心抽象做不到的?!   ?/p>

缺點(diǎn)是基于宏的代碼比較難以理解,因?yàn)楦俚膬?nèi)置規(guī)則可以使用。像一個(gè)普通的函數(shù),可以使用一個(gè)功能良好的宏而無需理解它的實(shí)現(xiàn)。然而,很難設(shè)計(jì)一個(gè)功能良好的宏!此外,在宏代碼中的編譯錯(cuò)誤難以解釋,因?yàn)樗麄冇脭U(kuò)展代碼來描述問題,而不是開發(fā)人員使用的源代碼級別的形式。

這些缺點(diǎn)是宏的重要的“特性”。這并不是說宏不好,它是有時(shí)是 Rust 的一部分,因?yàn)樗麄冋嬲枰啙?、抽象的代碼。我們要記住這個(gè)折衷。

定義一個(gè)宏

你可能看到過宏 vec!,用于初始化包含任意數(shù)量元素的向量。

let x: Vec<u32> = vec![1, 2, 3];

這不可能是一個(gè)普通的函數(shù),因?yàn)樗腥我鈹?shù)量的參數(shù)。但我們可以把它想象成下面語法的簡稱

let x: Vec<u32> = {
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
};

我們可以使用一個(gè)宏實(shí)現(xiàn)這個(gè)函數(shù):

macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}

哇,這是一個(gè)新的語法!讓我們分解一下。

macro_rules! vec { ... }

這表示我們定義一個(gè)名為 vec 的宏,正如 fn vec 將定義一個(gè)名為 vec 的函數(shù)。換句話說,我們用一個(gè)感嘆號非正式地編寫一個(gè)宏的名字,例如 vec!。感嘆號是調(diào)用語法的一部分,用來區(qū)分一個(gè)宏和一個(gè)普通的函數(shù)。

匹配

宏是通過一系列的規(guī)則來定義的,這些規(guī)則是用來模式匹配的。上面,我們有

( $( $x:expr ),* ) => { ... };

這就像一個(gè)匹配表達(dá)式的處理器,但編譯時(shí)匹配發(fā)生 Rust 語法樹。在最后的實(shí)例后面分號是可選的。= > 左邊”的“模式”被稱為“匹配器”。這些在語言里都有自己的小語法?!   ?/p>

匹配器 $x:expr 通過將語法樹綁定到元變量 $x 來匹配任何 Rust 表達(dá)式。標(biāo)識符 expr 是一個(gè)片段說明符,完整的標(biāo)識符是在本章后面的枚舉。$(...),* 周圍的匹配器將匹配零個(gè)或多個(gè)表達(dá)式,這些表達(dá)式由逗號分隔開?!   ?/p>

除了特殊的匹配器語法,任何出現(xiàn)在一個(gè)匹配器中的 Rust 指令必須完全匹配。例如,

macro_rules! foo {
(x => $e:expr) => (println!("mode X: {}", $e));
(y => $e:expr) => (println!("mode Y: {}", $e));
}

fn main() {
foo!(y => 3);
}

會打印出

mode Y: 3

通過函數(shù)

foo!(z => 3);

我們會得到以下編譯錯(cuò)誤

error: no rules expected the token `z`

擴(kuò)展

在大多數(shù)情況下,宏觀規(guī)則的右邊是普通的 Rust 語法。但我們可以拼接一些被匹配器捕獲的語法。下面是一個(gè)典型的例子:

$(
temp_vec.push($x);
)*

每個(gè)匹配表達(dá)式 $x 在宏擴(kuò)展中產(chǎn)生一個(gè)單獨(dú)的 push 語句。擴(kuò)展的副本與匹配器中的副本是同步的。    

因?yàn)?$x 已經(jīng)聲明為一個(gè)表達(dá)式匹配,我們不要在右側(cè)重復(fù) :expr。同樣,我們不能把逗號作為重復(fù)操作符的一部分。相反,我們在重復(fù)的塊內(nèi)有一個(gè)終止分號?!   ?/p>

另一個(gè)細(xì)節(jié):vec! 宏在右側(cè)有兩個(gè)雙括號。他們通常組合如下:

macro_rules! foo {
() => {{
...
}}
}

外層的括號是語法 macro_rules! 的一部分。實(shí)際上,你也可以使用 () 或 []。他們只是將右側(cè)劃分為一個(gè)整體?!   ?/p>

內(nèi)層括號是擴(kuò)展語法的一部分。記住,vec! 宏被用在一個(gè)表達(dá)式上下文。為了寫一個(gè)包含多個(gè)語句的表達(dá)式,包括 let-bindings,我們需要使用一個(gè)塊。如果你的宏擴(kuò)展到一個(gè)單個(gè)表達(dá)式,你就不需要這些額外的括號。    

注意,我們從未聲明宏產(chǎn)生一個(gè)表達(dá)式。事實(shí)上,這是不確定的,直到我們作為一個(gè)表達(dá)式使用宏。小心,你可以編寫一個(gè)宏,它的擴(kuò)展可以在多個(gè)上下文中起作用。例如,數(shù)據(jù)類型的簡寫作為一個(gè)表達(dá)式或模式是有效的。

重復(fù)

重復(fù)操作符遵循以下兩個(gè)主要規(guī)則:

  1. $(...)* 為它包含的所有 $name 同步處理一個(gè)重復(fù)“層”,并且
  2. 每個(gè) $name 必須至少在它能匹配的盡可能多的 $(...)* 下。如果它在更多的重復(fù)操作符下,它會適當(dāng)?shù)膹?fù)制。

這個(gè)結(jié)構(gòu)復(fù)雜的宏說明了變量從外層重復(fù)層的復(fù)制。

macro_rules! o_O {
(
$(
$x:expr; [ $( $y:expr ),* ]
);*
) => {
&[ $($( $x + $y ),*),* ]
}
}

fn main() {
let a: &[i32]
= o_O!(10; [1, 2, 3];
   20; [4, 5, 6]);

assert_eq!(a, [11, 12, 13, 24, 25, 26]);
}

上面包含了大部分的匹配器語法。這些例子使用 $(...)* ,這是一種“零個(gè)或多個(gè)”匹配?;蛘吣憧梢詫?$(...)+ 進(jìn)行“一個(gè)或多個(gè)”匹配。兩種形式都可選地包括一個(gè)分隔符,它可以是任何除 +* 的符號?!   ?/p>

該系統(tǒng)是基于“Macro-by-Example”的。

衛(wèi)生

一些語言通過使用簡單的文本替換來實(shí)現(xiàn)宏,從而導(dǎo)致各種各樣的問題。例如,這個(gè) C 程序打印 13,而不是預(yù)期的 25。

#define FIVE_TIMES(x) 5 * x

int main() {
printf("%d\n", FIVE_TIMES(2 + 3));
return 0;
}

擴(kuò)展后,我們有 5 * 2 + 3,乘法有比加法更高的優(yōu)先級。如果你使用很多 C 宏,你可能知道以避免這個(gè)問題的通用方法,還有其它五六種方法。在 Rust 里,我們不必?fù)?dān)心這些。

macro_rules! five_times {
($x:expr) => (5 * $x);
}

fn main() {
assert_eq!(25, five_times!(2 + 3));
}

元變量 $x 被解析為一個(gè)表達(dá)式節(jié)點(diǎn),并保持它在語法樹上的位置,即使在替換以后。    

宏觀系統(tǒng)的另一個(gè)常見的問題是“變量捕獲”。這里有一個(gè) C 宏,使用 a GNU C extension模擬 Rust 的表達(dá)式塊。

#define LOG(msg) ({ \
int state = get_log_state(); \
if (state > 0) { \
printf("log(%d): %s\n", state, msg); \
} \
})

這是一個(gè)簡單發(fā)生嚴(yán)重故障的用例:

const char *state = "reticulating splines";
LOG(state)

這可以擴(kuò)展為

const char *state = "reticulating splines";
int state = get_log_state();
if (state > 0) {
printf("log(%d): %s\n", state, state);
}

命名為 state 第二個(gè)變量覆蓋了第一個(gè)變量。這是一個(gè)問題,因?yàn)?print 語句應(yīng)該參考這兩個(gè)變量?!   ?/p>

下面 Rust 宏可以達(dá)到預(yù)期結(jié)果。

macro_rules! log {
($msg:expr) => {{
let state: i32 = get_log_state();
if state > 0 {
println!("log({}): {}", state, $msg);
}
}};
}

fn main() {
let state: &str = "reticulating splines";
log!(state);
}

這個(gè)能起作用是因?yàn)?Rust 有一個(gè)衛(wèi)生宏系統(tǒng)。每個(gè)宏擴(kuò)展發(fā)生在一個(gè)獨(dú)特的“語法語境”,每個(gè)變量被產(chǎn)生它的語法語境所標(biāo)記。好像 main 里面的變量 state 在宏里被涂上不同的顏色,因此他們互相不沖突?!   ?/p>

這也限制了宏在調(diào)用點(diǎn)引入新的綁定的能力。以下代碼就不會起作用:

macro_rules! foo {
() => (let x = 3);
}

fn main() {
foo!();
println!("{}", x);
}

相反,你需要通過變量名調(diào)用,所以它被正確的語法語境所標(biāo)記。

macro_rules! foo {
($v:ident) => (let $v = 3);
}

fn main() {
foo!(x);
println!("{}", x);
}

這適用于 let 綁定和循環(huán)標(biāo)簽,而不適用于 items。那么下面的代碼可以通過編譯:

macro_rules! foo {
() => (fn x() { });
}

fn main() {
foo!();
x();
}

遞歸宏

一個(gè)宏的擴(kuò)展可以包括更多的宏調(diào)用,包括調(diào)用正在擴(kuò)展的同一宏。這些遞歸宏用于處理樹形結(jié)構(gòu)輸入,正如下面(簡單的) HTML 速記所示:

macro_rules! write_html {
($w:expr, ) => (());

($w:expr, $e:tt) => (write!($w, "{}", $e));

($w:expr, $tag:ident [ $($inner:tt)* ] $($rest:tt)*) => {{
write!($w, "<{}>", stringify!($tag));
write_html!($w, $($inner)*);
write!($w, "</{}>", stringify!($tag));
write_html!($w, $($rest)*);
}};
}

fn main() {
use std::fmt::Write;
let mut out = String::new();

write_html!(&mut out,
html[
head[title["Macros guide"]]
body[h1["Macros are the best!"]]
]);

assert_eq!(out,
"<html><head><title>Macros guide</title></head>\
 <body><h1>Macros are the best!</h1></body></html>");
}

調(diào)試宏代碼

為了看到宏擴(kuò)展的結(jié)果,運(yùn)行 rustc --pretty expanded。輸出代表一個(gè)整體運(yùn)行結(jié)果,所以你也可以把結(jié)果存入 rustc,它有時(shí)候會比原始的編譯產(chǎn)生更好的錯(cuò)誤信息。注意,如果多個(gè)同名的變量(但在不同的語法語境內(nèi))在相同的范圍內(nèi)起作用,--pretty expanded 的輸出可能有不同的意義。在這種情況下,--pretty expanded,hygiene 會告訴你關(guān)于語法語境的情況?!   ?/p>

rustc 提供了兩種語法擴(kuò)展以幫助宏調(diào)試?,F(xiàn)在,他們是不穩(wěn)定的?!   ?/p>

  1. log_syntax!(...) 將其參數(shù)打印到標(biāo)準(zhǔn)輸出,在編譯時(shí),沒有“擴(kuò)展”?!  ?/li>
  2. trace_macros!(true) 在每次宏擴(kuò)展時(shí)產(chǎn)生編譯器信息。在擴(kuò)展結(jié)束時(shí)使用 trace_macros!(false) 。

語法要求

即使當(dāng) Rust 代碼包含 un-expanded 宏時(shí),它可以解析為一個(gè)完整的語法樹。這個(gè)屬性對編輯器和其他處理代碼的工具是非常有用的。它也對 Rust 宏系統(tǒng)的設(shè)計(jì)產(chǎn)生一些后果?!   ?/p>

一個(gè)后果是,解析一個(gè)宏調(diào)用時(shí) Rust 必須確定宏是否代表

  • 零個(gè)或多個(gè)項(xiàng)目,
  • 零個(gè)或多個(gè)方法,   
  • 一個(gè)表達(dá)式,
  • 一個(gè)語句,或  
  • 一個(gè)模式。

在代碼塊中的宏調(diào)用可以代表一些項(xiàng)目,或者一個(gè)表達(dá)式或語句。Rust 使用一個(gè)簡單的規(guī)則來消除這個(gè)不確定性。宏調(diào)用的代表項(xiàng)目必須是

  • 由花括號分隔開,例如 foo! { ... } ,或者
  • 由一個(gè)分號終止,例如 foo!(...);

擴(kuò)展前解析另一個(gè)后果是宏調(diào)用必須由有效 Rust 符號組成。此外,圓括號,方括號,花括號必須在一個(gè)宏調(diào)用中是平衡的。例如,foo!([)是禁止的。這允許 Rust 知道宏調(diào)用在哪里結(jié)束?!   ?/p>

更正式地,宏調(diào)用體必須是一個(gè)“標(biāo)記樹”序列。一個(gè)標(biāo)記樹遞歸地定義為

  • 一系列匹配的由 () ,[] ,或 {} 包圍的標(biāo)記樹,或者
  • 任何其它單個(gè)標(biāo)記

在一個(gè)匹配器中,每個(gè)元變量都有一個(gè)“片段說明符”,來識別匹配哪些語法形式。

  • ident:標(biāo)識符。例如:x; foo
  • path:一個(gè)合格的名字。例如: T::SpecialA。
  • expr: 一個(gè)表達(dá)式。例如:2 + 2; if true then { 1 } else { 2 }; f(42)。
  • ty:一個(gè)類型。例如:i32; Vec<(char, String)>; &T
  • pat:一個(gè)模式。例如: Some(t); (17, 'a'); _ 。
  • stmt:單個(gè)語句。例如: let x = 3。
  • block:一個(gè)括號分隔的語句序列。例如: { log(error, "hi"); return 12; }。
  • item:一個(gè)項(xiàng)目。例如: fn foo() { }; struct Bar; 。
  • meta:一個(gè) "元項(xiàng)目", 在屬性中建立的。 例如: cfg(target_os = "windows")。
  • tt:一個(gè)單個(gè)標(biāo)記樹。

還有其他關(guān)于元變量后下一個(gè)標(biāo)記的附加規(guī)則:

  • 變量 expr 后必須加下面中的一個(gè): => , ;
  • 變量 ty 和 path 后必須加下面中的一個(gè): => , : = > as
  • 變量 pat 后必須加下面中的一個(gè):=> , =
  • 其它變量后可能要加其它符號。

這些規(guī)則提供一些在不破壞現(xiàn)有宏的情況下,Rust 語法發(fā)展的靈活性。

宏系統(tǒng)不處理解析的不明確性。例如,語法 $($t:ty)* $e:expr 總是無法解析,因?yàn)榻馕銎鲗⒈黄冗x擇解析 $t 和解析 $e。將調(diào)用語法改為在前面加一個(gè)獨(dú)特的符號可以解決這個(gè)問題。在這種情況下,你可以編寫 $(T $t:ty)* E $e:exp

范圍和宏導(dǎo)入/導(dǎo)出

宏在編譯的早期階段名稱解析前被擴(kuò)展。一個(gè)缺點(diǎn)是,相對于其他結(jié)構(gòu)的語言,范圍工作原理不同?!   ?/p>

宏的定義和擴(kuò)展都發(fā)生在一個(gè) crate 資源的深度優(yōu)先,詞序遍歷。所以一個(gè)定義在模塊范圍內(nèi)的宏對在同一個(gè)模塊內(nèi)任何后續(xù)代碼是可見的,其中包括任何后續(xù)的孩子 mod 項(xiàng)目的主體。

一個(gè)定義在單個(gè) fn 的體內(nèi)的宏,或其它不在模塊范圍的任何地方,只有在這個(gè)項(xiàng)目內(nèi)是可見的?!   ?/p>

如果一個(gè)模塊有 macro_use 屬性,其宏在它的孩子模塊的 mod 項(xiàng)目后的父模塊中是可見的。如果父模塊也有 macro_use 屬性,那么宏在父模塊的 mod 項(xiàng)目后的祖父模塊中也是可見的,等等。    

macro_use屬性也可以出現(xiàn)在 extern crate。在這種情況下它控制從 extern crate 加載哪些宏,如

#[macro_use(foo, bar)]
extern crate baz;

如果屬性被簡單定義如 #[macro_use],所有宏被加載。如果沒有 #[macro_use] 那么 宏就不能被加載。只有定義 #[macro_export] 屬性的宏可能被加載?!   ?/p>

為了加載沒有連接到輸出的 crate 的宏,使用 #[no_link]。    

一個(gè)例子:

macro_rules! m1 { () => (()) }

// visible here: m1

mod foo {
// visible here: m1

#[macro_export]
macro_rules! m2 { () => (()) }

// visible here: m1, m2
}

// visible here: m1

macro_rules! m3 { () => (()) }

// visible here: m1, m3

#[macro_use]
mod bar {
// visible here: m1, m3

macro_rules! m4 { () => (()) }

// visible here: m1, m3, m4
}

// visible here: m1, m3, m4

當(dāng)用 #[macro_use] extern crate 加載這個(gè)庫時(shí),只有 m2 將被導(dǎo)入?!   ?/p>

Rust 參考有一個(gè)宏相關(guān)屬性的列表。

變量 $crate

進(jìn)一步困難發(fā)生在當(dāng)一個(gè)宏在多個(gè) crates 被使用。也就是 mylib 定義如下

pub fn increment(x: u32) -> u32 {
x + 1
}

#[macro_export]
macro_rules! inc_a {
($x:expr) => ( ::increment($x) )
}

#[macro_export]
macro_rules! inc_b {
($x:expr) => ( ::mylib::increment($x) )
}

inc_a 只在 mylib 起作用,同時(shí) inc_b 只能在庫外起作用。此外,如果用戶在另一個(gè)名字下引入 mylib ,inc_b 將失去作用 ?!   ?/p>

Rust 沒有針對 crate 參考的衛(wèi)生系統(tǒng),但它確實(shí)提供了一個(gè)解決這個(gè)問題的簡單方法。在一個(gè)從一個(gè)名為foo的 crate 引入的宏,特殊宏變量 $crate 將擴(kuò)展到 ::foo。相反,當(dāng)一個(gè)宏被定義,然后在同一 crate 中被使用,$crate 就不會擴(kuò)展。這意味著我們可以這樣寫

#[macro_export]
macro_rules! inc {
($x:expr) => ( $crate::increment($x) )
}

來定義一個(gè)在庫內(nèi)庫外的宏。函數(shù)名也擴(kuò)展到 ::increment 或者 ::mylib::increment。

為了保持這個(gè)系統(tǒng)簡單而正確,#[macro_use] extern crate ... 可能只出現(xiàn)在 crate 的根部,而不是 mod 內(nèi)部。這將確保 $crate 是一個(gè)標(biāo)識符。

深端

介紹性章節(jié)曾今提到過遞歸宏,但它沒有給出完整的描述。遞歸宏是有用的另一個(gè)原因:每個(gè)遞歸調(diào)用給你匹配宏參數(shù)的另一個(gè)機(jī)會。    

作為一個(gè)極端的例子,盡管不明智,在 Rust 的宏系統(tǒng)實(shí)現(xiàn)位循環(huán)標(biāo)記自動(dòng)機(jī)是可能的。

macro_rules! bct {
// cmd 0:  d ... => ...
(0, $($ps:tt),* ; $_d:tt)
=> (bct!($($ps),*, 0 ; ));
(0, $($ps:tt),* ; $_d:tt, $($ds:tt),*)
=> (bct!($($ps),*, 0 ; $($ds),*));

// cmd 1p:  1 ... => 1 ... p
(1, $p:tt, $($ps:tt),* ; 1)
=> (bct!($($ps),*, 1, $p ; 1, $p));
(1, $p:tt, $($ps:tt),* ; 1, $($ds:tt),*)
=> (bct!($($ps),*, 1, $p ; 1, $($ds),*, $p));

// cmd 1p:  0 ... => 0 ...
(1, $p:tt, $($ps:tt),* ; $($ds:tt),*)
=> (bct!($($ps),*, 1, $p ; $($ds),*));

// halt on empty data string
( $($ps:tt),* ; )
=> (());
}

練習(xí):使用宏來減少上述 bct! 宏的定義中的復(fù)制。

常見的宏

下面是一些你會在 Rust 代碼中常見的宏。

panic!

這個(gè)宏會導(dǎo)致當(dāng)前線程的叛逆。你可以給它一個(gè)消息產(chǎn)生叛逆:

panic!("oh no!");

vec!

vec !宏在整本書被使用,所以你可能已經(jīng)見過了。它毫不費(fèi)力地創(chuàng)建 Vec:

let v = vec![1, 2, 3, 4, 5];

它還允許你用重復(fù)的值構(gòu)建向量。例如,一百個(gè) 0:

let v = vec![0; 100];

assert! 和 assert_eq!

這兩個(gè)宏用于測試。assert! 傳入一個(gè)布爾值,assert_eq! 傳入兩個(gè)值并且比較它們。像下面這樣:

// A-ok!

assert!(true);
assert_eq!(5, 3 + 2);

// nope :(

assert!(5 < 3);
assert_eq!(5, 3);

try!

try! 用于錯(cuò)誤處理。它傳入可以返回 Result<T, E> 的參數(shù),并給出 T 如果它是 Ok,并且返回 Err(E) 。像這樣:

use std::fs::File;

fn foo() -> std::io::Result<()> {
let f = try!(File::create("foo.txt"));

Ok(())
}

這比這樣做干凈:

use std::fs::File;

fn foo() -> std::io::Result<()> {
let f = File::create("foo.txt");

let f = match f {
Ok(t) => t,
Err(e) => return Err(e),
};

Ok(())
}

unreachable!

當(dāng)你認(rèn)為一些代碼不應(yīng)該執(zhí)行是使用這個(gè)宏:

if false {
unreachable!();
}

有時(shí),編譯器可能會讓你有一個(gè)不同的分支,你知道它永遠(yuǎn)不會運(yùn)行。在這些情況下,使用這個(gè)宏,這樣如果你錯(cuò)了,你會得到一個(gè)關(guān)于它的 panic!。

let x: Option<i32> = None;

match x {
Some(_) => unreachable!(),
None => println!("I know x is None!"),
}

unimplemented!

當(dāng)你想讓你的函數(shù)檢查類型,不想擔(dān)心寫出函數(shù)的主體時(shí) unimplemented! 宏可以被使用。這種情況的一個(gè)例子是你想要一次同時(shí)用多個(gè)所需的方法實(shí)現(xiàn)一個(gè)特征。把其它的定義為 unimplemented! 直到你準(zhǔn)備寫他們。

程序宏

如果 Rust 宏系統(tǒng)做不到你需要的,你可能想要編寫一個(gè)編譯器插件。相比 macro_rules !宏,這需要更多的工作,不太穩(wěn)定的接口,bugs 可能更難追蹤。作為交換你獲得了在編譯器內(nèi)運(yùn)行任意 Rust 代碼的靈活性。這就是語法擴(kuò)展插件有時(shí)被稱為“程序宏”的原因。

在效率和可重用性方面, vec! 在 libcollections 的實(shí)際定義不同于這里介紹的。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號