Rust 函数

函数

Rust支持函数式编程。

函数本身就是一种类型,函数类型变量可以作为其他函数的参数或者返回值,也可以赋值给别的变量,还可以直接调用执行。

定义函数

使用fn关键字定义函数,函数由函数签名和函数体组成。

函数签名由函数名、参数、返回值类型组成

函数体包含于{}内,是函数要执行的具体代码。

函数需要调用才会执行。

fn add(x: i32, y:i32) -> i32 {
    
    
    x+y   // 结尾没有分号
}

fn main() {
    
    
    let x = 5;
    let y = {
    
    
        let x = 2;
        x + 1  // 结尾没有分号
    };
    
    let sum = add(x,y);
    println!("{} + {} = {}",x,y,sum);
}
  • main函数

    main函数是程序的入口函数,对于可执行文件来说,main函数是必不可少的,对于库函数来说,main函数不是必须的

  • 函数体

    函数体由一系列语句和一个可选的结尾表达式构成。

    结尾没有分号,代表这是一个表达式而非语句,将会自动返回表达式的值,表示式的结尾如果加上分号,就变成了语句,语句没有返回值

  • 函数参数

    是函数签名的一部分。

    函数参数必须明确指定数据类型,但不能指定默认值。

    函数参数可以分为可变和不可变参数,默认不可变参数,需要可变操作时,需要加上mut关键字。

  • 返回值

    如果函数需要返回值给调用者,在函数定义时需要明确返回值类型。

    使用 -> 数据类型来定义

    函数只能有一个返回值,需要返回多个值时,可以使用元组类型

    Rust中每个函数都有返回值,即使没有显示返回值的函数,也会隐式地返回一个单元值()

    一般,函数隐式地返回函数体最后一个表达式的值

    可以使用return 语句来显示返回。

方法和函数

方法和函数类似,使用fn关键字定义,可以有参数、返回值和函数体。

结构体的方法必须在结构体的上下文中定义,也就是定义在impl块中。

在impl块中定义的不一定是方法,也有可能是关联函数。

方法要求第一个参数必须是self, 代表调用该方法的结构体实例。

在方法中使用&self能够读取实例中的数据,使用&mut self能够向实例中写入数据。

使用方法替代函数的最大好处在于组织性,将结构体实例的所有行为都一起放入impl块中

关联函数是指在impl块中定义,但是不以self作为参数的函数,与结构体相关联,但是不直接作用于结构体实例,常用作返回一个结构体实例的构造函数

调用:

  • 在方法内部可以使用self.字段名 来访问结构体的字段
  • 在方法外使用实例名.方法名 调用方法
  • 调用关联函数使用 结构体名::关联函数名
  • 在调用结构体方法时,第一个参数self不需要传递实参,由Rust编译器完成
pub struct Student {
    
    
    name :&'static str,
    score : i32,
}

impl Student {
    
    
    pub fn new(name : &'static str,socre:i32) ->Self{
    
    
		Student{
    
    name,socre}
    }
    
    pub fn get_name(&self) -> &str {
    
    
		self.name
    }           
}

fn main(){
    
    
    let mut student : Student = Student::new("wkk",22);
    println!("{:?}",student);
}
高阶函数

以函数为参数或者返回值的函数。

函数是一种类型,函数类型的变量可以像其他类型的变量一样使用,可以被直接调用执行,也可以作为其他函数的参数或者返回值。

实现这一切的基础是函数指针,函数指针类型使用fn() 来指定

函数指针

指向函数的指针,值是函数的地址

声明中必须显示指定函数后指针的类型fn()

fn hello(){
    
    
    println!("hello wkk");
}

fn main(){
    
    
    let fn_ptr: fn() = hello;
    let other_fn = hello; // 函数hello本身的类型 , other_fn 不是函数指针类型
}

不管是函数指针类型,还是函数本身的类型,都可以直接进行调用。

函数作为参数

函数作为参数时,为了提升代码可读性,可以使用type关键字为函数指针类型定义别名

type MathOp = fn(i32,i32)->i32;
函数作为返回值

函数作为返回值时,也可以使用type关键字为函数指针类型定义别名

type MathOp = fn(i32,i32) -> i32;
fn math_op(op:&str) -> MathOp {
    
    
    match op {
    
    
        "add" => add,
         _    => subtract,
    }
}
闭包

闭包是在一个函数内创建的匿名函数,虽然没有函数名,但可以将闭包赋值给一个变量,通过调用该变量完成闭包的调用。

闭包可以访问外层函数中的变量,即闭包是一个可以捕获外部环境变量的函数。外部环境变量是指闭包定义时所在的作用域中的变量

基本语法

闭包由管道符 || 和 大括号 {} 组合而成

管道符中指定闭包的参数,如果由多个参数,使用逗号分隔。

闭包的参数类型可以省略。

管道符后可以指定返回值类型,但不是必须的。

大括号即闭包提,用来存放执行语句。如果闭包体只有一行,大括号可以省略。

闭包体中最后一个表达式的值默认为闭包的返回值。

let add_one = |x:u32| -> u32 {
    
     x+1 };
类型推断

闭包不像函数那样严格要求为参数和返回值注明类型,因为闭包通常是应用相对较小的场景和上下文,编译器可以可靠的推断参数和返回值的类型

