Rust编程与项目实战-函数指针

【图书介绍】《Rust编程与项目实战》-CSDN博客

《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)

Rust编程与项目实战_夏天又到了的博客-CSDN博客

Rust是一种现代系统编程语言,它支持函数指针。函数指针是指向函数的指针,可以将函数作为参数传递给其他函数或存储在变量中。Rust中的函数指针可以用于实现回调函数、动态分发和多态等功能。本节将介绍Rust中的函数指针的基本用法和高级用法。

9.3.1 什么是函数指针

如果在程序中定义了一个函数,那么在编译时,编译系统会为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针。可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。例如:

let pfunc: fn(i32)->i32;

pfunc就是一个指向函数的指针变量,且这个函数有一个i32类型的参数,并且返回值的类型也是i32。关键字fn表示函数的意思,这里相当于指代某个函数名。

9.3.2 用函数指针变量调用函数

如果想调用一个函数,除可以通过函数名调用外,还可以通过指向函数的指针变量来调用该函数。下面先通过一个简单的例子来回顾一下函数的调用情况,代码如下:

fn add_one(x:i32) -> i32  					//定义一个函数
{
    x + 1 
} 

fn main() 
{ 
    let res = add_one(5);  					//通过函数名来调用函数
    println!("The answer is: {}",res); 
}

在代码中,add_one(5)就是通过函数名来调用函数的,返回一个整型变量。然后通过函数指针变量来调用函数。运行结果:The answer is: 6。

【例9.5】 通过函数指针变量调用函数

打开VS Code,输入代码如下:

fn add_one(x:i32) -> i32 
{
    x + 1 							//返回加1的结果
} 

fn main() 
{ 
    //定义一个函数指针变量pfunc,并指向一个函数
    let pfunc: fn(i32)->i32;  	//此时只是定义好了函数指针变量,还没具体指向某个函数
    pfunc = add_one;  			//把具体的函数赋给函数指针变量pfunc
    println!("The answer is: {}", pfunc(5));  		//通过pfunc来调用函数
}

在代码中,我们定义了一个函数add_one,它有一个i32类型的参数,并返回一个i32类型的整数。在main中,我们定义了一个函数指针变量pfunc,并指向一个带有i32类型的参数并返回i32类型整数的函数,此时只是定义了函数指针变量,还没具体指向某个函数。然后通过函数名add_one赋值给pfunc后,pfunc就指向函数add_one了,也就是pfunc存储的内容就是函数add_one在内存中的入口地址。最后,通过pfunc(5)来调用函数add_one,效果相当于add_one(5)。

编译运行,运行结果如下:

The answer is: 6

由此看出,无论是通过函数名调用函数,还是通过函数指针变量调用函数,结果都一样。

另外需要注意以下几点:

(1)定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义时指定的类型的函数。例如"let pfunc: fn(i32)->i32;"表示指针变量pfunc只能指向函数返回值为整型且有一个整型参数的函数。在程序中把哪一个函数(该函数的返回值是整型的且有一个整型参数)的地址赋给它,它就指向哪一个函数。在一个程序中,一个指针变量可以先后指向同类型的不同函数。

(2)如果要用指针调用函数,必须先使指针变量指向该函数。例如pfunc=add_one,就把add_one函数的入口地址赋了指针变量pfunc。

(3)在给函数指针变量赋值时,只需给出函数名而不必给出参数,例如pfunc=add_one,因为是将函数入口地址赋给p,而不牵涉实参与形参的结合问题。如果写成pfunc=add_one(5);就会出错了。pfunc=add_one(5);是将调用add_one函数所得到的函数返回值赋给pfunc,而不是将函数入口地址赋给pfunc。

(4)用函数指针变量调用函数时,只需使用pfunc代替函数名即可,此时需要在括号中根据需要写上实参。例如d=pfunc(5);表示"调用由pfunc指向的函数,实参为5,得到的函数返回值赋给d"。请注意函数返回值的类型。从指针变量pfunc的定义中可以知道,函数的返回值应是整型。

(5)用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。比如下面的实例,让用户选择1或2,选1时调用max函数,输出二者中的大数,选2时调用min函数,输出二者中的小数。

【例9.6】 根据选择调用不同的函数

打开VS Code,输入代码如下:

fn max(a:i32,b:i32) -> i32		//定义函数max,求两个数的较大值
{
    if a > b { return a; }  		//如果a大于b,则返回a
    else { return b; }    		//否则返回b
} 

fn min(a:i32,b:i32) -> i32  		//定义函数min,求两个数的较小值
{
    if a < b { return a; }  		//如果a小于b,则返回a
    else { return b; }  			//否则返回b
} 

fn main() 
{ 
    let p: fn(i32,i32)->i32;		/定义函数指针变量p,指向一个有两个整型参数的函数,返回整型值
    let choose =2;  				//模拟用户选择,选择1就是执行max,否则执行min
    let (a,b) = (5,6);			//定义两个整型变量,以后作为函数的实参
    if choose==1 { p=max;}		//当choose是1时,让p指向max
    else {p=min;}  				//当choose不是1时,则让p指向min

    let res = p(5,6);				//通过函数指针调用函数
    if choose==1 { println!("the max of {} and {} is {}",a,b,res)}		//输出a和b的较大值
    else {println!("the min of {} and {} is {}",a,b,res)} 		//输出a和b的较小值
}

这个例子说明怎样使用指向函数的指针变量。定义两个函数max和min,分别用来求大数和小数。在主函数中根据choose是1或2,使指针变量指向max函数或min函数。这就体现了函数指针的灵活性。

