Rust編程語言入門之高級特性

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

高級特性 主要內容 不安全 Rust 高級 Trait 高級 類型 高級函數和閉包 巨集 一、不安全 Rust 匹配命名變數 隱藏著第二個語言,它沒有強制記憶體安全保證:Unsafe Rust(不安全的 Rust) 和普通的 Rust 一樣,但提供了額外的“超能力” Unsafe Rust 存在的原因: ...


高級特性

主要內容

  • 不安全 Rust
  • 高級 Trait
  • 高級 類型
  • 高級函數和閉包
  • 巨集

一、不安全 Rust

匹配命名變數

  • 隱藏著第二個語言,它沒有強制記憶體安全保證:Unsafe Rust(不安全的 Rust)
    • 和普通的 Rust 一樣,但提供了額外的“超能力”
  • Unsafe Rust 存在的原因:
    • 靜態分析是保守的。
      • 使用 Unsafe Rust:我知道自己在做什麼,並承擔相應風險
    • 電腦硬體本身就是不安全的,Rust需要能夠進行底層系統編程

Unsafe 超能力

  • 使用 unsafe 關鍵字來切換到 unsafe Rust,開啟一個塊,裡面放著 Unsafe 代碼
  • Unsafe Rust 里可執行的四個動作(unsafe 超能力):
    • 解引用原始指針
    • 調用 unsafe 函數或方法
    • 方法或修改可變的靜態變數
    • 實現 unsafe trait
  • 註意:
    • Unsafe 並沒有關閉借用檢查或停用其它安全檢查
    • 任何記憶體安全相關的錯誤必須留在 unsafe 塊里
    • 儘可能隔離 Unsafe 代碼,最好將其封裝在安全的抽象里,提供安全的API

解引用原始指針

  • 原始指針
    • 可變的:*mut T
    • 不可變的:*const T。意味著指針在解引用後不能直接對其進行賦值
    • 註意:這裡的 * 不是解引用符號,它是類型名的一部分。
  • 與引用不同,原始指針:
    • 允許通過同時具有不可變和可變指針或多個執行同一位置的可變指針來忽略借用規則
    • 無法保證能指向合理的記憶體
    • 允許為null
    • 不實現任何自動清理
  • 放棄保證的安全,換取更好的性能/與其它語言或硬體介面的能力

解引用原始指針

fn main() {
  let mut num = 5;
  
  let r1 = &num as *const i32;
  let r2 = &mut num as *mut i32;
  unsafe {
    println!("r1: {}", *r1);
    println!("r2: {}", *r2);
  }
  
  let address = 0x012345usize;
  let r = address as *const i32;
  unsafe {
    println!("r: {}", *r); // 報錯 非法訪問
  }
}
  • 為什麼要用原始指針?
    • 與 C 語言進行介面
    • 構建借用檢查器無法理解的安全抽象

調用 unsafe 函數或方法

  • unsafe 函數或方法:在定義前加上了 unsafe 關鍵字
    • 調用前需手動滿足一些條件(主要靠看文檔),因為Rust無法對這些條件進行驗證
    • 需要在 unsafe 塊里進行調用
unsafe fn dangerous() {}

fn main() {
  unsafe {
    dangerous();
  }
}

創建 Unsafe 代碼的安全抽象

  • 函數包含 unsafe 代碼並不意味著需要將整個函數標記為 unsafe
  • 將 unsafe 代碼包裹在安全函數中是一個常見的抽象
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
  let len = slice.len();
  
  assert!(mid <= len);
  
  (&mut slice[..mid], &mut slice[mid..]) // 報錯 cannot borrow `*slice` as mutable more than once at a time
}

fn main() {
  let mut v = vec![1, 2, 3, 4, 5, 6];
  
  let r = &mut v[..];
  let (a, b) = r.split_at_mut(3);
  assert_eq!(a, &mut [1, 2, 3]);
  assert_eq!(b, &mut [4, 5, 6]);
}

修改之後:

use std::slice;

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
  let len = slice.len();
  let ptr = slice.as_mut_ptr()
  
  assert!(mid <= len);
  
  unsafe {
    (
      slice::from_raw_parts_mut(ptr, mid),
      slice::from_raw_parts_mut(ptr.add(mid), len = mid),
    )
  }
}

