Rust Programming :The Complete Developor‘s Guide--02 Working With Data

Enumeration

  • Data that can be one of multiple different possibilities
    • Each possibiliy is called a “variant”
  • Provides information about your program to the compiler
    • More robust programs
Recap
  • Enum can only be one vatiant at a time
  • More robust programs when paired with match
  • Make program code easier to read
Demo: enum
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

fn main() {
    let go = Direction::Right;
    match go {
        Direction::Up => println!("go up"),
        Direction::Down => println!("go down"),
        Direction::Left => println!("go left"),
        Direction::Right => println!("go right"),
    }
}

Structure

  • A type that contains multiple pieces of fata
    • All or nothing - cannot have same pieces of data and not others
  • Each pieces of data is called a “field”
  • Makes working with data easier
    • Similar data can be grouped together
Recap
  • Structure deal with multiple pieces of data
  • All fields must be present to create a struct
  • Fields can be accessed using a dot (.)
Demo : Structure
struct GroceryItem {
    stock: i32,
    price: f64,
}

fn main() {
    let cereal = GroceryItem {
        stock: 10,
        price: 2.99,
    };

    println!("stock:{:?}", cereal.stock);
    println!("price:{:?}", cereal.price);
}
enum Flavor {
    Sparkling,
    Sweet,
    Fruity,
}

struct Drink {
    flavor: Flavor,
    fluid_oz: f64,
}

fn print_drink(drink: Drink) {
    match drink.flavor {
        Flavor::Sparkling => {
            println!("sparkling");
        }
        Flavor::Sweet => {
            println!("sweet");
        }
        Flavor::Fruity => {
            println!("fruity");
        }
    }

    println!("oz:{:?}", drink.fluid_oz);
}

fn main() {
    let sweet = Drink {
        flavor: Flavor::Sweet,
        fluid_oz: 6.0,
    };

    print_drink(sweet);
}

Tuples

  • A type of “record”
  • Store data anonymously,No need to name fields
  • Useful to return pairs of data from functions
  • Can be “destructured” easily into variables
enum Access {
    Full,
}

fn one_two_three() -> (i32, i32, i32) {
    (1, 2, 3)
}

fn main() {
    let numbers = one_two_three();
    let (x, y, z) = one_two_three();

    println!("{:?}, {:?}", x, numbers.0);
    println!("{:?}, {:?}", y, numbers.1);
    println!("{:?}, {:?}", z, numbers.2);

    let (employee, access) = ("Jake", Access::Full);
}
Recap
  • Allow for anonymous data access
  • Useful when destructuring information into multiple variables
  • Can contain any number of fields
    • Use struct when more than 2 or 3 fields
Demo: Tuples
fn main() {
    let coord = (2, 3);
    println!("{:?}, {:?}", coord.0, coord.1);

    let (x, y) = (2, 3);
    println!("{:?}, {:?}", x, y);

    let user_info = ("Emma", 20);
    let (name, age) = user_info;
    println!("name:{:?}, age:{:?}", name, age);
}

Expressions

  • Rust is an expression-based language
    • Most things are evaluated and return some value
  • Expresion values coalesce to a single point
    • Can be used for nesting logic
Recap
  • Expressions allow nested logic
  • if and match expressions can be nested
    • Best to not use more than two or three levels
Demo expression
enum Menu {
    Burger,
    Fries,
    Drink,
}

fn main() {
    let my_num = 3;
    let is_lt_t = if my_num < 5 { true } else { false };
    let message = match my_num {
        1 => "hello",
        _ => "goodbye",
    };

    let item = Menu::Drink;
    let drink_type = "water";
    let order_placed = match item {
        Menu::Drink => {
            if drink_type == "water" {
                true
            } else {
                false
            }
        }
        _ => true,
    };
}

Fundamentals : Intermediate Memory

Basic memory refresh
  • Memory is stored using binary
    • Bits: 0 or 1
  • Computer optimized for bytes
    • 1byte == 8 contiguous bits
  • Fully contiguous
Addresses
  • All data in memory has an “address”
    • Used to locate data
    • Always the same -only data changes
  • Usually don’t utilize addresses directly
    • Variables handle most of the work
