Rust 的數據類型,以及與眾不同的變量聲明


楔子

Rust 中每一個變量的值,都有其特定的數據類型,Rust 會根據數據的類型來決定如何處理它們,例如分配空間。而 Rust是一門靜態語言,這意味着它在編譯程序的過程中需要知道所有變量的具體類型。

Rust 的數據類型分為兩類:標量類型(scalar)和復合類型(compound),我們先來說一下標量類型。

標量類型

標量類型是單個值類型的統稱,Rust 內建了 4 種基礎的標量類型:整數、浮點數、布爾值及字符。

整數

整數是指那些沒有小數部分的數字,Rust 中的整數類型分為以下幾種:

Rust 里面的類型名稱設計的非常精簡,i32 就是 int32,u16 就是 uint16。而 isize 和 usize 則取決於當前的系統,如果是 32 位,那么 isize、usize 就等價於 i32、u32,如果是 64 位,那么 isize、usize 就等價於 i64、u64。

fn main() {
    // 聲明變量需要使用 let 關鍵字
    // 語法格式為:let 變量: 類型 = 值
    // 這里就類似於 Go 里面的 var a int32 = 666
    let a: i32 = 666;
    // 打印的時候使用 {} 作為占位符
    println!("a = {}", a);  // a = 666

    // 如果數字比較多,還可以使用 _ 進行分隔,增強可讀性
    // 注意的是,我們這里的 b 沒有指定類型
    // 那么默認是 i32,因為 i32 相對來說速度最快
    let b = 10_00_00_00;
    println!("b = {}", b);  // b = 10000000
}

另外整數在 Rust 里面還有一種特殊的表達方式,比如:let a = 33u16,因為 u8, i8, u16, i32 等等都可以表示 33。所以不指定類型的話,光有 33,Rust 就不知道它的精度是多少,於是 let a = 33 會自動將 a 推斷成 int32。

但如果在整數后面加上類型,比如 33u16,那么 Rust 就知道這是一個 u16 類型的整數。於是 let a = 33u16,就會知道 a 是一個 u16 類型的變量,和 let a: u16 = 33 的作用相同。

當然 let a: u16 = 33u16 也可以,只不過有點多此一舉,但是 let a: u16 = 33u32 這種方式則不行,因為前后矛盾了。

以上整數都是用十進制表示,我們也可以使用二進制、八進制、十六進制創建整數:

fn main() {
    let a = 33u16;
    let b: i32 = 0b11_01_10_11;  // 二進制
    let c = 0o567i64;            // 八進制
    let d = 0xFFFFu32;           // 十六進制
    println!("{} {} {} {}",
             a, b, c, d);  // 33 219 375 65535
}

最后在 Rust 里面,u8 類型的變量還有另一種表達方式:

fn main() {
    let a = b'A';
    println!("a = {}", a);  // a = 65
}

打印出來的是整數,因為本質上就是個 u8。但注意:這里和別的語言有點不同,Rust 里面需要有一個前綴 b,但是在其它語言中沒有。

整數溢出

然后聊一聊整數溢出的問題,假設你有一個 u8 類型的變量,它可以存儲從 0 到 255 的數字。當你嘗試將該變量修改為某個超出范圍的值(比如 256)時,就會發生整數溢出。

Rust 對這一行為也有相應的處理規則,如果你在調試(debug)模式下進行編譯,那么 Rust 就會在程序中包含整數溢出的運行時檢測代碼,並在整數溢出發生時觸發程序 panic。

如果你在編譯時使用了帶有 --release 標記的發布(release)模式,那么 Rust 就不會包含那些可能會觸發 panic 的檢查代碼。作為替代,Rust 會在溢出發生時執行二進制補碼環繞。簡而言之,任何超出類型最大值的數值都會被環繞為類型最小值。以 u8 為例,256 會變成 256 - 2 ^ 8、也就是 0,257 會變成 257 - 2^ 8、也就是 1。

浮點數

浮點數就是帶小數的數字,Rust 提供了兩種基礎的浮點數類型:f32 和 f64,它們分別占用 32 位和 64 位空間。由於在現代 CPU 中 f64 與 f32 的運行效率相差無幾,卻擁有更高的精度,所以在 Rust 中,默認會將浮點數字面量的類型推導為 f64。