fn main() {
  let address = 0x012345usize;
  let r = address as *mut i32;
  
  let slice: &[i32] = unsafe {
    slice::from_raw_parts_mut(r, 10000)
  };
}

使用 extern 函數調用外部代碼

  • extern 關鍵字:簡化創建和使用外部函數介面(FFI)的過程。
  • 外部函數介面(FFI,Foreign Function Interface):它允許一種編程語言定義函數,並讓其它編程語言能調用這些函數
extern "C" {
  fn abs(input: i32) -> i32;
}

fn main() {
  unsafe {
    println!("Absolute value of -3 according to C: {}", abs(-3));
  }
}
  • 應用二進位介面(ABI,Application Binary Interface):定義函數在彙編層的調用方式
  • “C” ABI 是最常見的ABI,它遵循 C 語言的ABI

從其它語言調用 Rust 函數

  • 可以使用 extern 創建介面,其它語言通過它們可以調用 Rust 的函數
  • 在 fn 前添加 extern 關鍵字,並指定 ABI
  • 還需添加 #[no_mangle]註解:避免 Rust 在編譯時改變它的名稱
#[no_mangle]
pub extern "C" fn call_from_c() {
  println!("Just called a Rust function from C!");
}

fn main() {}

訪問或修改一個可變靜態變數

  • Rust 支持全局變數,但因為所有權機制可能產生某些問題,例如數據競爭
  • 在 Rust 里,全局變數叫做靜態(static)變數
static HELLO_WORLD: &str = "Hello, world!";

fn main() {
  println!("name is: {}", HELLO_WORLD);
}

靜態變數

  • 靜態變數與常量類似
  • 命名:SCREAMING_SNAKE_CASE
  • 必須標註類型
  • 靜態變數只能存儲 'static 生命周期的引用,無需顯示標註
  • 訪問不可變靜態變數是安全的

常量和不可變靜態變數的區別

  • 靜態變數:有固定的記憶體地址,使用它的值總會訪問同樣的數據
  • 常量:允許使用它們的時候對數據進行複製
  • 靜態變數:可以是可變的,訪問和修改靜態可變變數是不安全(unsafe)的
static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
  unsafe {
    COUNTER += inc;
  }
}

fn main() {
  add_to_count(3);
  
  unsafe {
    println!("COUNTER: {}", COUNTER);
  }
}

實現不安全(unsafe)trait

  • 當某個 trait 中存在至少一個方法擁有編譯器無法校驗的不安全因素時,就稱這個 trait 是不安全的
  • 聲明 unsafe trait:在定義前加 unsafe 關鍵字
    • 該 trait 只能在 unsafe 代碼塊中實現
unsafe trait Foo {
  // methods go here
}

unsafe impl Foo for i32 {
  // method implementations go here
}

fn main() {}

何時使用 unsafe 代碼

  • 編譯器無法保證記憶體安全,保證 unsafe 代碼正確並不簡單
  • 有充足理由使用 unsafe 代碼時,就可以這樣做
  • 通過顯示標記 unsafe,可以在出現問題時輕鬆的定位

二、高級 Trait

在 Trait 定義中使用關聯類型來指定占位類型

  • 關聯類型(associated type)是 Trait中的類型占位符,它可以用於Trait的方法簽名中:
    • 可以定義出包含某些類型的 Trait,而在實現前無需知道這些類型是什麼
pub trait Iterator {
  type Item;
  
  fn next(&mut self) -> Option<Self::Item>;
}

fn main() {
  println!("Hello, world!");
}

關聯類型與泛型的區別

泛型 關聯類型
每次實現 Trait 時標註類型 無需標註類型
可以為一個類型多次實現某個 Trait(不同的泛型參數) 無法為單個類型多次實現某個 Trait

例子:

pub trait Iterator {
  type Item;
  
  fn next(&mut self) -> Option<Self::Item>;
}

pub trait Iterator2<T> {
  fn next(&mut self) -> Option<T>;
}

struct Counter {}

impl Iterator for Counter {
  type Item = u32;
  
  fn next(&mut self) -> Option<Self::Item> {
    None
  }
}

