Rust 结构体、枚举、模式匹配、Option 和 Result、包和模块

1. 结构体(Structs)深度解析

什么是结构体?

结构体是一种自定义数据类型,允许你将多个相关的值组合在一起,形成一个有意义的组合。

定义和使用结构体

// 1. 基本结构体定义
struct User {
    username: String,      // 字段:用户名
    email: String,         // 字段:邮箱
    sign_in_count: u64,    // 字段:登录次数
    active: bool,          // 字段:是否活跃
}

// 2. 创建结构体实例
fn create_user() {
    // 不可变实例
    let user1 = User {
        email: String::from("alice@example.com"),
        username: String::from("alice123"),
        active: true,
        sign_in_count: 1,
    };
    
    // 可变实例
    let mut user2 = User {
        email: String::from("bob@example.com"),
        username: String::from("bob456"),
        active: true,
        sign_in_count: 1,
    };
    
    // 修改可变实例的字段
    user2.sign_in_count = 2;
    user2.email = String::from("bob.new@example.com");
    
    println!("用户1邮箱: {}", user1.email);
    println!("用户2登录次数: {}", user2.sign_in_count);
}

字段初始化简写

fn build_user(email: String, username: String) -> User {
    // 当参数名与字段名相同时,可以简写
    User {
        email,        // 等同于 email: email
        username,     // 等同于 username: username
        active: true,
        sign_in_count: 1,
    }
}

结构体更新语法

fn update_user() {
    let user1 = User {
        email: String::from("user1@example.com"),
        username: String::from("user1"),
        active: true,
        sign_in_count: 5,
    };
    
    // 使用结构体更新语法创建新实例
    let user2 = User {
        email: String::from("user2@example.com"),
        username: String::from("user2"),
        ..user1  // 其余字段使用 user1 的值
    };
    
    // user2 的 active 和 sign_in_count 与 user1 相同
    println!("user2活跃状态: {}", user2.active); // true
    println!("user2登录次数: {}", user2.sign_in_count); // 5
}

元组结构体

// 元组结构体 - 有名字的元组
struct Color(i32, i32, i32);      // RGB颜色
struct Point(i32, i32, i32);      // 3D坐标

fn use_tuple_structs() {
    let black = Color(0, 0, 0);
    let white = Color(255, 255, 255);
    let origin = Point(0, 0, 0);
    
    // 通过索引访问
    println!("黑色 R: {}, G: {}, B: {}", black.0, black.1, black.2);
    println!("原点坐标: ({}, {}, {})", origin.0, origin.1, origin.2);
    
    // 模式匹配解构
    let Color(r, g, b) = white;
    println!("白色 RGB: {}, {}, {}", r, g, b);
}

类单元结构体

// 没有任何字段的结构体,用于需要在类型上实现trait但不需要数据的情况
struct Marker;
struct ClickEvent;

fn use_unit_structs() {
    let marker = Marker;
    let click = ClickEvent;
    
    // 这些结构体不包含数据,只表示类型
}

结构体方法详解

// 为 User 结构体实现方法
impl User {
    // 关联函数(类似静态方法) - 没有 self 参数
    fn new(username: String, email: String) -> User {
        User {
            username,
            email,
            active: true,
            sign_in_count: 0,
        }
    }
    
    // 不可变引用方法 - &self
    fn get_info(&self) -> String {
        format!("用户: {} <{}>", self.username, self.email)
    }
    
    // 检查用户是否活跃
    fn is_active(&self) -> bool {
        self.active
    }
    
    // 可变引用方法 - &mut self
    fn update_email(&mut self, new_email: String) {
        self.email = new_email;
        self.sign_in_count += 1; // 每次更新都算一次活动
    }
    
    // 获取所有权的方法 - self
    fn deactivate(self) -> String {
        format!("用户 {} 已被停用", self.username)
        // 这里 self 被消耗,之后不能再使用
    }
}

// 可以有多个 impl 块
impl User {
    fn increment_sign_in(&mut self) {
        self.sign_in_count += 1;
    }
}