fn main() {
    // 默認是 f64
    let a = 3.14;  
    // 可以顯式指定為 f32
    let b: f32 = 3.14;
    // 浮點數也支持將類型寫在數值的后面
    let c = 3.14f32;  
    println!("{} {} {}", a, b, c);  // 3.14 3.14 3.14
}

比較簡單,沒啥可說的。

另外對於所有的數值類型,Rust 都支持常見的數學運算,邏輯運算、位運算等等。下面的代碼展示了如何在 let 語句中使用這些運算進行求值:

fn main() {
    let add = 1 + 2;
    let sub = 5 - 3;
    let mul = 3 * 4;
    let div = 8 / 2;
    let modular = 10 % 4;
    println!(
        "{} {} {} {} {}", 
        add, sub, mul, div, modular
    );  // 3 2 12 4 2

    // 位運算
    let lshift = 2 << 3;
    let rshift = 64 >> 3;
    let invert = !64;
    let bitwise_and = 15 & 37;
    let bitwise_or = 16 | 32;
    let bitwise_xor = 16 ^ 32;
    println!(
        "{} {} {} {} {} {}", 
        lshift, rshift, invert,
        bitwise_and, bitwise_or, bitwise_xor
    );  // 16 8 -65 5 48 48
}

這些運算和其它語言沒有什么本質的區別,需要注意的是取反操作,Rust 用的是 !,而 C 用的是 ~

布爾

正如其它大部分編程語言一樣,Rust 的布爾類型也只擁有兩個可能的值:true 和 false,它會占據一個字節的空間大小。你可以使用 bool 來表示一個布爾類型,例如:

fn main() {
    let flag1 = true;
    let flag2: bool = false;
    println!(
        "flag1 = {}, flag2 = {}", 
        flag1, flag2
    );  // flag = true, flag2 = false
}

布爾類型最主要的用途是在 if 表達式內作為條件使用,關於 if, for, while 等控制流我們后面會說。

字符

到目前為止,我們接觸到的大部分類型都只和數字有關,但 Rust 也同樣提供了相應的字符類型。在 Rust 中,char 類型被用於描述語言中最基礎的單個字符,但需要注意的是,char 類型使用單引號指定,而不同於字符串使用雙引號指定。

fn main() {
    let a: char = 'A';
    let b = '憨';
    let c = '🤔';
    let d = '😂';
    println!("{} {} {} {}", 
             a, b, c, d);  // A 憨 🤔 😂
}

注意 Rust 里面的 char 和 C 里面的 char 是有區別的,比如 'A',它在 C 和 Go 里面就是一個整數,是可以直接進行算術運算的。但在 Rust 里面不行,'A' 在 Rust 里面是一個字符,類似於長度為 1 的字符串。

所以 Rust 里面的 'A' 和 Go 里面的 'A' 是截然不同的,但 Rust 里面的 b'A' 和 Go 里面的 'A' 是相似的,因為都是無符號 8 位整數。

然后 Rust 中的 char 類型占 4 字節,是一個 Unicode 標量值,這也意味着它可以表示比 ASCII 多得多的字符內容。拼音字母、中文、日文、韓文、零長度空白字符,甚至是 emoji 表情都可以作為一個有效的 char 類型值。實際上,Unicode 標量可以描述從 U+0000 到 U+D7FF、以及從 U+E000 到 U+10FFFF 范圍內的所有值。

以上就是 Rust 的標量類型,有其它編程語言基礎的話很容易理解。另外可能你會感到好奇,為啥沒有字符串?原因是 Rust 的字符串非常重要,牽扯到了一些目前還沒有接觸到的知識,所以我們將字符串留到后面再說。

變量與可變性

了解完標量類型之后,我們先不急着看復合類型,先來了解一下 Rust 和其它語言截然不同的地方,也就是變量的可變性。舉個例子:

fn main() {
    let num = 123;
    num = num + 1;
    println!("num = {}", num);
}

你覺得這段代碼有什么問題嗎?有過 C 語言或 Go 語言經驗的人會覺得這是一段再正常不過的代碼了,先創建一個變量 num 等於 123,然后將 num 的值自增 1。確實如此,類似的代碼放在 C 或 Go 里面是完全正確的,但在 Rust 里面則不行。