Offsets
  • Items can be located at an address using an “offset”
  • Offsets begin at 0
  • Represent the number of bytes aways from the original address
    • Normally deal with indexes instead
                offset       - = 1 byte
              0  1  2  3
address  0    -  -  -  -
         4    -  +  -  -     + address 4, offset 1
         8    -  -  -  -
         12   -  -  -  -
         16   -  -  -  -
Recap
  • Memory uses addresses & offsets
  • Addresses are permanent, data differs
  • Offsets can be used to “index” into some data

Fundamentals : Ownership

Managing memory
  • Programs must track memory
    • If they fail to do so, a “leak” occurs
  • Rust utilizes an “ownership” model to manage memory
    • The “owner” of memory is responsible for cleaning up the memory
  • Memory can either be “moved” or “borrowed”
Example - Move
enum Light {
    Bright,
    Dull,
}

fn display_light(light: Light) {
    match light {
        Light::Bright => println!("bright"),
        Light::Dull => println!("dull"),
    }
}

fn main() {
    let dull = Light::Dull;
    display_light(dull);    // value moved here
    // display_light(dull);  //Error value used here after move
}
Example - Borrow
enum Light {
    Bright,
    Dull,
}

fn display_light(light: &Light) {
    match light {
        Light::Bright => println!("bright"),
        Light::Dull => println!("dull"),
    }
}

fn main() {
    let dull = Light::Dull;
    display_light(&dull);
    display_light(&dull);
}
Recap
  • Memory must be managed in some way to prevent leaks
  • Rust uses “ownership” to accomplish memory management
    • The “owner” of data must clean up the memory
    • This occurs automatically at the end of the scope
  • Default behavior is to “move” memory to a new owner
    • Use an ampersand (&) to allow code to “borrow” memory

Activity impl

enum Color {
    Brown,
    Red,
}

impl Color {
    fn print(&self) {
        match self {
            Color::Brown => println!("brown"),
            Color::Red => println!("red"),
        }
    }
}
struct Dimensions {
    width: f64,
    height: f64,
    depth: f64,
}

impl Dimensions {
    fn print(&self) {
        println!("width:{:?}", self.width);
        println!("height:{:?}", self.height);
        println!("depth:{:?}", self.depth);
    }
}
struct ShippingBox {
    color: Color,
    weight: f64,
    dimensions: Dimensions,
}

impl ShippingBox {
    fn new(weight: f64, color: Color, dimensions: Dimensions) -> Self {
        Self {
            weight,
            color,
            dimensions,
        }
    }

    fn print(&self) {
        self.color.print();
        self.dimensions.print();
        println!("weight:{:?}", self.weight);
    }
}
fn main() {
    let small_dimensions = Dimensions {
        width: 1.0,
        height: 2.0,
        depth: 3.0,
    };

    let small_box = ShippingBox::new(5.0, Color::Red, small_dimensions);
    small_box.print();
}

Structure

  • A type that contains multiple pieces of fata
    • All or nothing - cannot have same pieces of data and not others
  • Each pieces of data is called a “field”
  • Makes working with data easier
    • Similar data can be grouped together
Recap
  • Structure deal with multiple pieces of data
  • All fields must be present to create a struct
  • Fields can be accessed using a dot (.)
Demo : Structure
struct GroceryItem {
    stock: i32,
    price: f64,
}

fn main() {
    let cereal = GroceryItem {
        stock: 10,
        price: 2.99,
    };

    println!("stock:{:?}", cereal.stock);
    println!("price:{:?}", cereal.price);
}
enum Flavor {
    Sparkling,
    Sweet,
    Fruity,
}

struct Drink {
    flavor: Flavor,
    fluid_oz: f64,
}

fn print_drink(drink: Drink) {
    match drink.flavor {
        Flavor::Sparkling => {
            println!("sparkling");
        }
        Flavor::Sweet => {
            println!("sweet");
        }
        Flavor::Fruity => {
            println!("fruity");
        }
    }

    println!("oz:{:?}", drink.fluid_oz);
}

fn main() {
    let sweet = Drink {
        flavor: Flavor::Sweet,
        fluid_oz: 6.0,
    };

    print_drink(sweet);
}