fn use_methods() {
    let mut user = User::new(
        String::from("charlie"),
        String::from("charlie@example.com")
    );
    
    // 调用方法
    println!("{}", user.get_info());
    println!("是否活跃: {}", user.is_active());
    
    user.update_email(String::from("charlie.new@example.com"));
    user.increment_sign_in();
    
    let message = user.deactivate(); // user 的所有权被移动
    println!("{}", message);
    // 这里不能再使用 user
}

2. 枚举(Enums)深度解析

基本枚举

// 简单枚举
enum Direction {
    North,
    South,
    East,
    West,
}

// 带数据的枚举
enum WebEvent {
    PageLoad,                    // 单元变体
    KeyPress(char),              // 元组变体
    Paste(String),               // 元组变体
    Click { x: i64, y: i64 },    // 结构体变体
}

枚举的使用

fn use_enums() {
    // 创建枚举实例
    let direction = Direction::North;
    let event1 = WebEvent::PageLoad;
    let event2 = WebEvent::KeyPress('a');
    let event3 = WebEvent::Paste(String::from("hello"));
    let event4 = WebEvent::Click { x: 100, y: 200 };
    
    // 处理枚举
    match direction {
        Direction::North => println!("向北"),
        Direction::South => println!("向南"),
        Direction::East => println!("向东"),
        Direction::West => println!("向西"),
    }
}

枚举方法

impl WebEvent {
    fn description(&self) -> String {
        match self {
            WebEvent::PageLoad => String::from("页面加载"),
            WebEvent::KeyPress(c) => format!("按键: {}", c),
            WebEvent::Paste(s) => format!("粘贴: {}", s),
            WebEvent::Click { x, y } => format!("点击位置: ({}, {})", x, y),
        }
    }
    
    fn is_user_action(&self) -> bool {
        match self {
            WebEvent::PageLoad => false,
            _ => true,  // 其他都是用户操作
        }
    }
}

fn use_enum_methods() {
    let events = [
        WebEvent::PageLoad,
        WebEvent::KeyPress('x'),
        WebEvent::Click { x: 50, y: 75 },
    ];
    
    for event in events.iter() {
        println!("事件: {}, 用户操作: {}", 
                 event.description(), 
                 event.is_user_action());
    }
}

3. 模式匹配(Pattern Matching)深度解析

match 表达式

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState), // 携带状态的25美分
}

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    California,
    // ... 其他州
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("幸运硬币!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("来自 {:?} 州的25美分", state);
            25
        }
    }
}

fn comprehensive_matching() {
    let coin = Coin::Quarter(UsState::California);
    let value = value_in_cents(coin);
    println!("硬币价值: {} 美分", value);
}

匹配 Option

fn handle_option() {
    let some_number = Some(5);
    let no_number: Option<i32> = None;
    
    // 匹配 Some 和 None
    match some_number {
        Some(i) => println!("有值: {}", i),
        None => println!("没有值"),
    }
    
    match no_number {
        Some(i) => println!("有值: {}", i),
        None => println!("没有值"),
    }
    
    // 更复杂的匹配
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }
    
    let six = plus_one(Some(5));
    let none = plus_one(None);
    
    println!("plus_one(Some(5)) = {:?}", six);   // Some(6)
    println!("plus_one(None) = {:?}", none);     // None
}

通配模式和 _ 占位符

fn catch_all_patterns() {
    let dice_roll = 9;
    
    match dice_roll {
        3 => println!("移动3格"),
        7 => println!("幸运7,再掷一次"),
        other => println!("移动 {} 格", other), // 捕获所有其他值
    }
    
    // 使用 _ 忽略值
    match dice_roll {
        1 => println!("蛇梯棋,上梯子"),
        6 => println!("蛇梯棋,下梯子"),
        _ => (), // 什么都不做
    }
    
    // 匹配范围
    let age = 25;
    match age {
        0..=12 => println!("儿童"),
        13..=19 => println!("青少年"),
        20..=64 => println!("成人"),
        _ => println!("长者"),
    }
}

if let 简洁控制流