fn add_one_v1 (x :u32) -> u32 {
    
    
    x+1
}

let add_one_v2 = |x:u32| -> u32 {
    
    
    x+1
};

let add_one_v3 = |x| {
    
     x+1 };

let add_one_v4 = |x| x+1;

虽然编译器会为每个参数和返回值推断出一个具体的类型,但是如果多次调用同一个闭包却传递不同类型的参数会导致类型错误。

第一次调用闭包时,传递的参数类型,会作为这个闭包的参数类型。

捕获环境变量

闭包和函数的最大区别是,闭包可以捕获和使用其被定义时所在的作用域中的变量。

let i = 1;
let add = |x| x+i;
// 闭包捕获和使用了作用域中的变量i的值
迭代器

使用过for循环来遍历数据集合,并对每个元素执行指定的操作,但这一过程必须使用一个变量记录数据集合中每一次访问的位置

迭代器模式是将遍历数据集合的行为抽象为单独的迭代对象,这样在遍历集合时可以把集合中所有元素按顺序传递给处理逻辑。使用迭代器可以极大地简化数据操作

iterator trait

Iterator trait是迭代器模式的抽象接口,接口中有两个重要方法。

  • iter 方法用于返回一个迭代器实例
  • next 方法用户返回迭代器的下一元素,并将其封装在Some函数中。如果已经迭代到了集合的末尾(最后一个元素的后面),返回None

通过迭代器将数组转换为迭代器,再调用迭代器的next方法获取元素。

注意,声明的变量iter必须是可变的, 因为每一次调用next方法都会从迭代器中消费一个元素

let v = [1,2,3];
let mut iter = v.iter();

println!("{:?}",iter.next());   // Some(1)
消费器

Rust中的迭代器都是惰性的,它们不会自动发生遍历行为。Iterator trait中定义了一类方法,这类方法叫作消费器。

通过消费器可以消费迭代器中的元素,这些方法的默认实现都调用了next方法。

常用的消费器sum, any,collect

其他消费器: std::iter::Iterator 中可以找到

  1. sum

    sum可以对迭代器中的元素执行求和操作,它将迭代器中每个元素进行累加并返回总和。

    let v = [1,2,4];
    let total : i32 = v.iter().sum();
    
  2. any

    查找迭代器中是否存在满足条件的元素

    let v = [1,2,3,4];
    let res1 = v.iter().any(|&x| x == 2);
    let res2 = v.iter().any(|x| *x == 2);
    

    消费器any的内部实现中,调用迭代器的next 方法返回Option<&[T]> 或 Option<&mut[T]> 类型的值,再通过模式匹配得到&[T] 或者 &mut[T] 类型值。

    在消费器any调用闭包中只有使用引用和解引用两种方式。

  3. collect

    collect可以将迭代器转换成指定的容器类型,即将迭代器中元素收集到指定的容器中。

    let v1 = [1,2,3];
    let v2:Vec<i32> = v1.iter().map(|x| x+1).collect();
    

    使用map方法对原迭代器中的每个元素调用闭包执行加1的操作并生成一个新的迭代器

迭代器适配器

适配器Iterator trait中定义了一类方法,这类方法叫作迭代器适配器

将当前迭代器转换成另一种类型的迭代器,并支持链式调用多个迭代器适配器

由于所有的迭代器都是惰性的,必须使用一个消费器来获取迭代器适配器的调用结果。

常用的迭代器适配器map, take,filter,rec 和zip

其他迭代器适配器可在std::iter中查找

  1. map

    适配器map对迭代器中每个元素调用闭包并生成一个新的迭代器。

    let v = [1,2,3];
    let result : Vec<i32> = v.iter()
    		.map(|x| x+3).collect();
    // 调用collect 方法将迭代器中的元素收集到动态数组中
    
  2. take

    take 生成一个仅迭代原迭代器中前n个元素的新迭代器,常用于遍历指定数量的场景。

    let v = [1,2,3];
    let res = v.iter().take(3);
    
  3. filter

    适配器filter 对迭代器中每个元素调用闭包并生成一个过滤元素的新迭代器。

    闭包会返回true或者false ,如果返回true则该元素放入新的迭代器,否则将该元素忽略。

    let v = [1,2,3];
    let result : Vec<i32> = v.iter()
    	.map(|x| x+3 )
    	.filter(|x| x%3 == 0)
    	.collect();
    
  4. rev

    通常迭代器从左到右进行迭代。

    适配器rev可以反转迭代方法,生成一个方向相反的新迭代器,即新的迭代方向将从右向左进行迭代。

    let v = [1,2,3];
    let res = v.iter().rev();
    
  5. zip

    适配器zip可以将两个迭代器压缩在一起生成一个新的迭代器。

    它在两个迭代器上同时迭代并返回一个元组,第一个元素来自第一个迭代器,第二个元素来自第二个迭代器。

    如果两个迭代器中任一个迭代器返回None, 适配器zip返回None

    let v1 = [1,2,3];
    let v2 = [2,3,5];
    let result:Vec<i32> = v1.iter().zip(v2.iter())
    	.map(|(a,b)| a+b)
    	.filter(|x| x%3 == 0)
    	.collect();
    

猜你喜欢

转载自blog.csdn.net/first_bug/article/details/128008801