編譯的時候報錯,提示我們不能給不可變的變量 num 二次賦值,原因是 Rust 中的變量一旦聲明,默認就是不可變的。當一個變量不可變時,就意味着一旦它被綁定到某個值上面,后續就再也無法改變。

那么我們可不可以強行讓它改變呢?答案是可以的,只需要在聲明變量的時候在 let 后面加上一個 mut 關鍵字即可。意思就是告訴 Rust 編譯器,我這個變量是可變的,別給我報錯了。

fn main() {
    let mut num = 123;
    num = num + 1;
    println!("num = {}", num);  
    // num = 124
}

因為 mut 出現在了變量綁定的過程中,所以我們現在可以合法地將 num 綁定的值從 123 修改為 124 了。

關於 Rust 為什么將變量默認設計成不可變的,原因是當我們的代碼邏輯依賴於某個值不可變時,如果這個值發生變化,程序就無法繼續按照期望的方式運行下去,並且這種 bug 往往難以追蹤,特別是當修改操作只在某些條件下偶然發生的時候。

而 Rust 編譯器能夠保證那些聲明為不可變的變量一定不會發生改變,這也意味着你無須在閱讀和編寫代碼時追蹤一個變量會如何變化,從而使代碼邏輯更加易於理解和推導。當然變量如果不能變的話,那還叫變量嗎?只不過 Rust 將變量是否可變的權利交給了程序猿。

變量與常量

變量的不可變性可能會讓你聯想到另外一個概念:常量(constant),就像不可變變量一樣,綁定到常量上的值也無法被其它代碼修改,但常量和變量之間還是存在着一些細微的差別的。

首先我們不能用 mut 關鍵字來修飾一個常量,其次常量不僅是默認不可變的,它還總是不可變的。並且在常量聲明的時候我們要使用 const 關鍵字,並顯式地指定類型。

fn main() {
    // 常量在 Rust 當中一般大寫,變量則是小寫
    // 多個單詞之間用下划線分割,並遵循蛇形命名法
    // 注意:這里不可以寫成 const NUM = 33u8;
    // 必須要將類型寫在常量的后面,變量的話是可以的
    const NUM: u8 = 33;
    println!("NUM = {}", NUM);  // NUM = 33
}

常量可以被聲明在任何作用域中,甚至包括全局作用域,這在一個值需要被不同部分的代碼共同引用時十分有用。最后需要注意的是:我們只能將普通的字面量或者編譯階段就能確定的表達式綁定在常量上,而無法將一個函數的返回值,或其它需要在運行時計算的值綁定到常量上。

fn main() {
    const A: u8 = 123;
    // A 是常量,A + 1 也是常量
    // 所以這行語句合法
    const B: u8 = A + 1;
    // 不管表達式再復雜,都可以編譯時就計算出來
    // 等價於 const C: u32 = 37
    const C: u32 = 1 + 3 * 5 + 3 * 7;
    print!("{}", C);

    // 但下面是不合法的,因為 x 是一個變量
    // 它不能賦值給一個常量
    let x = 123;
    const Y: i32 = x + 1;
}

關於變量和常量之間的區別,還是很好理解的。

變量的隱藏

在 Rust 中聲明的變量,如果不使用 mut 修飾,那么變量默認是不可變的。我們無法修改它,但是卻可以隱藏它,舉個例子:

fn main() {
    let num = 123;
    let num = num + 1;
    println!("num = {}", num); 
    // num = 124
}

😲,剛接觸 Rust 的話可能會好奇,這不是將同一個變量重復聲明了嗎?在 C 和 Go 里面是這樣的,但在 Rust 里面則不是,在 Rust 里面這被稱為變量的隱藏(shadow)。

我們連續聲明了兩個同名變量,那么第一個變量會被第二個變量隱藏(shadow),這意味着我們后續使用這個名稱時,它對應的將會是第二個變量。我們可以重復使用 let 關鍵字並配以相同的名稱來不斷地隱藏變量:

fn main() {
    let age = 18;
    let age = age + 1;
    let age = age * 2;
    println!("age = {}", age);  // age = 38
}

這段程序首先將變量 age 綁定到 18 這個值上,然后又聲明了新的變量 age,此時第二個變量 age 會將第一個變量 age 隱藏,並綁定在第一個變量 age 加 1 之后的值上,此時 age 的值就是 19;然后又聲明了第三個變量 age,此時第三個變量 age 又會隱藏第二個 age,然后綁定在第二個 age 乘 2 之后的值上,此時 age 的值就是 38。