这个例子比较简单,只是示意性的,但它很有实用价值。在许多应用程序中,常用菜单提示输入一个数字,然后根据输入的不同值调用不同的函数,以实现不同的功能,就可以使用此方法。当然,也可以不用指针变量,而用if语句或 switch语句进行判断,直接通过函数名调用不同的函数。但是显然用指针变量可以使程序更简洁和专业。事实上,在大型软件中,函数指针的使用非常普遍。

9.3.3 函数指针做函数参数

指向变量的指针做参数我们已经学习过了,而指向函数的函数指针变量也可以用来做函数的参数。我们看下面的实例。

【例9.7】 函数指针做函数参数

打开VS Code,输入代码如下:

fn add_one(x:i32) -> i32   //定义一个函数,实现参数x加1的功能,并返回加1后的结果
{
    x + 1 
} 

fn plus(f: fn(i32)->i32, arg: i32) -> i32		//实现把arg加1的结果再相互加一下
{
    //如果f指向的函数是add_one,则相当于做add_one(arg+add_one(arg)
    //plus返回的结果是(arg+1)+(arg+1)
    f(arg) + f(arg) 
} 

fn main() 
{ 
    let answer = plus(add_one, 5);//调用plus函数,参数是函数名add_one传给函数指针f
    println!("The answer is: {}", answer); 		//输出结果
}

函数plus的第一个参数就是一个函数指针,并且在plus内部,我们通过函数指针f执行了两次函数调用,但调用的是谁呢?在plus内部不知道,只有等到main中的plus调用时,传递了add_one这个函数名给f之后,才知道plus内部将做两次add_one的调用,并且把结果相加后返回给整型变量answer。f指向的函数是add_one,则plus返回的结果是(arg+1)+(arg+1),又因为arg的值是5,因此最终answer的值是12。

编译运行,运行结果如下:

The answer is: 12

有人可能会问,既然在plus函数中要调用add_one函数,为什么不直接调用add_one而要用函数指针变量呢?何必绕这样一个圈子呢? 比如plus可以这样写:

fn add_one(x:i32) -> i32 
{
    x + 1 
} 
  
fn plus(arg: i32) -> i32 
{
    add_one(arg) + add_one(arg) 
} 

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

的确,如果只是用到add_one函数,完全可以在plus函数中直接调用add_one,而不必使用函数指针变量f。但是,如果在每次调用plus函数时,内部要调用的函数不是固定的,这次调用add_one,而下次要调用add_two,第3次要调用的就是add_three了。这时,使用函数指针变量就比较方便了。只要在每次调用plus函数时给出不同的函数名作为实参即可,plus函数不必做任何修改。这种方法是符合结构化程序设计方法原则的,是程序设计中常使用的,目的就是降低耦合性,这样可以把函数的实现和调用分开,让两个人并行完成,以此提高项目进度。
在下面的实例中,我们让作为参数的函数指针指向不同的函数。

【例9.8】 让作为参数的函数指针指向不同的函数

打开VS Code,输入代码如下:

fn max(a:i32,b:i32) -> i32    				//定义函数max,求两个数的较大值
{
    if a > b { return a; }  					//如果a大于b,则返回a
    else { return b; }    					//否则返回b
} 

fn min(a:i32,b:i32) -> i32  					//定义函数min,求两个数的较小值
{
    if a < b { return a; }  					//如果a小于b,则返回a
    else { return b; }  						//否则返回b
} 

fn mf(p: fn(i32,i32)->i32, a: i32,b:i32) -> i32 	//实现把arg加1的结果再相互加一下
{
    return p(a,b);	  //如果p指向max,则执行max(a,b),如果p指向min,则执行min(a,b)
} 

fn main() 
{ 
    let choose =2;	  //模拟用户选择,选择1就执行max,否则执行min
    let (a,b,mut res) = (5,6,0); //定义3个整型变量,a和b作为函数的实参,res存放函数结果
    if choose==1 {res = mf(max,a,b);} 		//当choose为1时,传max给参数p
    else {res = mf(min,a,b)}  				//当choose不为1时,传min给参数p
    
    if choose==1 { println!("the max of {} and {} is {}",a,b,res)}	//输出a和b的较大值
    else {println!("the min of {} and {} is {}",a,b,res)}     //输出a和b的较小值
}

通过这个实例,应该能体会函数指针作为参数的灵活性了,可以根据选择(变量choose的值)来决定传哪个函数到mf中,继而在mf中执行max或者min。

编译运行,运行结果如下:

the min of 5 and 6 is 5
相关推荐
Swift社区6 分钟前
Excel 列名称转换问题 Swift 解答
开发语言·excel·swift
一道微光10 分钟前
Mac的M2芯片运行lightgbm报错,其他python包可用,x86_x64架构运行
开发语言·python·macos
矛取矛求15 分钟前
QT的前景与互联网岗位发展
开发语言·qt
Leventure_轩先生15 分钟前
[WASAPI]从Qt MultipleMedia来看WASAPI
开发语言·qt
向宇it29 分钟前
【从零开始入门unity游戏开发之——unity篇01】unity6基础入门开篇——游戏引擎是什么、主流的游戏引擎、为什么选择Unity
开发语言·unity·c#·游戏引擎
wm104331 分钟前
java web springboot
java·spring boot·后端
是娜个二叉树!1 小时前
图像处理基础 | 格式转换.rgb转.jpg 灰度图 python
开发语言·python
Schwertlilien1 小时前
图像处理-Ch5-图像复原与重建
c语言·开发语言·机器学习
liuyunshengsir1 小时前
Squid代理服务器的安装使用
开发语言·php
只做开心事1 小时前
C++之红黑树模拟实现
开发语言·c++