Tuples

  • A type of “record”
  • Store data anonymously,No need to name fields
  • Useful to return pairs of data from functions
  • Can be “destructured” easily into variables
enum Access {
    Full,
}

fn one_two_three() -> (i32, i32, i32) {
    (1, 2, 3)
}

fn main() {
    let numbers = one_two_three();
    let (x, y, z) = one_two_three();

    println!("{:?}, {:?}", x, numbers.0);
    println!("{:?}, {:?}", y, numbers.1);
    println!("{:?}, {:?}", z, numbers.2);

    let (employee, access) = ("Jake", Access::Full);
}
Recap
  • Allow for anonymous data access
  • Useful when destructuring information into multiple variables
  • Can contain any number of fields
    • Use struct when more than 2 or 3 fields
Demo: Tuples
fn main() {
    let coord = (2, 3);
    println!("{:?}, {:?}", coord.0, coord.1);

    let (x, y) = (2, 3);
    println!("{:?}, {:?}", x, y);

    let user_info = ("Emma", 20);
    let (name, age) = user_info;
    println!("name:{:?}, age:{:?}", name, age);
}

Expressions

  • Rust is an expression-based language
    • Most things are evaluated and return some value
  • Expresion values coalesce to a single point
    • Can be used for nesting logic
Recap
  • Expressions allow nested logic
  • if and match expressions can be nested
    • Best to not use more than two or three levels
Demo expression
enum Menu {
    Burger,
    Fries,
    Drink,
}

fn main() {
    let my_num = 3;
    let is_lt_t = if my_num < 5 { true } else { false };
    let message = match my_num {
        1 => "hello",
        _ => "goodbye",
    };

    let item = Menu::Drink;
    let drink_type = "water";
    let order_placed = match item {
        Menu::Drink => {
            if drink_type == "water" {
                true
            } else {
                false
            }
        }
        _ => true,
    };
}

Fundamentals : Intermediate Memory

Basic memory refresh
  • Memory is stored using binary
    • Bits: 0 or 1
  • Computer optimized for bytes
    • 1byte == 8 contiguous bits
  • Fully contiguous
Addresses
  • All data in memory has an “address”
    • Used to locate data
    • Always the same -only data changes
  • Usually don’t utilize addresses directly
    • Variables handle most of the work
Offsets
  • Items can be located at an address using an “offset”
  • Offsets begin at 0
  • Represent the number of bytes aways from the original address
    • Normally deal with indexes instead
                offset       - = 1 byte
              0  1  2  3
address  0    -  -  -  -
         4    -  +  -  -     + address 4, offset 1
         8    -  -  -  -
         12   -  -  -  -
         16   -  -  -  -
Recap
  • Memory uses addresses & offsets
  • Addresses are permanent, data differs
  • Offsets can be used to “index” into some data

Fundamentals : Ownership

Managing memory
  • Programs must track memory
    • If they fail to do so, a “leak” occurs
  • Rust utilizes an “ownership” model to manage memory
    • The “owner” of memory is responsible for cleaning up the memory
  • Memory can either be “moved” or “borrowed”
Example - Move
enum Light {
    Bright,
    Dull,
}

fn display_light(light: Light) {
    match light {
        Light::Bright => println!("bright"),
        Light::Dull => println!("dull"),
    }
}

fn main() {
    let dull = Light::Dull;
    display_light(dull);    // value moved here
    // display_light(dull);  //Error value used here after move
}
Example - Borrow
enum Light {
    Bright,
    Dull,
}

fn display_light(light: &Light) {
    match light {
        Light::Bright => println!("bright"),
        Light::Dull => println!("dull"),
    }
}

fn main() {
    let dull = Light::Dull;
    display_light(&dull);
    display_light(&dull);
}
Recap
  • Memory must be managed in some way to prevent leaks
  • Rust uses “ownership” to accomplish memory management
    • The “owner” of data must clean up the memory
    • This occurs automatically at the end of the scope
  • Default behavior is to “move” memory to a new owner
    • Use an ampersand (&) to allow code to “borrow” memory

Demo impl

