rust学习笔记之基础:表达式、函数和控制结构

表达式

Rust 程序(大部分)由一系列语句构成:

fn main() {
    // 语句
    // 语句
}

Rust 有多种语句。最普遍的语句类型有两种:一种是声明绑定变量,另一种是表达式带上英文分号(😉:

fn main() {
    // 变量绑定
    let x = 5;

    // 表达式;
    x;
    x + 1;
    15;
}

代码块也是表达式,所以它们可以用作赋值中的值。代码块中的最后一个表达式将赋给适当的表达式,例如局部变量。但是,如果代码块的最后一个表达式结尾处有分号,则返回值为 ()(注:代码块中的最后一个语句是代码块中实际执行的最后一个语句,而不一定是代码块中最后一行的语句)。

fn main() {
    let x = 5u32;

    let y = {
        let x_squared = x * x;
        let x_cube = x_squared * x;

        // 将此表达式赋给 `y`
        x_cube + x_squared + x
    };

    let z = {
        // 分号结束了这个表达式,于是将 `()` 赋给 `z`
        2 * x;
    };

    println!("x is {:?}", x); // x is 5
    println!("y is {:?}", y); // y is 155
    println!("z is {:?}", z); // z is ()
}

函数

函数(function)使用 fn 关键字来声明。函数的参数需要标注类型,就和变量一样,如果函数返回一个值,返回类型必须在箭头 -> 之后指定。函数最后的表达式将作为返回值。也可以在函数内使用 return 语句来提前返一个值,甚至可以在循环或 if 内部使用。

Rust 代码中的函数和变量名使用下划线命名法(snake case,直译为蛇形命名法)规范风格。在下划线命名法中,所有字母都是小写并使用下划线分隔单词。

// 和 C/C++ 不一样,Rust 的函数定义位置是没有限制的
fn main() {
    // 我们可以在这里使用函数,后面再定义它
    fizzbuzz_to(100);
}

// 一个返回布尔值的函数
fn is_divisible_by(lhs: u32, rhs: u32) -> bool {
    // 边界情况,提前返回
    if rhs == 0 {
        return false;
    }

    // 这是一个表达式,可以不用 `return` 关键字
    lhs % rhs == 0
}

// 一个 “不” 返回值的函数。实际上会返回一个单元类型 `()`。
fn fizzbuzz(n: u32) -> () {
    if is_divisible_by(n, 15) {
        println!("fizzbuzz");
    } else if is_divisible_by(n, 3) {
        println!("fizz");
    } else if is_divisible_by(n, 5) {
        println!("buzz");
    } else {
        println!("{}", n);
    }
}

// 当函数返回 `()` 时,函数签名可以省略返回类型
fn fizzbuzz_to(n: u32) {
    for n in 1..=n {
        fizzbuzz(n);
    }
}

流程控制

if-else

if-else 分支判断和其他语言类似。不同的是,Rust 语言中的布尔判断条件不必使用小括号包裹,且每个条件后面都跟着一个代码块。if-else 条件选择是一个表达式,并且所有分支都必须返回相同的类型。条件必须是 bool 值。如果条件不是 bool 值,我们将得到一个错误。

fn main() {
    let n = 5;
    if n < 0 {
        print!("{} is negative", n);
    } else if n > 0 {
        print!("{} is positive", n);
    } else {
        print!("{} is zero", n);
    }

    let big_n =
        if n < 10 && n > -10 {
            println!(", and is a small number, increase ten-fold");

            10 * n // 这个表达式返回一个 `i32` 类型。
        } else {
            println!(", and is a big number, half the number");

            n / 2 // 这个表达式也必须返回一个 `i32` 类型。
        };
    //   ^ 不要忘记在这里加上一个分号!所有的 `let` 绑定都需要它。
    println!("{} -> {}", n, big_n);

    let condition = true;
    let number = if condition { 5 } else { 6 };
}

loop 循环

Rust 提供了 loop 关键字来表示一个无限循环。可以使用 break 语句在任何时候退出一个循环,还可以使用 continue 跳过循环体的剩余部分并开始下一轮循环。

fn main() {
    let mut count = 0u32;
    println!("Let's count until infinity!");

    // 无限循环
    loop {
        count += 1;

        if count == 3 {
            println!("three");
            continue; // 跳过这次迭代的剩下内容
        }
        println!("{}", count);

        if count == 5 {
            println!("OK, that's enough");
            break; // 退出循环
        }
    }
}

嵌套循环和标签
在处理嵌套循环的时候可以 break 或 continue 外层循环。在这类情形中,循环必须用一些 'label 来注明,并且标签必须传递给 break/continue 语句。

fn main(){
    let mut count = 0;

    // 指定循环标签(loop label)
    'counting_up: loop {
        println!("count = {}", count);
        let mut remaining = 10;

        loop {
            println!("remaining = {}", remaining);
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up; // 结束指定的循环
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {}", count);
}

从循环返回
loop 有个用途是尝试一个操作直到成功为止。若操作返回一个值,则可能需要将其传递给代码的其余部分:将该值放在 break 之后,它就会被 loop 表达式返回。

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;  // 返回值
        }
    };

    println!("The result is {}", result);
}