impl Iterator2<String> for Counter {
  fn next(&mut self) -> Option<String> {
    None
  }
}

impl Iterator2<u32> for Counter {
  fn next(&mut self) -> Option<u32> {
    None
  }
}

fn main() {
  println!("Hello, world!");
}

預設泛型參數和運算符重載

  • 可以在使用泛型參數時為泛型指定一個預設的具體類型。
  • 語法:<PlaceholderType=ConcreteType>
  • 這種技術常用於運算符重載(operator overloading)
  • Rust 不允許創建自己的運算符及重載任意的運算符
  • 但可以通過實現 std::ops 中列出的那些 trait 來重載一部分相應的運算符

例子一:

use std::ops::Add;

#[derive(Debug, PartialEq)]
struct Point {
  x: i32,
  y: i32,
}

impl Add for Point {
  type Output = Point;
  
  fn add(self, other: Point) -> Point {
    Point {
      x: self.x + other.x,
      y: self.y + other.y,
    }
  }
}

fn main() {
  assert_eq!(Point {x: 1, y: 0} + Point {x: 2, y: 3},
    Point {x: 3, y: 3}
  );
}

例子二:

use std::ops::Add;

struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters {
  type Output = Millimeters;
  
  fn add(self, other: Meters) -> Millimeters {
    Millimeters(self.0 + (other.0 * 1000))
  }
}

fn main() {
  
}

預設泛型參數的主要應用場景

  • 擴展一個類型而不破壞現有代碼
  • 允許在大部分用戶都不需要的特定場景下進行自定義

完全限定語法(Fully Qualified Syntax)如何調用同名方法

例子一:

trait Pilot {
  fn fly(&self);
}

trait Wizard {
  fn fly(&self);
}

struct Human;

impl Pilot for Human {
  fn fly(&self) {
    println!("This is your captain speaking.");
  }
}

impl Wizard for Human {
  fn fly(&self) {
    println!("Up!");
  }
}

impl Human {
  fn fly(&self) {
    println!("*waving arms furiously*");
  }
}

fn main() {
  let persion = Human;
  person.fly(); // Human 本身的 fly 方法
  Pilot::fly(&person);
  Wizard::fly(&person);
}

例子二:

trait Animal {
  fn baby_name() -> String;
}

struct Dog;

impl Dog {
  fn baby_name() -> String {
    String::from("Spot")
  }
}

impl Animal for Dog {
  fn baby_name() -> String {
    String::from("puppy")
  }
}

fn main() {
  println!("A baby dog is called a {}", Dog::baby_name()); // Dog 本身的關聯方法
}

完全限定語法(Fully Qualified Syntax)如何調用同名方法

  • 完全限定語法:<Type as Trait>::function(receiver_if_method, netx_arg, ...);
    • 可以在任何調用函數或方法的地方使用
    • 允許忽略那些從其它上下文能推導出來的部分
    • 當 Rust 無法區分你期望調用哪個具體實現的時候,才需使用這種語法
trait Animal {
  fn baby_name() -> String;
}

struct Dog;

impl Dog {
  fn baby_name() -> String {
    String::from("Spot")
  }
}

impl Animal for Dog {
  fn baby_name() -> String {
    String::from("puppy")
  }
}

fn main() {
  println!("A baby dog is called a {}", Dog::baby_name()); // Dog 本身的關聯方法
  println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

使用 supertrait 來要求 trait 附帶其它 trait 的功能

  • 需要在一個 trait 中使用其它 trait 的功能:
    • 需要被依賴的 trait 也被實現
    • 那個被間接依賴的 trait 就是當前 trait 的 supertrait
use std::fmt;

trait OutlinePrint: fmt::Display {
  fn outline_print(&self) {
    let output = self.to_string();
    let len = output.len();
    println!("{}", "*".repeat(len + 4));
    println!("*{}*", " ".repeat(len + 2));
    println!("* {} *", output);
    println!("*{}*", " ".repeat(len + 2));
    println!("{}", "*".repeat(len + 4));
  }
}

struct Point {
  x: i32,
  y: i32,
}

impl OutlinePrint for Point {}

impl fmt::Display for Point {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "({}, {})", self.x, self.y)
  }
}