Working With Data Option

  • A type that may be one of two things
    • Some data of a specified type
    • Nothing
  • Used in scenarios where data may not required or is unavailable
    • Unable to find something
    • Ran out of items in a list
    • Form field not filled out
Definition
enum Option<T> {
    Some(T),
    None
}
Example
struct Customer {
    age: Option<i32>,
    email: String,
}

fn main() {
    let mark = Customer {
        age: Some(22),
        email: "mark@example.com".to_owned(),
    };
    let becky = Customer {
        age: None,
        email: "becky@example.com".to_owned(),
    };

    match becky.age {
        Some(age) => println!("customer is {:?} years old", age),
        None => println!("customer age not provided"),
    }
}
Recap
  • Option represents either some data or nothing
    • Some(variabkle_name)
      • Data is available
    • None
      • No data is available
  • Useful when needing to work with optional data
  • Use Option to declare an optional type

Activity Option

struct Student {
    name: String,
    locker: Option<i32>,
}

fn main() {
    let mary = Student {
        name: "Mary".to_owned(),
        locker: Some(3),
    };

    println!("student: {:?}", mary.name);
    match mary.locker {
        Some(num) => println!("locker number is:  {:?}", num),
        None => println!("no locker assigned"),
    }
}

Demo Documentation

/// A favorite color
enum Color {
    Red,
    Blue,
}

/// A piece of mail
struct Mail {
    /// The destination address
    address: String,
}

/// Add two numbers together
fn add(a:i32, b:i32) ->i32 {
    a + b
}
fn main() {

}
cargo doc --open

Demo Standard Library

rustup doc

Working With Data Result

  • A data type that contains one of two types of data
    • “Successful” data
    • “Error” data
  • Used in scenarios where an action needs to be taken, but has the possibility of failure
    • Copying a file
    • Connecting to a website
enum Result<T, E> {
    OK(T),
    Err(E),
}
Recap
  • Result represents either success or failure
    • OK(variable_name)
      • The operation was completed
    • Error(variable_name)
      • The operation failed
  • Useful when working with functionality that can potentially fail
  • Use Result<T, E> when working with results
Demo Result
#[derive(Debug)]
enum MenuChoice {
    MainMenu,
    Start,
    Quit,
}

fn get_choice(input: &str) -> Result<MenuChoice, String> {
    match input {
        "mainmenu" => Ok(MenuChoice::MainMenu),
        "start" => Ok(MenuChoice::Start),
        "quit" => Ok(MenuChoice::Quit),
        _ => Err("menu choice not found".to_owned()),
    }
}

fn print_choice(choice: &MenuChoice) {
    println!("choice = {:?}", choice);
}

fn pick_choice(input: &str) -> Result<(), String> {
    let choice:MenuChoice = get_choice(input)?;
    print_choice(&choice);
    Ok(())
}

fn main() {
    let choice = get_choice("mainmenu");
    match choice {
        Ok(inner_choice) => print_choice(&inner_choice),
        Err(err) => println!("{}", err),
    }

    pick_choice("start");

    let choice = pick_choice("end");
    println!("choice value = {:?}", choice);
}
Activity Result and the question mark operator
enum Position {
    Maintenance,
    Marketing,
    Manager,
    LineSupervisor,
    KitchenStaff,
    AssemblyTech,
}

enum Status {
    Active,
    Terminated,
}

struct Employee {
    position: Position,
    status: Status,
}

fn try_access(employee: &Employee) -> Result<(), String> {
    match employee.status {
        Status::Terminated => return Err("terminated".to_owned()),
        _ => (),
    }

    match employee.position {
        Position::Maintenance => Ok(()),
        Position::Marketing => Ok(()),
        Position::Manager => Ok(()),
        _ => Err("invalid position".to_owned()),
    }
}

fn print_access(employee: &Employee) -> Result<(), String> {
    let attemp_access = try_access(employee)?;
    println!("access ok");
    Ok(())
}

fn main() {
    let manager = Employee {
        position: Position::Manager,
        status: Status::Active,
    };

    match print_access(&manager) {
        Err(e) => println!("access denied: {:?}", e),
        _ => (),
    };
}