fn if_let_usage() {
    let config_max = Some(3u8);
    
    // 使用 match
    match config_max {
        Some(max) => println!("最大配置: {}", max),
        _ => (),
    }
    
    // 使用 if let - 更简洁
    if let Some(max) = config_max {
        println!("最大配置: {}", max);
    }
    
    // if let 与 else 配合
    let mut count = 0;
    let coin = Coin::Quarter(UsState::Alaska);
    
    if let Coin::Quarter(state) = coin {
        println!("州的25美分来自 {:?}", state);
    } else {
        count += 1;
    }
    
    // 多个模式匹配
    let number = 13;
    if let 1 | 2 | 3 | 5 | 7 | 11 | 13 = number {
        println!("{} 是质数", number);
    }
}

4. Option 和 Result 深度解析

Option 详解

fn option_deep_dive() {
    // Option 的定义(标准库中)
    // enum Option<T> {
    //     Some(T),
    //     None,
    // }
    
    // 创建 Option
    let some_number: Option<i32> = Some(5);
    let some_string: Option<&str> = Some("hello");
    let absent_number: Option<i32> = None;
    
    // 使用 match 处理 Option
    fn square(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i * i),
        }
    }
    
    println!("square(Some(5)) = {:?}", square(Some(5))); // Some(25)
    println!("square(None) = {:?}", square(None));       // None
    
    // Option 的常用方法
    let x = Some(10);
    
    // unwrap - 获取 Some 中的值,如果是 None 则 panic
    let value = x.unwrap(); // 10
    // let bad = absent_number.unwrap(); // 这会 panic!
    
    // unwrap_or - 提供默认值
    let value_or_default = absent_number.unwrap_or(0); // 0
    
    // unwrap_or_else - 通过闭包计算默认值
    let value_or_computed = absent_number.unwrap_or_else(|| {
        println!("计算默认值");
        42
    }); // 42
    
    // map - 转换 Some 中的值
    let doubled = x.map(|n| n * 2); // Some(20)
    let still_none = absent_number.map(|n| n * 2); // None
    
    // and_then - 链式操作
    fn parse_number(s: &str) -> Option<i32> {
        s.parse().ok() // 将 Result 转换为 Option
    }
    
    let result = Some("42").and_then(parse_number); // Some(42)
    let bad_result = Some("hello").and_then(parse_number); // None
    
    // 组合使用
    let complex = Some("123")
        .and_then(parse_number)    // Some(123)
        .map(|n| n * 2)            // Some(246)
        .unwrap_or(0);             // 246
    
    println!("复杂操作结果: {}", complex);
}

Result<T, E> 详解

use std::fs::File;
use std::io::{self, Read, ErrorKind};

fn result_deep_dive() {
    // Result 的定义
    // enum Result<T, E> {
    //     Ok(T),
    //     Err(E),
    // }
    
    // 基本使用
    let file_result = File::open("hello.txt");
    
    // 使用 match 处理 Result
    let file = match file_result {
        Ok(file) => file,
        Err(error) => {
            panic!("打开文件失败: {:?}", error);
        }
    };
    
    // 更细致的错误处理
    let detailed_result = File::open("hello.txt");
    
    let file = match detailed_result {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => {
                // 文件不存在,尝试创建
                match File::create("hello.txt") {
                    Ok(fc) => fc,
                    Err(e) => panic!("创建文件失败: {:?}", e),
                }
            }
            other_error => {
                panic!("打开文件失败: {:?}", other_error);
            }
        },
    };
}

? 运算符

// 传播错误的传统方式
fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");
    
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e), // 提前返回错误
    };
    
    let mut s = String::new();
    
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

// 使用 ? 运算符简化
fn read_username_from_file_simple() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?; // 如果出错,立即返回错误
    let mut s = String::new();
    f.read_to_string(&mut s)?; // 同样,出错立即返回
    Ok(s)
}

// 更简洁的写法
fn read_username_from_file_shorter() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

// 使用 std::fs 的便捷函数
fn read_username_easiest() -> Result<String, io::Error> {
    std::fs::read_to_string("hello.txt")
}