while 循环

while 关键字可以用作条件循环(当条件满足时循环)。

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

for 循环

for 与区间

for in 结构可以遍历一个 Iterator(迭代器)。创建迭代器的一个最简单的方法是使用区间标记 a..b。这会生成从 a(包含此值) 到 b(不含此值)的,步长为 1 的一系列值。

fn main() {
    // 不包含结尾值
    for number in (1..4) {
        println!("{}!", number);
    }

    // 包含结尾值
    for number in 1..=100 {
        println!("{}!", number);
    }

    let a = [10, 20, 30, 40, 50];
    for element in a {
        println!("the value is: {}", element);
    }
}
for 与迭代器

for in 结构能以几种方式与 Iterator 互动。如果没有特别指定,for 循环会对给出的集合应用 into_iter() 函数,把它转换成一个迭代器。into_iter() - 会消耗集合。在每次迭代中,集合中的数据本身会被提供。一旦集合被消耗了,之后就无法再使用了,因为它已经在循环中被 “移除”了。

fn main() {
    let names = vec!["Bob", "Frank", "Ferris"];

    for name in names.into_iter() {
        match name {
            "Ferris" => println!("There is a rustacean among us!"),
            _ => println!("Hello {}", name),
        }
    }
}

iter - 在每次迭代中借用集合中的一个元素。这样集合本身不会被改变,循环之后仍可以使用。

fn main() {
    let names = vec!["Bob", "Frank", "Ferris"];

    for name in names.iter() {
        match name {
            &"Ferris" => println!("There is a rustacean among us!"),
            _ => println!("Hello {}", name),
        }
    }
}

iter_mut - 可变地(mutably)借用集合中的每个元素,从而允许集合被就地修改。

fn main() {
    let mut names = vec!["Bob", "Frank", "Ferris"];

    for name in names.iter_mut() {
        *name = match name {
            &mut "Ferris" => "There is a rustacean among us!",
            _ => "Hello",
        }
    }
    println!("names: {:?}", names);
}

match 匹配

Rust 通过 match 关键字来提供模式匹配,和 C 语言的 switch 用法类似。第一个匹配分支会被比对,并且所有可能的值都必须被覆盖。

fn main() {
    let number = 13;

    println!("Tell me about {}", number);
    match number {
        // 匹配单个值
        1 => println!("One!"),
        // 匹配多个值
        2 | 3 | 5 | 7 | 11 | 13 => println!("This is a prime"),
        // 匹配一个闭区间范围
        13..=19 => println!("A teen"),
        // 处理其他情况,匹配任意值而不绑定到该值
        _ => println!("Ain't special"),
    }

    let boolean = true;
    // match 也是一个表达式
    let binary = match boolean {
        // match 分支必须覆盖所有可能的值
        false => 0,
        true => 1,
    };
    println!("{} -> {}", boolean, binary);
}

元组

fn main() {
    let triple = (0, -2, 3);

    // match 可以解构一个元组
    match triple {
        // 解构出第二个和第三个元素
        (0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z),
        (1, ..)  => println!("First is `1` and the rest doesn't matter"),
        // `..` 可用来忽略元组的其余部分
        _      => println!("It doesn't matter what they are"),
        // `_` 表示不将值绑定到变量
    }
}