Data Structures Hashmap

  • Collection that stores data as key-value pairs
    • Data is located using the “key”
    • The data is the “value”
  • Similar to definitions in a dictionary
  • Very fast to retrieve data using the key
use std::collections::HashMap;

fn main() {
    let mut people = HashMap::new();
    people.insert("Susan", 21);
    people.insert("Ed", 13);
    people.insert("Will", 14);
    people.insert("Cathy", 22);
    people.remove("Susan");

    match people.get("Ed") {
        Some(age) => println!("age = {:?}", age),
        None => println!("not found"),
    }

    // iterate
    for (person, age) in people.iter() {
        println!("person = {:?} age = {:?}", person, age);
    }

    for person in people.keys() {
        println!("person = {:?}", person);
    }

    for age in people.values() {
        println!("age = {:?}", age);
    }
}
Demo Basic Closures
fn main() {
    let add = |a: i32, b: i32| -> i32 { a + b };
    let add = |a, b| a + b;
    let sum = add(2, 1);
}
Activity Map combinator
#[derive(Debug)]
struct User{
    user_id:i32,
    name: String,
}

fn find_user(name:&str) -> Option<i32> {
    let name = name.to_lowercase();
    match name.as_str() {
        "sam"=>Some(1),
        "matt"=>Some(5),
        "katie"=>Some(9),
        _=>None
    }
}
fn main() {
  let user_name = "sam";
  let user = find_user(user_name)
    .map(|user_id| 
        User{
            user_id: user_id, 
            name: user_name.to_owned()
        }
    );

    match user {
        Some(user)=> println!("{:?}", user),
        None => println!("user not found"),
    }
}
Demo Option Combinators
fn main() {
    let a:Option<i32> = Some(1);
    dbg!(a);

    let a_is_some = a.is_some();
    dbg!(a_is_some);

    let a_is_none = a.is_none();
    dbg!(a_is_none);

    let a_mapped = a.map(|num| num + 1);
    dbg!(a_mapped);

    let a_filtered = a.filter(|num| num == &1);
    dbg!(a_filtered);

    let a_or_else = a.or_else(|| Some(5));
    dbg!(a_or_else);

    let unwrapped = a.unwrap_or_else(||0);
    dbg!(unwrapped);
}
Activity Iterator
fn main() {
    let data: Vec<_> = vec![1, 2, 3, 4, 5]
        .into_iter()
        .map(|num| num * 3)
        .filter(|num| num > &10)
        .collect();

    for num in data {
        println!("{}", num);
    }
}
Demo Range
fn main() {
    let range = 1..=3;
    let range = 1..4;

    for num in 1..4 {
        println!("{:?}", num);
    }

    for ch in 'a'..='f' {
        println!("{:?}", ch);
    }
}
Demo if let
enum Color {
    Red,
    Blue,
    Green,
}

fn main() {
    let maybe_user = Some("jerry");
    if let Some(user) = maybe_user {
        println!("user={:?}", user);
    }

    let red = Color::Red;
    if let Color::Red = red {
        println!("its red");
    } else {
        println!("its not red");
    }
}
Demo while let
enum Color {
    Red,
    Blue,
    Green,
}

fn main() {
    let mut data = Some(3);
    while let Some(i) = data {
        println!("loop");
        data = None;
    }

    let numbers = vec![1, 2, 3, 4, 5];
    let mut number_iter = numbers.iter();
    while let Some(num) = number_iter.next() {
        println!("num = {:?}", num);
    }

    println!("done")
}
Demo Modules
mod greet {
    pub fn hello() {
        println!("Hello");
    }

    pub fn goodbye() {
        println!("goodbye");
    }
}

mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    pub fn sub(a: i32, b: i32) -> i32 {
        a - b
    }
}

fn main() {
    use greet::hello;
    hello();

    greet::goodbye();

    math::add(1, 2);
    math::sub(1, 2);
}
Activity Inline Modules
mod msg {
    pub fn trim(msg: &str) -> &str {
        msg.trim()
    }