// 在 main 函数中使用 Result
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let content = std::fs::read_to_string("hello.txt")?;
    println!("文件内容: {}", content);
    Ok(()) // 成功返回
}

组合 Option 和 Result

fn combine_option_result() {
    // Option 转 Result
    let some_number = Some(42);
    let result: Result<i32, &str> = some_number.ok_or("没有值");
    
    // Result 转 Option
    let good_result: Result<i32, &str> = Ok(42);
    let good_option = good_result.ok(); // Some(42)
    
    let bad_result: Result<i32, &str> = Err("错误");
    let bad_option = bad_result.ok(); // None
    
    // 复杂组合
    fn process_input(input: &str) -> Option<i32> {
        input.parse::<i32>().ok().filter(|&n| n > 0)
    }
    
    println!("process_input(\"42\") = {:?}", process_input("42")); // Some(42)
    println!("process_input(\"-5\") = {:?}", process_input("-5")); // None
    println!("process_input(\"abc\") = {:?}", process_input("abc")); // None
}

5. 包和模块深度解析

项目结构示例

my_project/
├── Cargo.toml          # 包配置
├── Cargo.lock          # 依赖锁文件
└── src/
    ├── main.rs         # 二进制crate根
    ├── lib.rs          # 库crate根
    ├── utils/          # 工具模块
    │   ├── mod.rs      # utils模块声明
    │   └── helpers.rs  # 辅助函数
    ├── models/         # 数据模型
    │   ├── mod.rs
    │   └── user.rs
    └── api/            # API模块
        ├── mod.rs
        └── v1.rs

模块系统详解

// src/lib.rs

// 声明模块
pub mod utils;      // 从 utils/mod.rs 或 utils.rs 加载
pub mod models;
pub mod api;

// 使用模块中的项
use crate::utils::helpers::some_helper_function;
use models::user::User;

// 重新导出(pub use)
pub use api::v1::make_api_request;

// 主库函数
pub fn library_function() -> String {
    some_helper_function()
}
// src/utils/mod.rs

// 声明子模块
pub mod helpers;

// 工具模块的公共函数
pub fn setup() {
    println!("工具模块初始化");
}

// 私有函数(只能在模块内部使用)
fn internal_helper() {
    println!("内部辅助函数");
}
// src/utils/helpers.rs

// 公共函数
pub fn some_helper_function() -> String {
    String::from("帮助函数被调用")
}

// 只能在当前crate内访问的函数
pub(crate) fn crate_private_function() {
    println!("只能在当前crate内访问");
}

// 模块私有函数
fn private_function() {
    println!("这个函数只在 helpers 模块内可见");
}
// src/models/mod.rs

pub mod user;

// 模型相关的公共接口
pub fn initialize_models() {
    println!("模型初始化");
}
// src/models/user.rs

use std::fmt;

// 公共结构体
#[derive(Debug)]
pub struct User {
    pub username: String,
    pub email: String,
    age: u8, // 私有字段
}

impl User {
    // 公共构造函数
    pub fn new(username: String, email: String, age: u8) -> User {
        User { username, email, age }
    }
    
    // 公共方法
    pub fn get_age(&self) -> u8 {
        self.age
    }
    
    // 私有方法
    fn validate(&self) -> bool {
        !self.username.is_empty() && !self.email.is_empty()
    }
}

// 实现 Display trait
impl fmt::Display for User {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "User: {} <{}>", self.username, self.email)
    }
}
// src/api/mod.rs

pub mod v1;

pub fn initialize_api() {
    println!("API模块初始化");
}
// src/api/v1.rs

// API v1 端点
pub fn make_api_request(endpoint: &str) -> String {
    format!("调用API v1 端点: {}", endpoint)
}

pub fn get_user_data(user_id: u32) -> String {
    format!("获取用户 {} 的数据", user_id)
}

使用声明详解

// src/main.rs

// 引入库crate
use my_project::{
    utils, 
    models::user::User, 
    make_api_request
};

// 标准库引入
use std::collections::{HashMap, HashSet};
use std::io::{self, Write};