枚举


enum Color {
    // 这三个取值仅由它们的名字(而非类型)来指定。
    Red,
    Blue,
    Green,
    // 这些则把 `u32` 元组赋予不同的名字,以色彩模型命名。
    RGB(u32, u32, u32),
    HSV(u32, u32, u32),
    HSL(u32, u32, u32),
    CMY(u32, u32, u32),
    CMYK(u32, u32, u32, u32),
}

fn main() {
    let color = Color::RGB(122, 17, 40);

    // 可以使用 `match` 来解构 `enum`。
    match color {
        Color::Red   => println!("The color is Red!"),
        Color::Blue  => println!("The color is Blue!"),
        Color::Green => println!("The color is Green!"),
        Color::RGB(r, g, b) =>
            println!("Red: {}, green: {}, and blue: {}!", r, g, b),
        Color::HSV(h, s, v) =>
            println!("Hue: {}, saturation: {}, value: {}!", h, s, v),
        Color::HSL(h, s, l) =>
            println!("Hue: {}, saturation: {}, lightness: {}!", h, s, l),
        Color::CMY(c, m, y) =>
            println!("Cyan: {}, magenta: {}, yellow: {}!", c, m, y),
        Color::CMYK(c, m, y, k) =>
            println!("Cyan: {}, magenta: {}, yellow: {}, key (black): {}!",
                c, m, y, k),
        // 不需要其它分支,因为所有的情形都已覆盖
    }
}

指针和引用
对指针来说,解构(destructure)和解引用(dereference)要区分开,因为这两者的概念是不同的。

  • 解引用使用 *
  • 解构使用 &ref、和 ref mut
fn main() {
    // 获得一个 `i32` 类型的引用。`&` 表示取引用。
    let reference = &4;

    match reference {
        // 如果用 `&val` 这个模式去匹配 `reference`,`val` 表示被 `reference` 引用的值 4。
        &val => println!("Got a value via destructuring: {:?}", val),
    }

    // 如果不想用 `&`,需要在匹配前解引用。
    match *reference {
        val => println!("Got a value via dereferencing: {:?}", val),
    }

    // 不用引用
    let _not_a_reference = 3;

    // `ref`更改了赋值行为,从而可以对具体值创建引用。下面这行将得到一个引用。
    let ref _is_a_reference = 3;

    // 相应地,定义两个非引用的变量,通过 `ref` 和 `ref mut` 仍可取得其引用。
    let value = 5;
    let mut mut_value = 6;

    // 使用 `ref` 关键字来创建引用
    match value {
        ref r => {
            println!("Got a reference to a value: {:?}", r)
            println!("Got a reference to a value: {:?}", *r)
        },
    }

    // 类似地使用 `ref mut`
    match mut_value {
        ref mut m => {
            // 已经获得了 `mut_value` 的引用,先要解引用,才能改变它的值。
            *m += 10;
            println!("We added 10. `mut_value`: {:?}", m);
        },
    }
}

卫语句
可以加上 match 卫语句来过滤分支。

fn main() {
    let pair = (2, -2);

    match pair {
        (x, y) if x == y => println!("These are twins"),
        // ^ `if` 条件部分是一个卫语句
        (x, y) if x + y == 0 => println!("Antimatter, kaboom!"),
        (x, _) if x % 2 == 1 => println!("The first one is odd"),
        _ => println!("No correlation..."),
    }
}

绑定
在 match 中,若间接地访问一个变量,则不经过重新绑定就无法在分支中再使用它。match 提供了 @ 符号来绑定变量到名称:

// `age` 函数,返回一个 `u32` 值。
fn age() -> u32 {
    15
}

fn main() {
    match age() {
        0             => println!("I haven't celebrated my first birthday yet"),
        // 在 1 ..= 12 分支中绑定匹配值到 `n` 。现在年龄就可以读取了。
        n @ 1  ..= 12 => println!("I'm a child of age {:?}", n),
        n @ 13 ..= 19 => println!("I'm a teen of age {:?}", n),
        // 不符合上面的范围。返回结果。
        n             => println!("I'm an old person of age {:?}", n),
    }
}

