Rust編程語言入門之泛型、Trait、生命周期

来源:https://www.cnblogs.com/QiaoPengjun/archive/2023/04/02/17279765.html
-Advertisement-
Play Games

泛型、Trait、生命周期 一、提取函數消除重覆 fn main() { let number_list = vec![34, 50, 25, 100, 65]; let mut largest = number_list[0]; for number in number_list { if num ...


泛型、Trait、生命周期

一、提取函數消除重覆

fn main() {
  let number_list = vec![34, 50, 25, 100, 65];
  let mut largest = number_list[0];
  for number in number_list {
    if number > largest {
      largest = number;
    }
  }
  
  println!("The largest number is {}", largest);
}

重覆代碼

  • 重覆代碼的危害:
    • 容易出錯
    • 需求變更時需要在多處進行修改
  • 消除重覆:提取函數
fn largest(list: &[i32]) -> i32 {
  let mut largest = list[0];
  for &item in list { // &item 解構
    if item > largest {
      largest = item;
    }
  }
  largest
}

fn main() {
  let number_list = vec![34, 50, 25, 100, 65];
  let result = largest(&number_list);
  println!("The largest number is {}", result);
  
  let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
  let result = largest(&number_list);
  println!("The largest number is {}", result);
}

消除重覆的步驟

  • 識別重覆代碼
  • 提取重覆代碼到函數體中,併在函數簽名中指定函數的輸入和返回值
  • 將重覆的代碼使用函數調用進行替代

二、泛型

泛型

  • 泛型:提高代碼復用能力
    • 處理重覆代碼的問題
  • 泛型是具體類型或其它屬性的抽象代替:
    • 你編寫的代碼不是最終的代碼,而是一種模版,裡面有一些“占位符”
    • 編譯器在編譯時將“占位符”替換為具體的類型
  • 例如:fn largest<T>(list: &[T]) -> T {...}
  • 類型參數:
    • 很短,通常一個字母
    • CamelCase
    • T:type 的縮寫

函數定義中的泛型

  • 泛型函數:
    • 參數類型
    • 返回類型
fn largest<T>(list: &[T]) -> T {
  let mut largest = list[0];
  for &item in list { 
    if item > largest {  // 比較 報錯 ToDo 
      largest = item;
    }
  }
  largest
}

fn main() {
  let number_list = vec![34, 50, 25, 100, 65];
  let result = largest(&number_list);
  println!("The largest number is {}", result);
  
  let char_list = vec!['y', 'm', 'a', 'q'];
  let result = largest(&char_list);
  println!("The largest number is {}", result);
}

Struct 定義中的泛型

struct Point<T> {
  x: T,
  y: T,
}

struct Point1<T, U> {
  x: T,
  y: U,
}

fn main() {
  let integer = Point {x: 5, y: 10};
  let float = Point(x: 1.0, y: 4.0);
  
  let integer1 = Point1 {x: 5, y: 10.0};
}
  • 可以使用多個泛型的類型參數
    • 太多類型參數:你的代碼需要重組為多個更小的單元

Enum 定義中的泛型

  • 可以讓枚舉的變體持有泛型數據類型
    • 例如 Option<T>Result<T, E>
enum Option<T> {
  Some(T),
  None,
}

enum Result<T, E> {
  Ok(T),
  Err(E),
}

fn main() {}

方法定義中的泛型

  • 為 struct 或 enum 實現方法的時候,可在定義中使用泛型
struct Point<T> {
  x: T,
  y: T,
}

impl<T> Point<T> {
  fn x(&self) -> &T {
    &self.x
  }
}

impl Point<i32> {
  fn x1(&self) -> &i32 {
    &self.x
  }
}

fn main() {
  let p = Point {x: 5, y: 10};
  println!("p.x = {}", p.x());
}
  • 註意:
    • 把 T 放在 impl 關鍵字後,表示在類型 T 上實現方法
      • 例如: impl<T> Point<T>
    • 只針對具體類型實現方法(其餘類型沒實現方法):
      • 例如:impl Point<f32>
  • struct 里的泛型類型參數可以和方法的泛型類型參數不同
struct Point<T, U> {
  x: T,
  y: U,
}

impl<T, U> Point<T, U> {
  fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
    Point {
      x: self.x,
      y: other.y,
    }
  }
}

fn main() {
  let p1 = Point {x: 5, y: 4};
  let p2 = Point {x: "Hello", y: 'c'};
  let p3 = p1.mixup(p2);
  
  println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

泛型代碼的性能

  • 使用泛型的代碼和使用具體類型的代碼運行速度是一樣的。
  • 單態化(monomorphization):
    • 在編譯時將泛型替換為具體類型的過程
fn main() {
  let integer = Some(5);
  let float = Some(5.0);
}

enum Option_i32 {
  Some(i32),
  None,
}

enum Option_f64 {
  Some(f64),
  None,
}

fn main() {
  let integer = Option_i32::Some(5);
  let float = Option_f64::Some(5.0);
}

三、Trait(上)

Trait

  • Trait 告訴 Rust 編譯器:
    • 某種類型具有哪些並且可以與其它類型共用的功能
  • Trait:抽象的定義共用行為
  • Trait bounds(約束):泛型類型參數指定為實現了特定行為的類型
  • Trait 與其它語言的介面(Interface)類似,但有些區別

定義一個 Trait

  • Trait 的定義:把方法簽名放在一起,來定義實現某種目的所必需的一組行為。
    • 關鍵字:trait
    • 只有方法簽名,沒有具體實現
    • trait 可以有多個方法:每個方法簽名占一行,以 ; 結尾
    • 實現該 trait 的類型必須提供具體的方法實現
pub trait Summary {
  fn summarize(&self) -> String;
}

// NewsArticle
// Tweet

fn main() {}

在類型上實現 trait

  • 與為類型實現方法類似
  • 不同之處:
    • impl xxxx for Tweet {...}
    • 在 impl 的塊里,需要對 Trait 里的方法簽名進行具體的實現

lib.rs 文件

pub trait Summary {
  fn summarize(&self) -> String;
}

pub struct NewsArticle {
  pub headline: String,
  pub location: String,
  pub author: String,
  pub content: String,
}

impl Summary for NewsArticle {
  fn summarize(&self) -> String {
    format!("{}, by {} ({})", self.headline, self.author, self.location)
  }
}

pub struct Tweet {
  pub username: String,
  pub content: String,
  pub reply: bool,
  pub retweet: bool,
}

impl Summary for Tweet {
  fn summarize(&self) -> String {
    format!("{}: {}", self.username, self.content)
  }
}

main.rs 文件

use demo::Summary;
use demo::Tweet;

fn main() {
  let tweet = Tweet {
    username: String::from("horse_ebooks"),
    content: String::from("of course, as you probably already know, people"),
    reply: false,
    retweet: false,
  };
  
  println!("1 new tweet: {}", tweet.summarize())
}

實現 trait 的約束

  • 可以在某個類型上實現某個 trait 的前提條件是:
    • 這個類型或這個 trait 是在本地 crate 里定義的
  • 無法為外部類型來實現外部的 trait:
    • 這個限制是程式屬性的一部分(也就是一致性)
    • 更具體地說是孤兒規則:之所以這樣命名是因為父類型不存在
    • 此規則確保其他人的代碼不能破壞您的代碼,反之亦然
    • 如果沒有這個規則,兩個crate 可以為同一類型實現同一個 trait,Rust就不知道應該使用哪個實現了

預設實現

lib.rs 文件

pub trait Summary {
  // fn summarize(&self) -> String;
  fn summarize(&self) -> String {
    String::from("(Read more...)")
  }
}

pub struct NewsArticle {
  pub headline: String,
  pub location: String,
  pub author: String,
  pub content: String,
}

impl Summary for NewsArticle {
  // fn summarize(&self) -> String {
   // format!("{}, by {} ({})", self.headline, self.author, self.location)
  // }
}

pub struct Tweet {
  pub username: String,
  pub content: String,
  pub reply: bool,
  pub retweet: bool,
}

impl Summary for Tweet {
  fn summarize(&self) -> String {
    format!("{}: {}", self.username, self.content)
  }
}

main.rs 文件

use demo::NewsArticle;
use demo::Summary;

fn main() {
  let article = NewsArticle {
    headline: String::from("Penguins win the Stanley Cup Championship!"),
    content: String::from("The pittsburgh penguins once again are the best hockey team in the NHL."),
    author: String::from("Iceburgh"),
    location: String::from("Pittsburgh, PA, USA"),
  };
  
  println!("1 new tweet: {}", article .summarize())
}
  • 預設實現的方法可以調用 trait 中其它的方法,即使這些方法沒有預設實現。
pub trait Summary {
  fn summarize_author(&self) -> String;
  
  fn summarize(&self) -> String {
    format!("Read more from {} ...", self.summarize_author())
  }
}

pub struct NewsArticle {
  pub headline: String,
  pub location: String,
  pub author: String,
  pub content: String,
}

impl Summary for NewsArticle {
  fn summarize_author(&self) -> String {
    format!("@{}", self.author)
  }
}
  • 無法從方法的重寫實現裡面調用預設的實現

四、Trait(下)

Trait 作為參數

pub fn notify(item: impl Summary) {
  println!("Breaking news! {}", item.summarize());
}
  • impl Trait 語法:適用於簡單情況
  • Trait bound 語法:可用於複雜情況
    • impl Trait 語法是 Trait bound 的語法糖
pub fn notify<T: Summary>(item: T) {
  println!("Breaking news! {}", item.summarize());
}
  • 使用 + 指定多個 Trait bound
pub fn notify(item: impl Summary + Display) {
  println!("Breaking news! {}", item.summarize());
}

pub fn notify<T: Summary + Display>(item: T) {
  println!("Breaking news! {}", item.summarize());
}
  • Trait bound 使用where 子句
    • 在方法簽名後指定 where 子句
pub fn notify<T: Summary + Display, U: Clone + Debug>(a: T, b: U) -> String {
  format!("Breaking news! {}", a.summarize())
}

pub fn notify<T, U>(a: T, b: U) -> String
where 
	T: Summary + Display, 
	U: Clone + Debug,
{
  format!("Breaking news! {}", a.summarize())
}

實現 Trait 作為返回類型

  • impl Trait 語法
pub fn notify1(s: &str) -> impl Summary {
  NewsArticle {
    headline: String::from("Penguins win the Stanley Cup Championship!"),
    content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
    author: String::from("Iceburgh"),
    location: String::from("Pittsburgh, PA, USA"),
  }
}
  • 註意: impl Trait 只能返回確定的同一種類型,返回可能不同類型的代碼會報錯

使用 Trait Bound 的例子

  • 例子:使用 Trait Bound 修複 largest 函數
fn largest<T: PartialOrd + Clone>(list: &[T]) -> T {
  let mut largest = list[0].clone();
  
  for item in list.iter() {
    if item > &largest { // std::cmp::ParticalOrd
      largest = item.clone();
    }
  }
  
  largest
}

fn main() {
  let number_list = vec![34, 50, 25, 100, 65];
  let result = largest(&number_list);
  println!("The largest number is {}", result);
  
  let char_list = vec!['y', 'm', 'a', 'q'];
  let result = largest(&char_list);
  println!("The largest char is {}", result)
}


fn largest<T: PartialOrd + Clone>(list: &[T]) -> &T {
  let mut largest = &list[0];
  
  for item in list.iter() {
    if item > &largest { // std::cmp::ParticalOrd
      largest = item;
    }
  }
  
  largest
}

fn main() {
  let str_list = vec![String::from("hello"), String::from("world")];
  let result = largest(&str_list);
  println!("The largest word is {}", result);
  
}

使用 Trait Bound 有條件的實現方法

  • 在使用泛型類型參數的 impl 塊上使用 Trait Bound,我們可以有條件的為實現了特定 Trait的類型來實現方法
use std::fmt::Display;

struct Pair<T> {
  x: T,
  y: T,
}

impl<T> Pair<T> {
  fn new(x: T, y: T) -> Self {
    Self {x, y}
  }
}

impl<T: Display + PartialOrd> Pair<T> {
  fn cmp_display(&self) {
    if self.x >= self.y {
      println!("The largest member is x = {}", self.x);
    } else {
      println!("The largest member is y = {}", self.y);
    }
  }
}
  • 也可以為實現了其它Trait的任意類型有條件的實現某個Trait
  • 為滿足Trait Bound 的所有類型上實現 Trait 叫做覆蓋實現(blanket implementations)
fn main() {
  let s = 3.to_string();
}

五、生命周期(1/4)

生命周期

  • Rust的每個引用都有自己的生命周期
  • 生命周期:引用保持有效的作用域
  • 大多數情況:生命周期是隱式的、可被推斷的
  • 當引用的生命周期可能以不同的方式互相關聯時:手動標註生命周期。

生命周期 - 避免懸垂引用(dangling regerence)

  • 生命周期的主要目標:避免懸垂引用(dangling regerence)
fn main() {
  {
    let r;
    {
      let x = 5;
      r = &x; // 報錯
    }
    println!("r: {}", r);
  }
}

借用檢查器

  • Rust編譯器的借用檢查器:比較作用域來判斷所有的借用是否合法。
fn main() {
  let x = 5;
  let r = &x;
  
  println!("r: {}", r);
}

函數中的泛型生命周期

fn main() {
  let string1 = String::from("abcd");
  let string2 = "xyz";
  
  let result = longest(string1.as_str(), string2);
  
  println!("The longest string is {}", result);
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  if x.len() > y.len() {
    x
  } else {
    y
  }
}

六、生命周期(2/4)

生命周期標註語法

  • 生命周期的標註不會改變引用的生命周期長度
  • 當指定了泛型生命周期參數,函數可以接收帶有任何生命周期的引用
  • 生命周期的標註:描述了多個引用的生命周期間的關係,但不影響生命周期

生命周期標註 - 語法

  • 生命周期參數名:
    • 以 ' 開頭
    • 通常全小寫且非常短
    • 很多人使用 'a
  • 生命周期標註的位置:
    • 在引用的 & 符號後
    • 使用空格將標註和引用類型分開

生命周期標註 - 例子

  • &i32 // 一個引用
  • &'a i32 // 帶有顯示生命周期的引用
  • &'a mut i32 // 帶有顯示生命周期的可變引用
  • 單個生命周期標註本身沒有意義

函數簽名中的生命周期標註

  • 泛型生命周期參數聲明在:函數名和參數列表之間的 <>里
  • 生命周期 'a 的實際生命周期是:x 和 y 兩個生命周期中較小的那個
fn main() {
  let string1 = String::from("abcd");
  let result;
  {
    let string2 = String::from("xyz");
    let result = longest(string1.as_str(), string2.as_str());  // 報錯 string2
  }
 
  println!("The longest string is {}", result);
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  if x.len() > y.len() {
    x
  } else {
    y
  }
}

七、生命周期(3/4)

深入理解生命周期

  • 指定生命周期參數的方式依賴於函數所做的事情
fn main() {
  let string1 = String::from("abcd");
  let string2 = "xyz";
  
  let result = longest(string1.as_str(), string2);
  
  println!("The longest string is {}", result);
}

fn longest<'a>(x: &'a str, y: &str) -> &'a str {
  x
}
  • 從函數返回引用時,返回類型的生命周期參數需要與其中一個參數的生命周期匹配
  • 如果返回的引用沒有指向任何參數,那麼它只能引用函數內創建的值