// 重命名引入
use std::fmt::Result as FmtResult;
use std::io::Result as IoResult;

fn main() {
    // 使用库函数
    let result = my_project::library_function();
    println!("{}", result);
    
    // 使用引入的模块
    utils::setup();
    
    // 创建用户实例
    let user = User::new(
        String::from("alice"),
        String::from("alice@example.com"),
        25
    );
    
    println!("{}", user);
    println!("用户年龄: {}", user.get_age());
    
    // 使用重新导出的函数
    let api_response = make_api_request("/users");
    println!("{}", api_response);
    
    // 使用通配符引入(谨慎使用)
    use std::collections::*;
    let mut map = HashMap::new();
    map.insert("key", "value");
    
    let mut set = HashSet::new();
    set.insert(1);
}

可见性规则

// 可见性修饰符示例
mod visibility_example {
    // 公共项 - 任何地方都可访问
    pub struct PublicStruct {
        pub public_field: i32,
        private_field: i32, // 即使结构体是公共的,字段也可以是私有的
    }
    
    // 结构体实现
    impl PublicStruct {
        pub fn new() -> PublicStruct {
            PublicStruct {
                public_field: 1,
                private_field: 2,
            }
        }
        
        // 公共方法可以访问私有字段
        pub fn get_private(&self) -> i32 {
            self.private_field
        }
        
        // 私有方法
        fn internal_method(&self) {
            println!("内部方法");
        }
    }
    
    // 只在当前crate内可见
    pub(crate) struct CratePublicStruct {
        pub value: i32,
    }
    
    // 只在父模块内可见
    pub(super) struct SuperPublicStruct;
    
    // 私有项(默认)
    struct PrivateStruct;
    
    // 公共枚举的所有变体默认都是公共的
    pub enum PublicEnum {
        Variant1,
        Variant2,
    }
}

高级模块特性

// 内联模块
mod inline_module {
    pub fn public_function() {
        println!("内联模块的公共函数");
    }
    
    fn private_function() {
        println!("内联模块的私有函数");
    }
    
    // 嵌套模块
    pub mod nested {
        pub fn function() {
            println!("嵌套模块函数");
        }
    }
}

// 使用 cfg 条件编译
#[cfg(target_os = "linux")]
mod linux_specific {
    pub fn linux_only_function() {
        println!("这是Linux特有的功能");
    }
}

#[cfg(target_os = "windows")]
mod windows_specific {
    pub fn windows_only_function() {
        println!("这是Windows特有的功能");
    }
}

// 测试模块
#[cfg(test)]
mod tests {
    use super::*; // 引入父模块的所有项
    
    #[test]
    fn test_user_creation() {
        let user = User::new("test".to_string(), "test@example.com".to_string(), 30);
        assert_eq!(user.get_age(), 30);
        assert_eq!(user.username, "test");
    }
}

综合实战示例

// 一个完整的博客系统示例
use std::error::Error;
use std::fmt;

// 错误类型
#[derive(Debug)]
pub enum BlogError {
    InvalidTitle,
    InvalidContent,
    PostNotFound,
}

impl fmt::Display for BlogError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            BlogError::InvalidTitle => write!(f, "标题无效"),
            BlogError::InvalidContent => write!(f, "内容无效"),
            BlogError::PostNotFound => write!(f, "文章未找到"),
        }
    }
}

impl Error for BlogError {}

// 文章状态
#[derive(Debug, Clone, PartialEq)]
pub enum PostStatus {
    Draft,
    Published,
    Archived,
}

// 文章结构体
pub struct BlogPost {
    title: String,
    content: String,
    author: String,
    status: PostStatus,
    created_at: String, // 简化处理,实际应该用 DateTime
}

impl BlogPost {
    pub fn new(title: String, content: String, author: String) -> Result<Self, BlogError> {
        if title.trim().is_empty() {
            return Err(BlogError::InvalidTitle);
        }
        if content.trim().is_empty() {
            return Err(BlogError::InvalidContent);
        }
        
        Ok(BlogPost {
            title,
            content,
            author,
            status: PostStatus::Draft,
            created_at: "2024-01-01".to_string(), // 简化
        })
    }
    