你也可以使用绑定来“解构” enum 变体,例如 Option:

fn some_number() -> Option<u32> {
    Some(42)
}

fn main() {
    match some_number() {
        // 得到 `Some` 可变类型,如果它的值(绑定到 `n` 上)等于 42,则匹配。
        Some(n @ 42) => println!("The Answer: {}!", n),
        // 匹配任意其他数字。
        Some(n) => println!("Not interesting... {}", n),
        // 匹配任意其他值(`None` 可变类型)。
        _ => (),
    }
}

if let

if let 语法以一种简洁的方式来处理只匹配一个模式的值而忽略其他模式的情况。if let 在这样的场合要简洁得多,并且允许指明数种失败情形下的选项:

fn main() {
    // 全部都是 `Option<i32>` 类型
    let number = Some(7);
    let letter: Option<i32> = None;
    let emoticon: Option<i32> = None;

    // `if let` :若 `let` 将 `number` 解构成 `Some(i)`,则执行语句块
    if let Some(i) = number {
        println!("Matched {:?}!", i);
    }

    // 如果要指明失败情形,就使用 else:
    if let Some(i) = letter {
        println!("Matched {:?}!", i);
    } else {// 解构失败。切换到失败情形。
        println!("Didn't match a number. Let's go with a letter!");
    };

    // 提供另一种失败情况下的条件。
    let i_like_letters = false;
    if let Some(i) = emoticon {
        println!("Matched {:?}!", i);
    // 解构失败。使用 `else if` 来判断是否满足上面提供的条件。
    } else if i_like_letters {
        println!("Didn't match a number. Let's go with a letter!");
    } else {
        // 条件的值为 false。于是以下是默认的分支:
        println!("I don't like letters. Let's go with an emoticon :)!");
    };
}

同样,可以用 if let 匹配任何枚举值:

enum Foo {
    Bar,
    Baz,
    Qux(u32)
}

fn main() {
    // 创建变量
    let a = Foo::Bar;
    let b = Foo::Baz;
    let c = Foo::Qux(100);

    // 变量 a 匹配到了 Foo::Bar
    if let Foo::Bar = a {
        println!("a is foobar");
    }

    // 变量 b 没有匹配到 Foo::Bar,因此什么也不会打印。
    if let Foo::Bar = b {
        println!("b is foobar");
    }

    // 变量 c 匹配到了 Foo::Qux,它带有一个值,就和上面例子中的 Some() 类似。
    if let Foo::Qux(value) = c {
        println!("c is {}", value);
    }
}

while let

和 if let 类似,while let 也可以把别扭的 match 改写得好看一些。

fn main() {
    // 将 `optional` 设为 `Option<i32>` 类型
    let mut optional = Some(0);

    // 当 `let` 将 `optional` 解构成 `Some(i)` 时,就执行语句块。否则就 `break`。
    while let Some(i) = optional {
        if i > 9 {
            println!("Greater than 9, quit!");
            optional = None;
        } else {
            println!("`i` is `{:?}`. Try again.", i);
            optional = Some(i + 1);
        }
    }
    // ^ `if let` 有可选的 `else`/`else if` 分句,而 `while let` 没有。
}

格式化输出

打印操作由 std::fmt 里面所定义的一系列宏来处理,包括:

  • format!:将格式化文本写到字符串。
  • print!:与 format! 类似,但将文本输出到控制台(io::stdout)。
  • println!: 与 print! 类似,但输出结果追加一个换行符。
  • eprint!:与 print! 类似,但将文本输出到标准错误(io::stderr)。
  • eprintln!:与 eprint! 类似,但输出结果追加一个换行符。