fn main() {}

使用 newtype 模式在外部類型上實現外部 trait

  • 孤兒規則:只有當 trait 或類型定義在本地包時,才能為該類型實現這個 trait
  • 可以通過 newtype 模式來繞過這一規則
    • 利用 tuple struct (元組結構體)創建一個新的類型
use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "[{}]", self.0.join(", "))
  }
}

fn main() {
  let w = Wrapper(vec![String::from("hello"), String::from("world")]);
  println!("w = {}", w);
}

三、高級類型

使用 newtype 模式實現類型安全和抽象

  • newtype 模式可以:
    • 用來靜態的保證各種值之間不會混淆並表明值的單位
    • 為類型的某些細節提供抽象能力
    • 通過輕量級的封裝來隱藏內部實現細節

使用類型別名創建類型同義詞

  • Rust 提供了類型別名的功能:
    • 為現有類型生產另外的名稱(同義詞)
    • 並不是一個獨立的類型
    • 使用 type 關鍵字
  • 主要用途:減少代碼字元重覆

例子一:

type Kilometers = i32;

fn main() {
  let x: i32 = 5;
  let y: Killometers = 5;
  println!("x + y = {}", x + y);
}

例子二:

fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {
  // --snip--
}

fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
  Box::new(|| println!("hi"))
}

fn main() {
  let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));
}

修改之後:

type Thunk = Box<dyn Fn() + Send + 'static>;

fn takes_long_type(f: Thunk) {
  // --snip--
}

fn returns_long_type() -> Thunk {
  Box::new(|| println!("hi"))
}

fn main() {
  let f: Thunk = Box::new(|| println!("hi"));
}

例子三:

use std::io::Error;
use std::fmt;

pub trait Write {
  fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
  fn flush(&mut self) -> Result<(), Error>;
  
  fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
  fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}

fn main() {
  
}

修改之後:

use std::fmt;

// type Result<T> = Result<T, std::io::Error>; // 聲明在 std::io 中

type Result<T> = std::io::Result<T>;

pub trait Write {
  fn write(&mut self, buf: &[u8]) -> Result<usize>;
  fn flush(&mut self) -> Result<()>;
  
  fn write_all(&mut self, buf: &[u8]) -> Result<()>;
  fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}

fn main() {
  
}

Never 類型

  • 有一個名為 ! 的特殊類型:
    • 它沒有任何值,行話稱為空類型(empty type)
    • 我們傾向於叫它 never 類型,因為它在不返回的函數中充當返回類型
  • 不返回值的函數也被稱作發散函數(diverging function)

例子一:

fn bar() -> ! { // 報錯 返回單元類型 不匹配
  
}

fn main() {}

例子二:

fn main() {
  let guess = "";
  
  loop {
    let guess: u32 = match guess.trim().parse() {
      Ok(num) => num,
      Err(_) => continue, // ! never 類型
    };
  }
}

註意:never 類型的表達式可以被強制的轉化為任意其它類型

例子三:

impl<T> Option<T> {
  pub fn unwrap(self) -> T {
    match self {
      Some(val) => val,
      None => panic!("called `Option::unwrap()` on a `None` value"), // !
    }
  }
}

例子四:

fn main() {
  println!("forever");
  
  loop {
    println!("and ever");
  }
}

動態大小和 Sized Trait

  • Rust 需要在編譯時確定為一個特定類型的值分配多少空間。
  • 動態大小的類型(Dynamically Sized Types,DST)的概念:
    • 編寫代碼時使用只有在運行時才能確定大小的值
  • str 是動態大小的類型(註意不是 &str):只有運行時才能確定字元串的長度
    • 下列代碼無法正常工作:
      • let s1: str = "Hello there!";
      • let s2: str = "How's it going?";
    • 使用 &str 來解決:
      • str 的地址
      • str 的長度

Rust使用動態大小類型的通用方式

  • 附帶一些額外的元數據來存儲動態信息的大小
    • 使用動態大小類型時總會把它的值放在某種指針後邊

另外一種動態大小的類型:trait

  • 每個 trait 都是一個動態大小的類型,可以通過名稱對其進行引用
  • 為了將 trait 用作 trait 對象,必須將它放置在某種指針之後
    • 例如 &dyn Trait 或 Box (Rc) 之後