    pub fn capitalize(msg: &str) -> std::borrow::Cow<'_, str> {
        if let Some(letter) = msg.get(0..1) {
            format!("{}{}", letter.to_uppercase(), &msg[1..msg.len()]).into()
        } else {
            msg.into()
        }
    }

    pub fn exciting(msg: &str) -> String {
        format!("{}", msg)
    }
}

mod math {
    pub fn add(lhs: isize, rhs: isize) -> isize {
        lhs + rhs
    }

    pub fn sub(lhs: isize, rhs: isize) -> isize {
        lhs - rhs
    }

    pub fn mul(lhs: isize, rhs: isize) -> isize {
        lhs * rhs
    }
}

fn main() {
    let result = {
        let two_plus_two = math::add(2, 2);
        let three = math::sub(two_plus_two, 1);
        math::mul(three, three)
    };

    assert_eq!(result, 9);
    println!("(2 + 2 - 1) * 3 = {}", result);
    {
        use msg::{capitalize, exciting, trim};

        let hello = {
            let msg = "hello";
            let msg = trim(msg);
            capitalize(msg)
        };

        let world = {
            let msg = "world";
            exciting(msg)
        };

        let msg = format!("{}, {}", hello, world);
        assert_eq!(&msg, "Hello, world");
        println!("{}", msg);
    }
}
Demo Testing
fn all_caps(word: &str) -> String {
    word.to_uppercase()
}

fn main() {}

#[cfg(test)]
mod test {
    use crate::*;
    #[test]
    fn check_all_caps() {
        let result = all_caps("hello");
        let expected = String::from("HELLO");

        assert_eq!(result, expected, "string should be all uppercase");
    }

    #[test]
    fn check_all_cap_test() {
        let result = all_caps("hello");
        let expected = String::from("world");

        assert_eq!(result, expected, "string should be all uppercase");
    }
}

运行 cargo test 查看测试结果

running 2 tests
test test::check_all_caps ... ok
test test::check_all_cap_test ... FAILED

failures:

---- test::check_all_cap_test stdout ----
thread 'test::check_all_cap_test' panicked at 'assertion failed: `(left == right)`
  left: `"HELLO"`,
 right: `"world"`: string should be all uppercase', src/main.rs:23:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    test::check_all_cap_test

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--bin rust_demo`
Activity Testing
/// ensures n is >= lower and <= upper
fn clamp(n: i32, lower: i32, upper: i32) -> i32 {
    if n < lower {
        lower
    } else if n > upper {
        upper
    } else {
        n
    }
}

/// divides a and b
fn div(a: i32, b: i32) -> Option<i32> {
    if b == 0 {
        None
    } else {
        Some(a / b)
    }
}

/// takes two strings and places them immediately one after another
fn concat(first: &str, second: &str) -> String {
    format!("{} {}", first, second)
}
fn main() {}

#[cfg(test)]
mod test {
    use crate::*;

    #[test]
    fn clamp_lower() {
        let result = clamp(10, 100, 1000);
        let expected = 100;
        assert_eq!(result, expected, "should be 100");
    }

    #[test]
    fn clamp_upper() {
        let result = clamp(5000, 100, 1000);
        let expected = 1000;
        assert_eq!(result, expected, "should be 500");
    }

    #[test]
    fn check_div() {
        let result = div(1, 1);
        let expected = Some(1);
        assert_eq!(result, expected, "should be 1");
    }

    #[test]
    fn check_div_zero() {
        let result = div(1, 0);
        let expected = None;
        assert_eq!(result, expected, "cannot divide zero");
    }

    #[test]
    fn check_concat() {
        let result = concat("a", "b");
        let expected = String::from("ab");
        assert_eq!(result, expected, "should be placed immediately adjacent");
    }
}
Demo External Crates

the dependency section (Cargo.toml) is where we will include our external crates

[dependencies]
humantime = "2.1.0"
use humantime::format_duration;
use std::time::Duration;

fn main() {
    let d = Duration::from_secs(9876);
    println!("{}", format_duration(d));
    let val1 = Duration::new(9420, 0);
    assert_eq!(format_duration(val1).to_string(), "2h 37m");
    let val2 = Duration::new(0, 32_000_000);
    assert_eq!(format_duration(val2).to_string(), "32ms");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值