fn main() {
    println!("Hello");

    // 通常情况下,`{}` 会被任意变量内容所替换。 变量内容会转化成字符串。
    println!("{} days", 31); // "31 days"

    // 用变量替换字符串有多种写法。比如可以使用位置参数。
    println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); // "Alice, this is Bob. Bob, this is Alice"

    // 可以使用命名参数。
    println!("{subject} {verb} {object}",
             object="the lazy dog",
             subject="the quick brown fox",
             verb="jumps over"); // "the quick brown fox jumps over the lazy dog"

    // 可以在 `:` 后面指定特殊的格式。
    println!("{} of {:b} people know binary, the other half don't", 1, 2); // "1 of 10 people know binary, the other half don't"

    // 可以按指定宽度来右对齐文本。下面语句输出 "     1",5 个空格后面连着 1。
    println!("{number:>width$}", number=1, width=6); // "     1"

    // 可以在数字左边补 0
    println!("{number:>0width$}", number=1, width=6); // "000001"
    println!("{:04}", 42);             // "0042"

    // 指定小数位数
    println!("Pi is roughly {number:.prec$}", prec = 3, number = 3.141592); // "Pi is roughly 3.142"

    // println! 会检查使用到的参数数量是否正确。
    println!("My name is {0}, {1} {0}", "Bond","James"); // "My name is Bond, James Bond"

    let people = "Rustaceans";
    println!("Hello {people}!");       // "Hello Rustaceans!"

    println!("{:?}", (3, 4)); // "(3, 4)"
    println!("{:#?}", (100, 200));  // "(
                                        // 100,
                                        //  200, )"

    // 创建一个包含单个 `i32` 的结构体(structure)。命名为 `Structure`。
    #[allow(dead_code)]
    struct Structure(i32);

    // 但是像结构体这样的自定义类型需要更复杂的方式来处理下面语句无法运行。
    // println!("This struct `{}` won't print...", Structure(3));
}

std::fmt 包含多种 trait 来控制文字显示,其中重要的两种 trait 的基本形式如下:

  • fmt::Debug:使用 {:?} 标记。格式化文本以供调试使用。
  • fmt::Display:使用 {} 标记。以更优雅和友好的风格来格式化文本。

上例使用了 fmt::Display,因为标准库提供了那些类型的实现。若要打印自定义类型的文本,需要更多的步骤。

调试(Debug)

所有的类型,若想用 std::fmt 的格式化打印,都要求实现至少一个可打印的 traits。 自动的实现只为一些类型提供,比如 std 库中的类型。所有其他类型 都必须手动实现。

fmt::Debug 这个 trait 使这项工作变得相当简单。所有类型都能推导(derive,即自动创建)fmt::Debug 的实现。但是 fmt::Display 需要手动实现。


fn main() {
    // 这个结构体不能使用 `fmt::Display` 或 `fmt::Debug` 来进行打印。
    struct UnPrintable(i32);

    // `derive` 属性会自动创建所需的实现,使这个 `struct` 能使用 `fmt::Debug` 打印。
    #[derive(Debug)]
    struct DebugPrintable(i32);

    // 推导 `Structure` 的 `fmt::Debug` 实现。`Structure` 是一个包含单个 `i32` 的结构体。
    #[derive(Debug)]
    struct Structure(i32);

    // 将 `Structure` 放到结构体 `Deep` 中。然后使 `Deep` 也能够打印。
    #[derive(Debug)]
    struct Deep(Structure);

    // 使用 `{:?}` 打印和使用 `{}` 类似。
    println!("{:?} months in a year.", 12);
    println!("{1:?} {0:?} is the {actor:?} name.",
             "Slater",
             "Christian",
             actor="actor's");

    // `Structure` 也可以打印!
    println!("Now {:?} will print!", Structure(3));

    // 使用 `derive` 的一个问题是不能控制输出的形式。假如我只想展示一个 `7` 怎么办?
    println!("Now {:?} will print!", Deep(Structure(7)));

    #[derive(Debug)]
    struct Person<'a> {
        name: &'a str,
        age: u8
    }
    let name = "Peter";
    let age = 27;
    let peter = Person { name, age };
    // 美化打印
    println!("{:#?}", peter);
}

显示(Display)

fmt::Debug 通常看起来不太简洁,因此自定义输出的外观经常是更可取的。这需要通过 手动实现 fmt::Display 来做到。fmt::Display 采用 {} 标记。实现方式看 起来像这样:

// (使用 `use`)导入 `fmt` 模块使 `fmt::Display` 可用
use std::fmt;