fn main() {
  let string1 = String::from("abcd");
  let string2 = "xyz";
  
  let result = longest(string1.as_str(), string2);
  
  println!("The longest string is {}", result);
}

fn longest<'a>(x: &'a str, y: &str) -> &'a str {
  let result = String::from("abc");
  result.as_str()  // 報錯 
}

fn longest<'a>(x: &'a str, y: &str) -> String {
  let result = String::from("abc");
  result
}

Struct 定義中的生命周期標註

  • Struct 里可包括:
    • 自持有的類型
    • 引用:需要在每個引用上添加生命周期標註
struct ImportantExcerpt<'a> {
  part: &'a str,
}

fn main() {
  let novel = String::from("Call me Ishmael. Some years ago ...")
  
  let first_sentence = novel.split('.')
  	.next()
  	.expect("Could not found a '.'");
  
  let i = ImportantExcerpt {
    part: first_sentence
  };
}

生命周期的省略

  • 我們知道:
    • 每個引用都有生命周期
    • 需要為使用生命周期的函數或Struct指定生命周期參數

生命周期省略規則

  • 在Rust引用分析中所編入的模式稱為生命周期省略規則。
    • 這些規則無需開發者來遵守
    • 它們是一些特殊情況,由編譯器來考慮
    • 如果你的代碼符合這些情況,那麼就無需顯式標註生命周期
  • 生命周期省略規則不會提供完整的推斷:
    • 如果應用規則後,引用的生命周期仍然模糊不清-> 編譯錯誤
    • 解決辦法:添加生命周期標註,表明引用間的相互關係