Sized trait

  • 為了處理動態大小的類型,Rust 提供了一個 Sized trait 來確定一個類型的大小在編譯時是否已知
    • 編譯時可計算出大小的類型會自動實現這一 trait
    • Rust 還會為每一個泛型函數隱式的添加 Sized 約束
fn generic<T>(t: T) {}

fn generic<T: Sized>(t: T) {} // 上面的generic 會隱式的轉化為這種

fn main() {}
  • 預設情況下,泛型函數只能被用於編譯時已經知道大小的類型,可以通過特殊語法解除這一限制

?Sized trait 約束

fn generic<T>(t: T) {}

fn generic<T: Sized>(t: T) {} 

fn generic<T: ?Sized>(t: &T) {} // ? 只能用在 sized上

fn main() {}
  • T 可能是也可能不是 Sized
  • 這個語法只能用在 Sized 上面,不能被用於其它 trait

四、高級函數和閉包

函數指針

  • 可以將函數傳遞給其它函數
  • 函數在傳遞過程中會被強制轉換成 fn 類型
  • fn 類型就是 “函數指針(function pointer)”
fn add_one(x: i32) -> i32 {
  x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
  f(arg) + f(arg)
}

fn main() {
  let answer = do_twice(add_one, 5);
  
  println!("The answer is: {}", answer);
}

函數指針與閉包的不同

  • fn 是一個類型,不是一個 trait
    • 可以直接指定 fn 為參數類型,不用聲明一個以 Fn trait 為約束的泛型參數
  • 函數指針實現了全部3種閉包 trait(Fn、FnMut、FnOnce):
    • 總是可以把函數指針用作參數傳遞給一個接收閉包的函數
    • 所以,傾向於搭配閉包 trait 的泛型來編寫函數:可以同時接收閉包和普通函數
  • 某些情景,只想接收 fn 而不接收閉包:
    • 與外部不支持閉包的代碼交互:C 函數

例子一

fn main() {
  let list_of_numbers = vec![1, 2, 3];
  let list_of_strings: Vec<String> = list_of_numbers
  .iter().map(|i| i.to_string()).collect();
  
  let list_of_numbers = vec![1, 2, 3];
  let list_of_strings: Vec<String> = list_of_numbers
  .iter().map(ToString::to_string).collect();
}

例子二

fn main() {
  enum Status {
    Value(u32),
    Stop,
  }
  
  let v = Status::Value(3);
  
  let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
}

返回閉包

  • 閉包使用 trait 進行表達,無法在函數中直接返回一個閉包,可以將一個實現了該 trait 的具體類型作為返回值。
fn returns_closure() -> Fn(i32) -> i32 { // 報錯 沒有一個已知的大小
  |x| x + 1
}

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
  Box::new(|x| x + 1)
}

fn main() {
  
}

五、巨集

巨集 macro

  • 巨集在Rust里指的是一組相關特性的集合稱謂:
    • 使用 macro_rules! 構建的聲明巨集(declarative macro)
    • 3 種過程巨集
      • 自定義 #[derive] 巨集,用於 struct 或 enum,可以為其指定隨 derive 屬性添加的代碼
      • 類似屬性的巨集,在任何條目上添加自定義屬性
      • 類似函數的巨集,看起來像函數調用,對其指定為參數的 token 進行操作

函數與巨集的差別

  • 本質上,巨集是用來編寫可以生成其它代碼的代碼(元編程,metaprogramming)
  • 函數在定義簽名時,必須聲明參數的個數和類型,巨集可處理可變的參數
  • 編譯器會在解釋代碼前展開巨集
  • 巨集的定義比函數複雜得多,難以閱讀、理解、維護
  • 在某個文件調用巨集時,必須提前定義巨集或將巨集引入當前作用域:
  • 函數可以在任何位置定義併在任何位置使用

macro_rules! 聲明巨集(棄用)

  • Rust 中最常見的巨集形式:聲明巨集
    • 類似 match 的模式匹配
    • 需要使用 marco_rules!
// let v: Vec<u32> = vec![1, 2, 3];

