当前位置: 首页 > article >正文

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


http://www.kler.cn/a/393138.html

相关文章:

  • 鸿蒙next版开发:ArkTS组件点击事件详解
  • qt QProcess详解
  • 【ChatGPT】 如何让ChatGPT分析数据并得出结论
  • matlab建模入门指导
  • 基于yolov8、yolov5的番茄成熟度检测识别系统(含UI界面、训练好的模型、Python代码、数据集)
  • 设计模式:工厂方法模式和策略模式
  • 案例学习java
  • AI大模型开发架构设计(18)——基于大模型构建企业知识库案例实战
  • 24年下软考网络工程师真题及答案,估分、备考速看!
  • React第一个项目
  • 【Lucene】详细讲解创建索引的步骤:分词、去停用词、语言处理、倒排表构建
  • 深入了解支持向量机:机器学习中的经典算法
  • Ue5 umg学习(三)文本控件
  • 交互新体验:Axure动态面板下的图片拖动技巧
  • 统信UOS开发环境支持rust
  • 计算机网络:运输层 —— TCP 协议概述与 TCP 报文段首部格式
  • JavaWeb常见注解
  • Flutter【05】企业级Flutter架构实践
  • 鸿蒙生态:开发者的新征程与挑战并存
  • conda和pip的镜像源配置和删除
  • 在双显示器环境中利用Sunshine与Moonlight实现游戏串流的同时与电脑其他任务互不干扰
  • k8s拓扑域 :topologyKey
  • 快递物流查询API接口如何用C#调用
  • Docker 安装Immich教程
  • 【Linux】内核模版加载modprobe | lsmod
  • 【Java Web】分页查询