// 定义一个结构体 `Structure`,包含一个 `i32` 元素。
struct Structure(i32);

// 为了使用 `{}` 标记,必须手动为类型实现 `fmt::Display` trait。
impl fmt::Display for Structure {
    // 这个 trait 要求 `fmt` 使用与下面的函数完全一致的函数签名
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 仅将 self 的第一个元素写入到给定的输出流 `f`。返回 `fmt:Result`,此结果表明操作成功或失败。
        write!(f, "{}", self.0)
    }
}

fmt::Display 的效果可能比 fmt::Debug 简洁。对于 Vec<T> 或其他任意泛型容器(generic container),fmt::Display 都没有 实现。因此在这些泛型的情况下要用 fmt::Debug

这并不是一个问题,因为对于任何非泛型的容器类型, fmt::Display 都能够实 现。

对一个结构体实现 fmt::Display,其中的元素需要一个接一个地处理到,这可能会很麻 烦。问题在于每个 write! 都要生成一个 fmt::Result。正确的实现需要 处理所有的 Result。Rust 专门为解决这个问题提供了 ? 操作符。

use std::fmt; // 导入 `fmt` 模块。

// 定义一个包含单个 `Vec` 的结构体 `List`。
struct List(Vec<i32>);

impl fmt::Display for List {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 使用元组的下标获取值,并创建一个 `vec` 的引用。
        let vec = &self.0;

        // 对 `write!` 进行尝试,观察是否出错。若发生错误,返回相应的错误。否则(没有出错)继续执行后面的语句。
        write!(f, "[")?;

        // 使用 `v` 对 `vec` 进行迭代,并用 `count` 记录迭代次数。
        for (count, v) in vec.iter().enumerate() {
            // 对每个元素(第一个元素除外)加上逗号。使用 `?` 来返回错误。
            if count != 0 { write!(f, ", ")?; }
            write!(f, "{}:{}",count, v)?;
        }

        // 加上配对中括号,并返回一个 fmt::Result 值。
        write!(f, "]")
    }
}

fn main() {
    let v = List(vec![1, 2, 3]);
    println!("{}", v); // [0:1, 1:2, 2:3]
}

格式化的方式是通过格式字符串来指定的:

  • format!("{}", foo) -> "3735928559"
  • format!("0x{:X}", foo) -> "0xDEADBEEF"
  • format!("0o{:o}", foo) -> "0o33653337357"

根据使用的参数类型是 X、o 还是未指定,同样的变量(foo)能够格式化 成不同的形式。

use std::fmt::{self, Formatter, Display};

struct City {
    name: &'static str,
    lat: f32,// 纬度
    lon: f32,// 经度
}

impl Display for City {
    // `f` 是一个缓冲区(buffer),此方法必须将格式化后的字符串写入其中
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let lat_c = if self.lat >= 0.0 { 'N' } else { 'S' };
        let lon_c = if self.lon >= 0.0 { 'E' } else { 'W' };

        // `write!` 和 `format!` 类似,但它会将格式化后的字符串写入一个缓冲区(即第一个参数f)中。
        write!(f, "{}: {:.3}°{} {:.3}°{}", self.name, self.lat.abs(), lat_c, self.lon.abs(), lon_c)
    }
}

struct Color {
    red: u8,
    green: u8,
    blue: u8,
}

impl Display for Color {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "RGB ({}, {}, {}) 0x{:02X}{:02X}{:02X}", self.red, self.green, self.blue, self.red, self.green, self.blue)
    }
}

fn main() {
    for city in [
        City { name: "Dublin", lat: 53.347778, lon: -6.259722 },
        City { name: "Oslo", lat: 59.95, lon: 10.75 },
        City { name: "Vancouver", lat: 49.25, lon: -123.1 },
    ].iter() {
        println!("{}", *city);
    }
    for color in [
        Color { red: 128, green: 255, blue: 90 },
        Color { red: 0, green: 3, blue: 254 },
        Color { red: 0, green: 0, blue: 0 },
    ].iter() {
        println!("{}", *color)
    }
}
posted @ 2025-07-21 09:57  carol2014  阅读(23)  评论(0)    收藏  举报