因此這和其它編譯型語言完全是相反的,所以學習 Rust 需要調整我們的三觀。

隱藏機制不同於為一個變量重新賦值,因為重新為變量賦值,在變量不可變的時候會導致編譯錯誤。而使用 let,我們相當於創建了新的變量,可以執行一系列的變換操作。

隱藏機制與重新賦值還有一個區別:由於重復使用 let 關鍵字會創建出新的變量,所以我們可以在復用變量名稱的同時改變它的類型。

fn main() {
    // 創建整型變量
    let num = 123;
    // 重新聲明同名變量 num,會隱藏上一個 num
    // 因為是新聲明的變量,所以它的類型、是否可變都與上一個 num 無關
    let mut num = 3.14;
    println!("num = {}", num);  // num = 3.14
    num = 3.15;   
    println!("num = {}", num);  // num = 3.15
}

如果是重新賦值的話,那么即使將變量聲明為可變的,我們也只能改變它的值,卻改變不了它的類型。之前變量是什么類型,重新賦值之后變量還是什么類型,換言之我們在賦值的時候,要根據變量的類型進行賦值。

比如一開始聲明的變量的時候,類型為整型,那么重新賦值也必須賦一個整數,給一個字符串是肯定不行的。因為對於編譯型語言來說,變量一旦聲明,它的類型就固定了。

fn main() {
    // 不使用 mut,值無法修改
    let mut num = 123;
    // 使用 mut,我們可以修改值,但是類型不會變
    // 換言之,我們重新賦的值必須還是一個整數才行
    // 否則編譯器報錯
    num = 3.14;
}

代碼執行之后,編譯器就會報錯:expected integer, found floating-point number,意思是期望一個整數,但我們傳了一個浮點數過去。如果在 C 里面的話則不會報錯,而是會將浮點數進行截斷得到整數,並在編譯的時候發出警告。

說到這再多提一句,Rust 對類型的檢測和 Go 語言一樣嚴格,不同類型的變量不能相互賦值。比如 i16 和 i32,盡管都表示整數,但它們是不同類型,所以不可以相互賦值。

因此上面的代碼會報錯,因為將浮點數賦值給了一個 i32 類型的變量,那問題來了,如果想讓它不報錯該怎么辦呢?很簡單,在 num = 3.14 的前面加上 let 關鍵字就行了。因為此時相當於重新創建了一個變量 num,並將第一個 num 給隱藏掉了。而既然是新創建的變量,那么它的類型、可變性都可以自由指定,與上一個 num 無關。

結合上下文的類型推斷

我們說 Rust 對類型的檢測非常嚴格,即使是相同類型,但如果精度不同,也不能相互賦值。

fn main() {
    let mut a: i16 = 123;
    a = 234u8;
}

這種做法是錯誤的,因為 a 是 i16,所以不可以將 u8 的整數賦給它。

但 Rust 有一個智能的地方,就是它類型推斷會結合上下文。

fn main() {
    let mut a = 123;
    a = 234u8;
}

上面這種做法是可以的,咦,不是說不同類型不能相互賦值嗎?這里的 a 應該是一個 i32,為什么能賦一個 u8 類型的整數呢?

原因是我們在創建 a 的時候沒有指定類型,那么理論上 Rust 會根據 123 將其推斷成 i32 類型,但 Rust 編譯器檢測到我們后續將一個 u8 類型的整數賦值給了 a,於是在聲明變量時就將 a 推斷成了 u8 類型。

而第一個示例之所以沒有通過,是因為我們在聲明變量的時候顯式地指定了類型為 i16,那么它的類型就已經確定為 i16,所以后續再賦值 u8 類型的整數就會報錯。

復合類型

復合類型(compound type)可以將多個不同類型的值組合為一個類型,Rust提供了兩種內置的基礎復合類型:元組(tuple)和數組(array)。

元組

元組是一種相當常見的復合類型,它可以將其它不同類型的多個值組合在一起。此外元組還擁有一個固定的長度:我們無法在聲明結束后增加或減少其中的元素數量。