#[macro_export]
macro_rules! vec {
  ($($x:expr),*) => {
    {
      let mut temp_vec = Vec::new();
      $(
        temp_vec.push($x);
      )*
      temp_vec
    }
  };
}

// let mut temp_vec = Vec::new();
// temp_vec.push(1);
// temp_vec.push(2);
// temp_vec.push(3);
// temp_vec

基於屬性來生成代碼的過程巨集

  • 這種形式更像函數(某種形式的過程)一些
    • 接收並操作輸入的 Rust 代碼
    • 生成另外一些 Rust 代碼作為結果
  • 三種過程巨集:
    • 自定義派生
    • 屬性巨集
    • 函數巨集
  • 創建過程巨集時:
    • 巨集定義必須單獨放在它們自己的包中,並使用特殊的包類型
use proc_macro;

#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
  
}

自定義 derive 巨集

  • 需求:
    • 創建一個 hello_macro 包,定義一個擁有關聯函數 hello_macro 的 HelloMacro trait
    • 我們提供一個能自動實現 trait 的過程巨集
    • 在它們的類型上標註 #[derive(HelloMacro)],進而得到 hello_macro 的預設實現
➜ cd rust

~/rust
➜ cargo new hello_macro --lib
     Created library `hello_macro` package

~/rust
➜ cd hello_macro

hello_macro on  master [?] via 

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

-Advertisement-
Play Games
更多相關文章
  • CSS 設置文字間距 一、設置字元間距 在 CSS 中,可以使用 letter-spacing 屬性來設置字元間距。該屬性控制每個字元之間的距離,可以設置負值來讓字元更接近,也可以設置正值來讓字元之間的距離更遠。 以下是一個示例,將字元間距設置為 0.1em: p { letter-spacing: ...
  • /*是否帶有小數*/ function isDecimal(strValue ) { var objRegExp= /^\d+\.\d+$/; return objRegExp.test(strValue); } /*校驗是否中文名稱組成 */ function ischina(str) { var ...
  • 最近,在 CodePen 上,看到一個非常有意思的圖片動效,效果如下: 原效果鏈接:CodePen Demo - 1 div pure CSS blinds staggered animation in 13 declarations 本身這個動畫效果,並沒有多驚艷。驚艷的地方在於原作者的實現方式非 ...
  • HTML學習筆記詳解 01 初識HTML HTML HTML,英文全稱為 Hyper Text Markup Language,中文翻譯為超文本標記語言,其中超文本包括:文字,圖片,音頻,視頻,動畫等 目前 目前主流使用的是HTML5+CSS3 HTML的優勢 主流瀏覽器都支持 微軟 GOOGLE ...
  • 當用戶在網頁中進行操作時,如點擊、滾動、輸入等,往往會頻繁地觸發事件。如果每個事件都立即執行相應的函數,可能會導致性能問題和用戶體驗不佳,因為這些函數可能需要執行複雜的操作,如計算、網路請求等。 為了優化這種情況,我們可以使用防抖和節流來限制函數的調用次數,從而提高性能和用戶體驗。 防抖 防抖是指在 ...
  • 簡介 原型模式(Prototype Pattern)是一種創建型設計模式,使你能夠複製已有對象,而無需使代碼依賴它們所屬的類,同時又能保證性能。 這種模式是實現了一個原型介面,該介面用於創建當前對象的克隆。當直接創建對象的代價比較大時,則採用這種模式。 如果你需要複製一些對象,同時又希望代碼獨立於這 ...
  • 閱讀原文:https://bysocket.com/openai-chatgpt-vs-developer/ ChatGPT 能取代多少程式員的工作?導致我們程式員失業嗎?這是一個很好的話題,我這裡分享下: 一、ChatGPT 是什麼?有什麼作用 ChatGPT是一種基於人工智慧技術的語言模型,是可 ...
  • 環境:CentOS 7.6_x64Python版本:3.9.12FreeSWITCH版本 :1.10.9 一、背景描述 ESL庫是FreeSWITCH對外提供的介面,使用起來很方便,但該庫是基於C語言實現的,Python使用該庫的話需要使用源碼進行編譯。如果使用系統自帶的Python版本進行編譯,過 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...