    pub fn publish(&mut self) {
        self.status = PostStatus::Published;
    }
    
    pub fn archive(&mut self) {
        self.status = PostStatus::Archived;
    }
    
    // 获取器方法
    pub fn title(&self) -> &str {
        &self.title
    }
    
    pub fn content(&self) -> &str {
        &self.content
    }
    
    pub fn author(&self) -> &str {
        &self.author
    }
    
    pub fn status(&self) -> &PostStatus {
        &self.status
    }
    
    pub fn preview(&self) -> String {
        if self.content.len() > 100 {
            format!("{}...", &self.content[..100])
        } else {
            self.content.clone()
        }
    }
}

// 博客系统
pub struct Blog {
    posts: Vec<BlogPost>,
    name: String,
}

impl Blog {
    pub fn new(name: String) -> Self {
        Blog {
            posts: Vec::new(),
            name,
        }
    }
    
    pub fn add_post(&mut self, post: BlogPost) {
        self.posts.push(post);
    }
    
    pub fn get_post(&self, index: usize) -> Option<&BlogPost> {
        self.posts.get(index)
    }
    
    pub fn get_published_posts(&self) -> Vec<&BlogPost> {
        self.posts
            .iter()
            .filter(|post| post.status() == &PostStatus::Published)
            .collect()
    }
    
    pub fn publish_post(&mut self, index: usize) -> Result<(), BlogError> {
        match self.posts.get_mut(index) {
            Some(post) => {
                post.publish();
                Ok(())
            }
            None => Err(BlogError::PostNotFound),
        }
    }
}

// 使用示例
fn main() -> Result<(), Box<dyn Error>> {
    let mut my_blog = Blog::new("我的技术博客".to_string());
    
    // 创建文章
    let post1 = BlogPost::new(
        "学习Rust".to_string(),
        "Rust是一门很棒的系统编程语言...".to_string(),
        "张三".to_string()
    )?;
    
    let post2 = BlogPost::new(
        "模式匹配详解".to_string(),
        "模式匹配是Rust中非常强大的特性...".to_string(),
        "李四".to_string()
    )?;
    
    // 添加文章到博客
    my_blog.add_post(post1);
    my_blog.add_post(post2);
    
    // 发布第一篇文章
    my_blog.publish_post(0)?;
    
    // 显示已发布的文章
    println!("博客: {}", my_blog.name);
    println!("已发布文章:");
    
    for post in my_blog.get_published_posts() {
        println!("标题: {}", post.title());
        println!("作者: {}", post.author());
        println!("预览: {}", post.preview());
        println!("状态: {:?}", post.status());
        println!("---");
    }
    
    Ok(())
}

Read more

Rust 生命周期

1. 什么是生命周期? 生命周期是引用有效的作用域范围。在 Rust 中,每个引用都有一个生命周期,这是它保持有效的作用域。 fn main() { // 生命周期开始 let x = 5; // x 的生命周期开始 let r = &x; // r 的生命周期开始,引用 x println!("r = {}", r); // 生命周期结束 } // x 和 r 的生命周期结束 2. 为什么需要生命周期? 问题:悬垂引用(Dangling References) // 这个代码无法编译! fn main() { let reference; { let value = 42; // value 在内部作用域创建

By amm

rust所有权、借用、生命周期

1. 所有权 所有权是 Rust 最独特的特性,它让 Rust 无需垃圾回收即可保证内存安全。 所有权规则 1. Rust 中的每一个值都有一个被称为其所有者的变量。 2. 值在任一时刻有且只有一个所有者。 3. 当所有者(变量)离开作用域,这个值将被丢弃。 变量与数据交互的方式 移动 fn main() { let s1 = String::from("hello"); // s1 拥有字符串 "hello" let s2 = s1; // s1 的所有权被移动(move)到 s2 // println!("{}, world!", s1); // 这行会编译错误!s1

By amm

© 2025 路不易All rights reserved.

黔ICP备2025043243号-1 | 公安备案图标 贵公网安备52052402000220号