為了創建元組,我們需要把一系列的值使用逗號分隔后放置到一對圓括號中,元組每個位置的值都有一個類型,這些類型不需要是相同的。為了演示,下面的例子中手動添加了不必要的類型注解:

fn main() {
    // 類型注解可以去掉,只不過在去掉之后
    // Rust 會默認將 44 推斷成 i32 類型
    // 注意:類型和值要匹配
    let tpl: (i32, f64, u8) = (33, 3.14, 44);
    // println! 不能直接打印元組
    // 因為元組內部沒有實現 std::fmt::Display
    // 我們需要將 {} 改成 {:?} 才能打印
    // 或者改成 {:#?} 還可以美觀打印
    println!("tpl = {:?}", tpl);  
    println!("tpl = {:#?}", tpl);
    /*
    tpl = (33, 3.14, 44)
    tpl = (
        33,
        3.14,
        44,
    )    
     */
}

由於一個元組也被視作一個單獨的復合元素,所以這里的變量 tpl 被綁定到了整個元組上。而為了從元組中獲得單個的值,我們可以像下面這樣使用模式匹配來解構元組:

fn main() {
    // 類型注解也可以去掉,Rust 會自動推斷
    let tpl = (33, 3.14, 44);
    // 此時 x、z 被推斷成 i32,y 被推斷成 f64
    let (x, y, z) = tpl;
    println!(
        "x = {}, y = {}, z = {}", x, y, z
    );  // x = 33, y = 3.14, z = 44
}

這段程序將變量 tpl 綁定在了元組上,隨后 let 關鍵字的右側使用了一個模式將 tpl 拆分為 3 個不同的部分:x、y 和 z,這個操作也被稱為解構(destructuring)。在解構的時候,左邊的幾個變量必須使用括號括起來,否則會出現語法錯誤。

fn main() {
    let tpl = (33, 3.14, 44);
    // let x, y, z = tpl; 是不合法的
    // 必須寫成 let (x, y, z) = tpl
    // 此外我們還可以指定變量的可變性
    // 上面的 x、y、z 都是不可變的,我們將其改為可變
    let (mut x, mut y, mut z) = tpl;
    println!(
        "x = {}, y = {}, z = {}", x, y, z
    );  // x = 33, y = 3.14, z = 44
    x = 66;
    y = 4.44;
    z = 88;
    println!(
        "x = {}, y = {}, z = {}", x, y, z
    );  // x = 66, y = 4.44, z = 88
}

注意:在解構的時候我們不能這樣做,let mut (x, y, z) = tpl,這么做是不符合 Rust 語法的。如果希望變量可變,那么需要單獨給指定變量的前面加上 mut,比如我希望 x 可變,那么就在 x 的前面加上 mut 即可。如果希望所有變量都可變,那么所有變量前面都要加上 mut。

除了解構,我們還可以通過索引並使用點號來訪問元組中的值:

fn main() {
    let tpl = (33, 3.14, 44);
    // 通過 tpl.index 即可訪問元組內的元素
    // 只不過使用的是 . 而不是 []
    let x = tpl.0;
    let y = tpl.1;
    let z = tpl.2;
    println!(
        "x = {}, y = {}, z = {}", x, y, z
    );  // x = 33, y = 3.14, z = 44
    
    // 當然更加方便的做法還是這種:let (x, y, z) = tpl;
    // 到這里,我們可以發現 Rust 是支持多元賦值的
    // 但是要通過元組的方式、也就是用小括號括起來(等號兩邊都需要)
    // 此時 x、z 可變,y 不可變
    let (mut x, y, mut z) = (1u8, 66, 3.14);
    println!(
        "x = {}, y = {}, z = {}", x, y, z
    );  // x = 1, y = 66, z = 3.14
}

所以還是很簡單的,使用小括號創建元組,並且元組里面的元素沒有類型要求、數量不限;只是一旦創建,元組的大小就固定了,我們不可以再往里面添加元素、刪除元素。但修改元素是可以的:

