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