輸入、輸出生命周期

  • 生命周期在:
    • 函數/方法的參數:輸入生命周期
    • 函數/方法的返回值:輸出生命周期

生命周期省略的三個規則

  • 編譯器使用3個規則在沒有顯示標註生命周期的情況下,來確定引用的生命周期
    • 規則 1 應用於輸入生命周期
    • 規則 2、3 應用於輸出生命周期
    • 如果編譯器應用完 3 個規則之後,仍然有無法確定生命周期的引用 -> 報錯
    • 這些規則適用於 fn 定義和 impl 塊
  • 規則 1:每個引用類型的參數都有自己的生命周期
  • 規則 2:如果只有 1 個輸入生命周期參數,那麼該生命周期被賦給所有的輸出生命周期參數
  • 規則 3:如果有多個輸入生命周期參數,但其中一個是 &self 或 &mut self (是方法),那麼 self 的生命周期會被賦給所有的輸出生命周期參數

生命周期省略的三個規則 - 例子

  • 假設我們是編譯器:
  • fn first_word(s: &str) -> &str {
  • fn first_word<'a>(s: &'a str) -> &str {
  • fn first_word<'a>(s: &'a str) -> &'a str {
  • fn longest(x: &str, y: &str) -> &str{
  • fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str{ // 報錯

八、生命周期(4/4)

方法定義中的生命周期標註

  • 在 Struct 上使用生命周期實現方法,語法和泛型參數的語法一樣
  • 在哪聲明和使用生命周期參數,依賴於:
    • 生命周期參數是否和欄位、方法的參數或返回值有關
  • Struct 欄位的生命周期名:
    • 在 impl 後聲明
    • 在 struct 名後聲明
    • 這些聲明周期是 Struct 類型的一部分
  • impl 塊內的方法簽名中:
    • 引用必須綁定於 Struct 欄位引用的生命周期,或者引用是獨立的也可以
    • 生命周期省略規則經常使得方法中的生命周期標註不是必須的
struct ImportantExcerpt<'a> {
  part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
  fn level(&self) -> i32 {
    3
  }
  
  fn snnounce_and_return_part(&self, announcement: &str) -> &str {
    println!("Attention please: {}", announcement);
    self.part
  }
}

fn main() {
  let novel = String::from("Call me Ishmael. Some years ago ...")
  
  let first_sentence = novel.split('.')
  	.next()
  	.expect("Could not found a '.'");
  
  let i = ImportantExcerpt {
    part: first_sentence,
  };
}

靜態生命周期

  • 'static 是一個特殊的生命周期:整個程式的持續時間。
    • 例如:所有的字元串字面值都擁有 ‘static 生命周期
    • let s: &'static str = "I have a static lifetime.";
  • 為引用指定 ’static 生命周期前要三思:
    • 是否需要引用在程式整個生命周期內都存活。

泛型參數類型、Trait Bound、生命周期

use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where
	T: Display,
{
  println!("Announcement! {}", ann);
  if x.len() > y.len() {
    x
  } else {
    y
  }
}

fn main() {}

本文來自博客園,作者:QIAOPENGJUN,轉載請註明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17279765.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 訪問命令行(command line interface,CLI) Linux控制台 1)Linux系統啟動時,會自動創建多個虛擬控制台。虛擬控制台是運行在Linux系統記憶體中的終端會話。多數Linux發行版會啟動5~6個 2)通常必須按下Ctrl+Alt組合鍵,然後再按一個功能鍵(F1~F7)來進 ...
  • 資料庫安全與保護 第一節 資料庫完整性 資料庫完整性是指資料庫中數據的正確性和相容性。 數據完整性約束是為了防止資料庫中存在不符合語義的數據,為了維護數據的完整性,DBMS 必須提供一種機制來檢查資料庫中的數據,以判斷其是否滿足語義規定的條件。 這些加在資料庫數據之上的語義約束條件就是數據完整性約束 ...
  • 包含五個內容:①Navicat連接伺服器MySQL;②如何查看MySQL用戶名和密碼;③修改MySQL登錄密碼;④error 1045 (28000): access denied for user 'root'@'localhost' (using password:yes)錯誤解決方法;⑤安裝M... ...
  • 一:在hadoop3.3中安裝配置sqoop1.4.7 前言: sqoop功能已經非常完善了,沒有什麼可以更新的了,官方停止更新維護了。因此官方集成的hadoop包停留在了2.6.0版本,在hadoop3.3.0版本會提示類版本過低錯誤,但純凈版sqoop有缺少必須的第三方庫,所以將這兩個包下載下來 ...
  • 1. 集合論是SQL語言的根基 1.1. UNION 1.1.1. SQL-86標準 1.2. NTERSECT和EXCEPT 1.2.1. SQL-92標準 1.3. 除法運算(DIVIDE BY) 1.3.1. 沒有被標準化 2. 註意事項 2.1. SQL能操作具有重覆行的集合,可以通過可選項 ...
  • 說明 有讀者反饋: 學習uniapp ios 插件開發不知道從哪些文章看起,沒有一個清晰的學習路線 本文就做一個解答。 首先本系列的文章是作者精心排過序的,如果想要完整的學習uniapp ios原生插件開發技術的話,建議是按文章順序瀏覽。 當然您如果有相關的開發經驗,且只對某一技術實現感興趣的話,也 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 最近碰到這樣一個問題,在一張封面上直接顯示書名,可能會存在書名看不太清楚的情況(容易受到背景干擾),如下 為瞭解決這個問題,設計師提了一個“究極”方案,將書名背後的圖片模糊一下,這個在 CSS 中很好實現,僅需backdrop-filte ...
  • 路由守衛 作用:對路由進行許可權控制 配置路由守衛應在暴露前配置 分類:全局守衛、獨享守衛、組件內守衛 首先先給需要鑒權的路由設置好meta配置項。 meta配置項:是vue-router中的一個對象,主要用於存儲路由的元數據(meta data)信息。這些元數據信息可以是一些描述性的內容,比如頁面的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...