动态

详情 返回 返回

rust學習二十.13、RUST的函數指針fn和匿名函數 - 动态 详情

函數指針是一個好東西。

一、簡述

函數指針就是執行一段函數代碼的指針。

根據官方的説法,函數指針實現了FnOnce,FnMut,Fn特質。

由於函數指針是一種數據類型,所以rustc允許它作為函數/方法的參數,這樣就給程序設計添加了不少的靈活性.

我估摸着,rust設計者就是為了讓rust能夠適應潮流:在函數/方法中直接傳遞匿名函數/閉包

一個典型的帶有函數指針的rust函數定義如下:

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32
這種定義方式其實某種程度上更加符合我的思維方式。
 
在java中,我們不能那麼定義,最多隻能定義一個函數接口為參數,例如:
 
void Fight(Tool tool,Target tar)

它看起來不如rust來的那麼明顯。rust的定義能夠讓我立刻意識到這是函數指針參數(意味着這是一段代碼)。

當然,只要願意,應該也可以把函數指針以不那麼明顯的方式定義,後文有例子.

二、示例

fn add_one(x: i32) -> i32 {
    x + 1
}
fn multiply_10(x: i32) -> i32 {
    x * 10
}

type  FnPointer = fn(i32,i32) -> String;

fn  link(f: FnPointer, x:i32,y:i32) {
   let dst= f(x,y);
   println!("{}",dst);
}

/**
 * 第一個參數是函數指針,fn指針實現了FnOnce,FnMut,Fn特質,所以可以接受匿名函數作為參數。
 */
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}


fn 書本例子() {
    let list_of_numbers = vec![1, 2, 3];
    //利用rustc,象我這樣的傻瓜也能寫rust!
    let list_of_strings: Vec<String> =
        list_of_numbers.iter().map(|i| "0".to_owned()+&i.to_string()).collect();
    println!("{:?}", list_of_strings);
    let list_of_strings2: Vec<String> =
        list_of_numbers.iter().map(ToString::to_string).collect();
    println!("{:?}", list_of_strings2);
}


fn main() {
    //封裝再封裝,就可以解決一些複雜問題,至於是否高效是另外一回事了.
    let funcs=[add_one, multiply_10];
    let numbers = [123, 233, 3232, 32334, 5333];
    for i in 0..numbers.len() {
        let answer1 = do_twice(funcs[0], numbers[i]);
        let answer2=  do_twice(funcs[1], numbers[i]);
        println!("({}+1)*2= {},({}*10)*2={}",numbers[i],answer1,numbers[i],answer2);    
    }

    // do_twice同時也能接受匿名函數,rust的解釋:fn指針實現了FnOnce,FnMut,Fn特質,所以可以接受匿名函數作為參數。
    
    println!("\n匿名函數也能被接受作為參數的演示....");
    let answer3 = do_twice(|x| x * 2+99, numbers[0]);
    println!("{}經過處理後等於{}",numbers[0],answer3);

    // 書本例子,讓我們感興趣是類似java等語言的流式操作
    書本例子();

    //類型別名
    let link_to = |x:i32,y:i32| x.to_string()+&y.to_string();
    link(link_to, 123,899);
}

 

上例基本模仿書本,主要演示兩個問題:

1.函數指針如何定義

2.使用函數指針作為參數的函數,也能接收匿名函數為入參

另外順便演示瞭如何讓函數指針看起來不那麼明顯(效果上類似java等一些語言),以下代碼用於達成這個目的:

type  FnPointer = fn(i32,i32) -> String;
fn  link(f: FnPointer, x:i32,y:i32) {
   let dst= f(x,y);
   println!("{}",dst);
}

 

如果函數指針比較複雜,也許用上別名也不錯。

輸出結果:

三、函數指針的替代方案

在函數/方法中除了可以通過指定函數指針的方式來傳遞函數,其實還有幾種方式:

1.利用特質綁定+通用類型

2.還是特質綁定+通用類型,只不過是另外一種書寫形式

3.使用特質對象+動態分發dyn

以下就是例子:

/**
 * 函數指針的替代者1  -- 利用特質綁定
 */ 
fn batman<T: FnOnce(i32,i32)->String>(f:T) {
    let result=f(1094,101);
    println!("{}",result);
}
/**
 * 函數指針的替代者2 -- 特質綁定的簡化寫法,rust 1.13引入的trait bound syntax
 */
fn eggman(f:impl FnOnce(i32,i32)->String) {
    let result=f(1094,103);
    println!("{}",result);
}
/**
 * 函數指針的替代者3 -- 使用特質對象
 */
fn pigman(f:&dyn Fn(i32,i32)->String) {
    let result=f(2094,103);
    println!("{}",result);
}

fn main(){
    batman(|x:i32,y:i32| x.to_string()+&y.to_string()+" 黑蝙蝠俠 🦇");
    eggman(|x:i32,y:i32| x.to_string()+&y.to_string()+" 蛋蛋大俠 🥚🥚");
    pigman(&|x:i32,y:i32| x.to_string()+&y.to_string()+" 豬豬俠士 🐷");   
}

 

輸出結果:

這裏例子中,比較特殊的就是pigman的定義:

fn pigman(f:&dyn Fn(i32,i32)->String)

這裏的定義比較特殊,用上了&dyn,為什麼要這樣了?

  • 因為特質對象必須使用動態分發,這是rust規定的
  • 要使用&,是因為特質對象大小不確定,rustc不知道大小,所以只能加一個引用,以便構成一個指針(編譯器可以知道大小)

最後,pigman用了Fn,而不是FnOnce,這是因為FnOnce要求移出,要求移出則必須實現Copy。為了不Copy,所以改為Fn。

記住,rust中函數/方法本身也是一種類型。

四、小結

1.函數指針fn的存在某種程度上方便了程序設計-這讓程序設計更加靈活

2.函數指針實現了FnOnce,FnMut,Fn,按照rust的説法就是可以定義一個匿名函數作為函數指針傳遞給函數/方法

3.函數指針的存在,使得編寫代碼某種程度上方便了部分程序員

4.事實上,就是沒有函數指針,其實也有其它方式能過實現類似的效果

 

Add a new 评论

Some HTML is okay.