- Rust編程:入門、實戰與進階
- 朱春雷
- 3067字
- 2021-04-30 12:37:26
4.1 函數
函數是Rust程序的基本構造單位,也是程序執行的基本語法結構。Rust中函數不僅承載了諸如高階函數、參數模式匹配等函數式編程的特性,還包含了為結構體及其實例實現方法面向對象范式的特性。
除Rust核心庫和標準庫提供的函數外,執行一個特定任務的代碼也可以被封裝成函數,使代碼更具可讀性和復用性。本節會介紹普通函數的定義與使用、函數與方法的區別以及高階函數的定義與使用。
4.1.1 定義函數
Rust中函數使用fn關鍵字定義,由函數簽名和函數體組合而成。函數簽名由函數名、參數和返回值類型組成,主要作用是防止定義兩個相同簽名的函數。函數名建議使用snake case規范風格,所有字母都是小寫并使用下劃線分隔單詞。參數用于將外部變量或者值傳遞到函數內部使用,但參數不是必需的,可以置空。同樣,返回值類型也不是必需的,如果函數需要返回某個值,應在函數簽名中指定返回值類型。函數體被包含于一對大括號之內,是函數要執行的具體代碼。
函數需要調用才會被執行,讓函數運行起來的過程叫作函數調用。在fn1函數中調用fn2函數,fn1函數就叫作函數調用者。函數定義時指定的參數叫作形參,調用函數時傳遞給函數的變量或者值叫作實參。當一個函數擁有參數(形參),在調用該函數時必須為這些參數提供具體的值(實參),并且函數調用時傳遞的實參數量和類型必須與形參的數量和類型一致。
先來看一個簡單的函數定義與調用的示例,如代碼清單4-1所示。
代碼清單4-1 函數定義與調用
1 fn add(x: i32, y: i32) -> i32 { 2 x + y 3 } 4 5 fn main() { 6 let x = 5; 7 let y = { 8 let x = 2; 9 x + 1 10 }; 11 12 let sum = add(x, y); 13 println!("{} + {} = {}", x, y, sum); 14 } 15 16 // 5 + 3 = 8
(1)main函數
main函數是程序的入口點。對于二進制可執行文件來說,main函數是必不可少的。對于庫函數來說,main函數不是必需的。
(2)函數體
函數體由一系列語句和一個可選的結尾表達式構成。代碼清單4-1中,main函數的函數體內聲明了兩個變量x和y,調用add函數執行加法計算,并將計算結果作為返回值與變量sum綁定。第7~10行代碼賦值語句的右側是一個代碼塊,它的返回值是3,這個值與變量y綁定。需要注意的是,第9行代碼中“x+1”的結尾沒有分號,代表這是一個表達式而非語句,將會自動返回表達式的值。表達式的結尾如果加上分號,它就變成了語句,而語句沒有返回值。
(3)函數參數
函數參數是一種特殊變量,它是函數簽名的一部分。Rust要求函數參數必須明確指定數據類型,但不能指定默認值。函數參數分為可變和不可變參數,默認是不可變參數。當需要可變操作時,可以使用mut關鍵字。函數如果有多個參數,可以使用逗號分隔。
代碼清單4-1中,第1行代碼的add函數有兩個i32類型的形參x和y,第12行代碼調用add函數時向其傳遞兩個實參,實參x的值是5,實參y的值是3。
(4)返回值
如果函數需要返回值給調用者,在函數定義時就要明確返回值的類型,這可以在函數簽名中使用“→”加上數據類型來定義。函數只能有唯一的返回值,如果需要返回多個值,可以使用元組類型。Rust中每個函數都有返回值,即使是沒有顯式返回值的函數,也會隱式地返回一個單元值()。
大部分情況下,函數隱式地返回函數體最后一個表達式的值。add函數體中最后一個表達式“x+y”的值將默認視為函數的返回值,即第2行代碼“x+y”等同于“return x+y;”。但是,對于流程控制結構中的循環或條件判斷分支,如果需要提前退出函數并返回指定的值,必須顯式地使用return語句來返回。
不管是顯式還是隱式,函數體中返回值的類型必須和函數簽名中返回值的類型一致,否則將會導致程序錯誤。
4.1.2 方法和函數
方法來自面向對象的編程范式,它表示某個類型實例的行為。方法與函數類似,使用fn關鍵字定義,可以有參數、返回值和函數體。2.3.3節介紹過關于結構體的知識,這里以結構體為例介紹結構體的方法和關聯函數。
結構體的方法必須在結構體的上下文中定義,也就是定義在impl塊中。需要注意的是,定義在impl塊中的不一定是方法,有可能是關聯函數。方法要求第一個參數必須是self,它代表調用該方法的結構體實例。在方法中使用&self能夠讀取實例中的數據,使用&mut self能夠向實例中寫入數據。使用方法替代函數的最大好處在于組織性,應該將結構體實例所有的行為都一起放入impl塊中。
關聯函數是指在impl塊中定義,但又不以self作為參數的函數。它與結構體相關聯,但不直接作用于結構體實例,常用作返回一個結構體實例的構造函數。
代碼清單4-2中,結構體Student的impl塊中定義了get_name、set_name、get_score、set_score這4個方法和new關聯函數。方法的第一個參數都是self,在方法內部可以使用“self.字段名”語法來訪問結構體的字段,在方法外部可使用“實例名.方法名”語法調用方法。調用關聯函數可使用“結構體名::關聯函數名”語法。在調用結構體方法時,第一個參數self不需要傳遞實參,這個參數的傳遞是由Rust編譯器完成的。因為在impl Student上下文,Rust知道self的類型是結構體Student,即&self等價于student: &Student。
代碼清單4-2 結構體方法和關聯函數
1 #[derive(Debug, PartialEq)] 2 pub struct Student { 3 name: &'static str, 4 score: i32, 5 } 6 7 impl Student { 8 pub fn new(name: &'static str, score: i32) -> Self { 9 Student { name, score } 10 } 11 12 pub fn get_name(&self) -> &str { 13 self.name 14 } 15 16 pub fn set_name(&mut self, name: &'static str) { 17 self.name = name; 18 } 19 20 pub fn get_score(&self) -> i32 { 21 self.score 22 } 23 24 pub fn set_score(&mut self, score: i32) { 25 self.score = score; 26 } 27 } 28 29 fn main() { 30 let mut student: Student = Student::new("zhangsan", 59); 31 println!("name: {}, score: {}", student.get_name(), student.get_score()); 32 33 student.set_score(60); 34 println!("{:?}", student); 35 } 36 37 // name: zhangsan, score: 59 38 // Student { name: "zhangsan", score: 60 }
4.1.3 高階函數
高階函數是指以函數為參數或返回值的函數,是函數式編程語言最基礎的特性。函數是一種類型,函數類型的變量可以像其他類型的變量一樣使用,既可以被直接調用執行,也可以作為其他函數的參數或返回值。實現這一切的基礎是函數指針,函數指針類型使用fn()來指定。
1. 函數指針
函數指針是指向函數的指針,其值是函數的地址。代碼清單4-3中,第6行代碼聲明了一個函數指針fn_ptr,在聲明中必須顯式指定函數指針類型fn()。需要注意的是,等號右側使用的是函數名hello,而不是調用函數hello。第7行代碼打印出fn_ptr的指針地址,證明其是一個函數指針。
代碼清單4-3 函數指針
1 fn hello() { 2 println!("hello function pointer!"); 3 } 4 5 fn main() { 6 let fn_ptr: fn() = hello; 7 println!("{:p}", fn_ptr); 8 9 let other_fn = hello; 10 // println!("{:p}", other_fn); 11 12 fn_ptr(); 13 other_fn(); 14 } 15 16 // 0x1000f1020 17 // hello function pointer! 18 // hello function pointer!
第9行代碼變量other_fn聲明中沒有指定函數指針類型fn(),如果取消第10行的注釋,編譯代碼會得到如下錯誤提示。
error[E0277]: the trait bound `fn() {hello}: std::fmt::Pointer` is not satisfied --> src/main.rs:10:22 | 10 | println!("{:p}", other_fn); | ^^^^^^^^ the trait `std::fmt::Pointer` is not implemented for `fn() {hello}` | = note: required by `std::fmt::Pointer::fmt`
根據錯誤信息可知,other_fn的類型實際上是fn() {hello},這是函數hello本身的類型,而非函數指針類型。但是,從第12、13行代碼可以看到,不管是函數指針類型,還是函數hello本身的類型,都可以直接進行調用。
2. 函數作參數
函數作為參數時,為了提升代碼可讀性,可以使用type關鍵字為函數指針類型定義別名。
代碼清單4-4中,第1行代碼使用type關鍵字為函數指針類型fn(i32, i32) -> i32定義了別名MathOp。第2行代碼中math函數的第一個參數op的類型是MathOp,因此math是高階函數。main函數中調用math函數,并傳入add或subtract函數作為實參,它們會自動轉換成函數指針類型。
代碼清單4-4 函數作參數
1 type MathOp = fn(i32, i32) -> i32; 2 fn math(op: MathOp, x: i32, y: i32) -> i32 { 3 println!("{:p}", op); 4 op(x, y) 5 } 6 7 fn add(x: i32, y: i32) -> i32 { 8 x + y 9 } 10 11 fn subtract(x: i32, y: i32) -> i32 { 12 x - y 13 } 14 15 fn main() { 16 let (x, y) = (8, 3); 17 println!("add operation result: {}", math(add, x, y)); 18 println!("subtraction operation result: {}", math(subtract, x, y)); 19 } 20 21 // 0x104860fb0 22 // add operation result: 11 23 // 0x104860ff0 24 // subtraction operation result: 5
3. 函數作返回值
函數作為返回值時,也可以使用type關鍵字為函數指針類型定義別名。代碼清單4-5中,第2行代碼math_op函數的返回值是函數指針類型,函數體中使用match模式匹配,如果字符串字面量是“add”則返回add函數,否則返回subtract函數。這里的add和subtract都是函數名。
代碼清單4-5 函數作返回值
1 type MathOp = fn(i32, i32) -> i32; 2 fn math_op(op: &str) -> MathOp { 3 match op { 4 "add" => add, 5 _ => subtract, 6 } 7 } 8 9 fn add(x: i32, y: i32) -> i32 { 10 x + y 11 } 12 13 fn subtract(x: i32, y: i32) -> i32 { 14 x - y 15 } 16 17 fn main() { 18 let (x, y) = (8, 3); 19 20 let mut op = math_op("add"); 21 println!("operation result: {}", op(x, y)); 22 23 op = math_op("divide"); 24 println!("operation result: {}", op(x, y)); 25 } 26 27 // operation result: 11 28 // operation result: 5
- Progressive Web Apps with React
- Android Jetpack開發:原理解析與應用實戰
- 程序員面試筆試寶典
- Building Mobile Applications Using Kendo UI Mobile and ASP.NET Web API
- 信息安全技術
- 軟件架構:Python語言實現
- Android系統原理及開發要點詳解
- SQL Server與JSP動態網站開發
- Haskell Data Analysis Cookbook
- 區塊鏈技術與應用
- Building Microservices with .NET Core
- AutoCAD基礎教程
- HTML5 WebSocket權威指南
- Learning Gerrit Code Review
- 歐姆龍PLC編程指令與梯形圖快速入門