fn main() {
    // 如果想修改,還是要使用 mut 對變量進行修飾
    // 不管是對 tpl 重新賦值,還是修改 tpl 的某個元素
    // 都意味着 tpl 發生改變,都要使用 mut 進行聲明
    let mut tpl = (33, 3.14, 44);
    println!(
        "tpl = {:?}", tpl); // tpl = (33, 3.14, 44)
    // 元組一旦創建,大小固定、並且每個元素的類型也固定
    // tpl.0 被推斷為 i32,那么修改之后必須還是 i32
    tpl.0 = 3333;
    println!(
        "tpl = {:?}", tpl); // tpl = (3333, 3.14, 44)
    
    // 如果希望將第一個元素改成 i64,那么只能重新賦值
    // 並且賦值的時候顯式指定類型,即 tpl: (i64, f64, i32)
    // 但比較麻煩,因為我們只對第一個元素的類型有要求
    // 所以也可以使用下面這種方式
    let mut tpl = (123i64, tpl.1, tpl.2);
    println!(
        "tpl = {:?}", tpl);  // tpl = (123, 3.14, 44)
}

這就是 Rust 的元組,和 Python 的元組有着相似之處,但又不完全一樣。

數組

我們同樣可以在數組中存儲多個值,與元組不同,數組中的每一個元素都必須是相同的類型。Rust 中的數組擁有固定的長度,一旦聲明就不能再隨意更改大小,這和其它靜態語言是比較相似的。所以當想要確保元素類型相同、且數量固定時,那么數組是一個非常有用的工具。

在 Rust 中,我們可以將以逗號分隔的值放置在一對方括號內來創建一個數組:

fn main() {
    let arr = [1, 2, 3, 4];
    println!(
        "arr = {:?}", arr
    );  // arr = [1, 2, 3, 4]
}

Rust 標准庫也提供了一個更加靈活的動態數組(vector)類型。動態數組是一個類似於數組的集合結構,但它允許用戶自由地調整數組長度,后續會說。

那么數組在創建的時候如何指定元素的類型呢?

fn main() {
    // 聲明數組元素類型的同時,也要指定數組元素的個數
    // 可以看到做法還是蠻怪異的,另外記得個數、類型要匹配
    let arr: [i8; 4] = [1, 2, 3, 4];
    println!("arr = {:?}", arr);  // arr = [1, 2, 3, 4]
}

Rust 還提供了一種初始化數組的方式,當創建一個所有元素都相同的數組時會非常方便。

fn main() {
    // [m;n] 表示創建一個含有 n 個元素的數組
    // 里面的元素都為 m
    let arr = [3;4];
    println!(
        "arr = {:?}", arr
    );  // arr = [3, 3, 3, 3]
}

此外也可以創建多維數組:

fn main() {
    // 表示數組里面有兩個元素
    // 每個元素都是含有三個 i32 的數組
    let arr: [[i32;3]; 2] = [[1, 2, 3], 
                             [2, 3, 4]];
    println!(
        "arr = {:?}", arr
    );  // arr = [[1, 2, 3], [2, 3, 4]]
}

然后是數組元素的訪問,數組由一整塊分配在棧上的內存組成,可以通過索引來訪問一個數組中的所有元素,比如:

fn main() {
    let arr = [[1, 2, 3], [11, 22, 33]];
    println!(
        "arr[1] = {:?}, arr[1][1] = {}",
        arr[1], arr[1][1]
    );  // arr[1] = [11, 22, 33], arr[1][1] = 22
}

訪問數組必然會伴隨索引越界的問題,假設數組中有 5 個元素,但我們嘗試訪問第 6 個元素,那么顯然是會報錯的。

fn main() {
    let arr = [1, 2, 3];
    println!("arr[4] = {}", arr[4]);
}

上述代碼會發生編譯錯誤,也就是在編譯的時候就會得到如下錯誤信息:

index out of bounds: the length is 3 but the index is 4

但如果我們稍微動一下手腳:

fn main() {
    let arr = [1, 2, 3];
    let indexes = [4, 5, 6];
    // arr[indexes[0]] -> arr[4]
    println!("arr[4] = {}", arr[indexes[0]]);
}

此時依然會報錯,只不過這個錯誤發生在運行時期,也就是說編譯是可以通過的。

小結

以上就是 Rust 的基本數據類型,至於更復雜的類型我們后續再聊。然后是 Rust 的變量,和其它靜態語言有着很大的不同,比如變量的隱藏(或者說遮蔽),以及 mut 關鍵字。這些新特性都會使得 Rust 看起來與眾不同,並且具有不一樣的魅力。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM