Location>code7788 >text

C++ function pointers

Popularity:852 ℃/2024-10-03 05:00:52

summarize

This article describes in detail pointers to common functions and class member functions in C/C++. Combined with C++ code examples, it explains how function pointers can be used as inputs to other functions, return values, and practical tips on how typedef can improve code readability. For pointers to class member functions (methods), they are categorized into static and non-static. Finally, the C++ syntax for declaring, assigning, and defining ordinary functions, non-static member functions of classes, and static member functions of classes is summarized for review.

Pointers to Common Functions

Declarations, Definitions and Assignments

First let's distinguish between the following 4 declaration statements:

int x; // x is a variable of type int
int *x; // x is a pointer to a variable of type int
int *x(); // x is a function that returns a pointer of type int
int (*x)(); // x is a pointer to a function with null input parameters and an int pointer return type

Thus, to change the declaration statement of a function named fun into a declaration statement for the function pointer variable pfun, you simply need to set thefun becomes (*pfun), the others remain unchanged (note: parentheses are essential!) .

double fun(string& str1, string &str2); // declaration of function fun
double (*pfun)(string& str1, string &str2); // declaration of function pointer variable pfun

In C/C++, the name of an array variable is a pointer constant to the type of the array element (holding the address of the first element), and similarly, the name of a function denotes a pointer constant to the function (holding the address of the function's entry):

int array[5]; // array is a pointer constant to int
int *p = array; // p is a constant pointer to int, p = array is legal
p[3]; // Equivalent to array[3].

void test(int a){ cout << "a=" << a << endl;} // test is a function pointer constant with input parameter int and return type void
void (*pf)(int); // Declare function pointer pf: pf is a pointer to a function that takes int as its input and returns void.
pf = test; // assign the address of the test function to pf (both must have the same input argument list and return type)
pf(123); // Equivalent to test(123).

There are a few points that need to be clarified:

// Declarations and assignments can be written together:
void (*pf)(int) = test;

// 2 types of assignment: (equivalent)
pf = test; // direct function name assignment
pf = &test; // function-name address assignment, equivalent to pf = test

// 2 types of calls: (equivalent)
pf(123); // direct pf call with arguments
(*pf)(123); // dereference function pointer first, then call with arguments, equivalent to pf(123)

Example 1:

#include <iostream>
using namespace std;

int add(int x, int y){
	return x + y;
}

int substract(int x, int y){
	return x - y;
}

int main(){
	int (*fp)(int, int); // Defining function pointer variables:fp
	fp = add; // Combine functions of the same typeaddassign a value tofp
	cout << fp(1, 3) << endl;
	cout << (*fp)(1, 3) << endl; // equivalencefp(1,3)
	fp = &substract; // Combine functions of the same typesubstractassign a value tofp, take an address symbol"&"may be added or not
	cout << fp(1, 3) << endl;
	cout << (*fp)(1, 3) << endl; // equivalencefp(1,3)
	return 0;
}

Output:

4
4
-2
-2

Overloaded functions automatically match types

If there is an overloaded function with the same name, the compiler automatically selects the one with the matching type:

#include <iostream>
using namespace std;

int add(int x, int y){
	return x + y;
}

int add(int x, int y, int z){ // heavy load (on a truck)
	return x + y + z;
}

int main(){
	int (*fp)(int, int) = add; // The compiler automatically selects theadd(int x, int y)together withfpmatch
	cout << "1 + 2 = " << fp(1,2) << endl;

	int (*fp2)(int, int, int) = add; // The compiler automatically selects theadd(int x, int y, int z)together withfp2match
	cout << "1 + 2 + 3 = " << fp2(1,2,3) << endl;
	
	return 0;
}

Output:

1 + 2 = 3
1 + 2 + 3 = 6

Type-complex function pointers and the wonders of typedefs

When the input and return lists of a function are complex, especially when there are function pointers nested in the arguments/return values, the readability of the code will be greatly reduced.

1、Input parameters contain function pointers

There are two ways to write a function pointer as an input parameter (formal parameter): by setting the input parameterint to int (*pf)(int, int), indicates that the input parameter is a function pointer pf (pointing to a function of type int (*pf)(int, int)).

  • void test(int fun(int, int)) // Write the declaration of the fun function directly in the place of the argument, inside test fun is used as a formal parameter variable to represent the function pointer
  • void test(int (*pf)(int, int)) // Write the declaration of the function pointer in the place of the parameter, inside test pf is used as a formal parameter variable, which is equivalent to the way it is written above.

Example 2: add2() and add3() in the following example are two equivalent ways of writing function declarations/function pointers as input parameters.

#include <iostream>
using namespace std;

int add(int x, int y){ // two-parameter function add2: add two numbers together
return x + y; }
}

int add2(int (*fun)(int, int), int x, int y, int z){ // Construct the three-argument function add2() using the function declaration statement as a formal parameter
return fun(fun(x,y),z);
}

int add3(int fun(int, int), int x, int y, int z){ // Equivalent to the above, construct the three-argument function add3() using the function pointer as a formal parameter.
return fun(fun(x,y),z); }
}

int main(){
int (*fp)(int, int); int (*fp)(int, int); int
fp = add;
cout << "1 + 2 = " << fp(1,2) << endl;

int (*fp2)(int (*fun)(int, int), int, int, int); // The first argument is a two-argument function pointer, the second through fourth arguments are int
fp2 = add2; // fp2 is assigned the name of a function of the same type
cout << "1 + 2 + 3 = " << fp2(fp,1,2,3) << endl;

fp2 = add3; // Equivalent to above, add3 and add2 are effectively the same, both can be received by fp2
cout << "4 + 5 + 6 = " << fp2(fp,4,5,6) << endl;

fp2(fp,4,5,6) < endl; return 0;
}

Output results:

1 + 2 = 3
1 + 2 + 3 = 6
4 + 5 + 6 = 15

2、Return value is function pointer

The way to write a function pointer as a return value: simply replace theint fun(int) becomes int (*fun(int))(double, double), means fun(int) returns a function pointer pf (pointing to type int (*pf)(double, double)).

  • int (*createAdd())(int, int); // Define a function named createAdd() with null input parameters and a return value of a function pointer pTemp (of type int (pTemp)(int, int)).
  • int (*createAlgorithm(int type))(int, int); // Define a function called createAlgorithm(int): the input parameter is int, and the return value is a pointer to the function corresponding to the operator, pTemp (whose type is int (*pTemp)(int, int))

Example 3: The createAdd() function with no parameter input returns a function pointer:

#include <iostream>
using namespace std;

int add(int x, int y){
return x + y; }
}

// Define a function called createAdd().
// The input parameter is null.
// The return value is a function pointer pTemp (whose type is int (*pTemp)(int, int))
int (*createAdd())(int, int){
return add;
}

int main(){
int (*fp)(int, int); // Declare a function pointer to fp
fp = createAdd(); // fp receives the return value of createAdd(), both are function pointers of the same type and match
cout << "1 + 2 = " << fp(1,2) << endl;
return 0;
}

Output:

1 + 2 = 3

Example 4: The createAlgorithm(int type) function with parameter input returns a function pointer:

#include <iostream>
using namespace std;

// Define three operations: null, addition, and subtraction.
int none(int , int ){
return 0; }
}
int add(int x, int y){
return x + y; }
}
int substract(int x, int y){
return x - y; } int substract(int x, int y){ return x - y; }
}

// Define a function called createAlgorithm(int):
// The input parameter is int, indicating which operator to choose (1 for addition, 2 for subtraction)
// The return value is a pointer to the function corresponding to the operator pTemp (whose type is int (*pTemp)(int, int))
int (*createAlgorithm(int type))(int, int){
switch(type){
case 1.
return add.
break.
case 2: return substract; break; case 3: return add; break; case 4: return add; break
return substract; break; case 2.
break.
default: return none; return substract; break; case 2: return substract; break
return none; }
}
}

int main(){
int (*fp)(int, int); // Declare a function pointer fp
fp = createAlgorithm(1); // fp receives the return value of createAlgorithm(1), both are function pointers of the same type that match
cout << "1 + 2 = " << fp(1,2) << endl;
fp = createAlgorithm(2); // fp receives the return value of createAlgorithm(2), both are function pointers of the same type that match
cout << "5 - 1 = " << fp(5,1) << endl;
return 0;
}

Output:

1 + 2 = 3
5 - 1 = 4

Example 5: Both the input and return parameters contain function pointers: (int (calculateAndTransfer(int (fun)(int, int), int x, int y))(int, int){...}))

#include <iostream>
using namespace std;

int add(int x, int y){
return x + y; }
}
int substract(int x, int y){
return x - y; } int substract(int x, int y){ return x - y; }
}

// Define a function called calculateAndTransfer(int): perform the calculation once internally, and return the input function pointer to pass it on again
// The input parameters are the function pointer (of type int (*pTemp)(int, int)) and two int variables.
// The return value is the same function pointer pTemp (whose type is int (*pTemp)(int, int))
int (*calculateAndTransfer(int (*fun)(int, int), int x, int y))(int, int){
cout << "fun(x,y) = " << fun(x,y) << endl;
endl; return fun.
}

int main(){
int (*fp)(int, int); int
fp = calculateAndTransfer(add,1,2);
cout << "1 + 2 = " << fp(1,2) << endl;
fp = calculateAndTransfer(substract,5,1);
cout << "5 - 1 = " << fp(5,1) << endl;
fp(5,1) << endl; return 0;
}

Output:

fun(x,y) = 3
1 + 2 = 3
fun(x,y) = 4
5 - 1 = 4

As you can see, when the function pointer appears in the input parameter or return value position, the code readability will be greatly reduced, which is not conducive to the development and maintenance of the project.

3, typedef to improve code readability

3.1 typedef custom type aliases

The above example shows that when the input parameter or return value of a function contains a function pointer, the readability of the code will be greatly reduced. To solve this problem, C++ can use typedef to alias a type in advance, and then use the customized alias to write more understandable code.

Two ways of using typedef for functions/function pointers:

  • typedef int Fun(int, int); // Type alias: Fun represents a class of functions.
  • **typedef int (PFun)(int, int); ** // Type alias: PFun represents a class of function pointers (int (pf)(int, int))

Fun in the first way is a custom function alias, and PFun in the second way is a custom function pointer alias. The two ways are equivalent when entered as a formal parameter (the compiler automatically converts Fun to a function pointer), but the second way is still recommended to make it easier to understand without the implicit conversion.

3.2 Rewriting with typedef: function pointers as input parameters

Example 6: Rewriting with typedef: function pointer as input parameter:

#include <iostream>
using namespace std;

typedef int Fun(int, int); // Type alias: Fun represents a class of functions.
typedef int (*PFun)(int, int); // Alias: PFun represents a class of function pointers.

int add(int x, int y){ // two-parameter function add2: add two numbers.
return x + y; }
}

int add2(int (*fun)(int, int), int x, int y, int z){ // Construct the three-argument function add2(), using fun() with two-argument inputs.
return fun(fun(x,y),z); // Use the two-argument input fun() to construct the three-argument function add2().
}

int add4(PFun fun, int x, int y, int z){ // Equivalent to add2, use typedef to improve readability, construct three-argument function add3()
return fun(fun(x,y),z); }
}

int add5(Fun fun, int x, int y, int z){ // Equivalent to add4, where the function name is automatically converted to a function pointer as a formal parameter.
return fun(fun(x,y),z); } int add5(fun, int x, int y, int z)
}

int main(){
// The function pointer is used directly:
int (*fp)(int, int);
fp = add;
cout << "1 + 2 = " << fp(1,2) << endl;

// Same as above, but with typedef to improve readability
PFun fp2 = add; // same type as fp
cout << "1 + 2 = " << fp2(1,2) << endl;

// The code for add4 and add5 using typedef is more readable than add2 when the function pointer is used as an input parameter (formal parameter):
cout << "1 + 2 + 3 = " << add2(fp2, 1, 2, 3) << endl;
cout << "1 + 2 + 3 = " << add4(fp2, 1, 2, 3) << endl;
cout << "1 + 2 + 3 = " << add5(fp2, 1, 2, 3) << endl;

endl; return 0;
}

3.3 Rewriting with typedef: function pointer as return value:

The following two are equivalent writeups: both define a function named createAlgorithm(int), with an input parameter of int and a return value of the function pointer pTemp (whose type is int (*pTemp)(int, int))

  • int (createAlgorithm(int type))(int, int){ ...} // Return type writing is hard to read without using typedefs
  • typedef int (*PFun)(int, int);
    PFun createAlgorithm(int type){ ... } // As you can see, writing PFun with types defined by typedefs is easier to understand than writing return types in general.

Example 7: Rewriting with typedef: function pointer as return value:

#include <iostream>
using namespace std;

// PFun is a type alias for a function pointer to a function of type int (*pTemp)(int, int)
typedef int (*PFun)(int, int);

// Define three operations: null, addition, and subtraction.
int none(int , int ){
return 0; }
}
int add(int x, int y){
return x + y; }
}
int substract(int x, int y){
return x - y; } int substract(int x, int y){ return x - y; }
}

// Define a function called createAlgorithm(int):
// The input parameter is int, indicating which operator to choose (1 for addition, 2 for subtraction)
// The return value is a pointer to the function corresponding to the operator pTemp (which is of type int (*pTemp)(int, int))
// Equivalent: int (*createAlgorithm(int type))(int, int){ xxx }
PFun createAlgorithm(int type){
switch(type){
case 1.
return add.
break.
case 2.
return substract; break; case 2.
break.
default: return none; return substract; break; case 2: return substract; break
return none; }
}
}

int main(){
PFun fp = createAlgorithm(1); // fp receives the return value of createAlgorithm(1), both are function pointers of the same type that match
cout << "1 + 2 = " << fp(1,2) << endl;
fp = createAlgorithm(2); // fp receives the return value of createAlgorithm(2), both are function pointers of the same type that match
cout << "5 - 1 = " << fp(5,1) << endl;
return 0;
}

Output:

1 + 2 = 3
5 - 1 = 4

Note: type aliases in addition to typedef, there are using and decltype, can be realized function pointer alias definition. For example, the following three statements define the equivalence of PFun1, PFun2, PFun3.

Example 8: Three equivalent ways to write typedef, using and decltype:

#include <iostream>
using namespace std;

int add(int x, int y){
return x + y; }
}

// Here are three equivalent ways to write a type alias:
typedef int (*PFun1)(int, int); // Type alias: PFun1 represents a class of function pointers.
using PFun2 = int (*)(int, int); // Type alias: PFun2 is equivalent to PFun1.
typedef decltype(add) *PFun3; // Alias: PFun3 is equivalent to PFun1.

int main(){
PFun1 fp1 = add.
PFun2 fp2 = add.
PFun3 fp3 = fp2.
cout << "1 + 2 = " << fp1(1,2) << endl;
cout << "1 + 2 = " << fp2(1,2) << endl;
cout << "1 + 2 = " << fp3(1,2) << endl;
fp3(1,2) << endl; return 0;
}

Output:

1 + 2 = 3
1 + 2 = 3
1 + 2 = 3

3.4 Rewriting with typedef: both input and return parameters contain function pointers: the following two ways of writing are equivalent:

  • int (*calculateAndTransfer(int (*fun)(int, int), int x, int y))(int, int)
  • typedef int (*PFun)(int, int);
    PFun calculateAndTransfer(PFun fun, int x, int y); // typedef greatly improves readability

Example 9: Rewrite Example 5 with typedef to give the code better readability:

#include <iostream>
using namespace std;

typedef int (*PFun)(int, int);

int add(int x, int y){
return x + y; }
}
int substract(int x, int y){
return x - y; } int substract(int x, int y){ return x - y; }
}

// Define a function called calculateAndTransfer(int): perform the calculation once internally, and return the input function pointer to pass it on again
// The input parameters are the function pointer (of type int (*pTemp)(int, int)) and two int variables.
// The return value is the same function pointer pTemp (whose type is int (*pTemp)(int, int))
// The following is equivalent: int (*calculateAndTransfer(int (*fun)(int, int), int x, int y))(int, int){
PFun calculateAndTransfer(PFun fun, int x, int y){
cout << "fun(x,y) = " << fun(x,y) << endl;
endl; return add.
}

int main(){
PFun fp.
fp = calculateAndTransfer(add,1,2);
cout << "1 + 2 = " << fp(1,2) << endl;
fp = calculateAndTransfer(substract,5,1);
cout << "5 - 1 = " << fp(5,1) << endl;
fp(5,1) << endl; return 0;
}

Output:

fun(x,y) = 3
1 + 2 = 3
fun(x,y) = 4
5 - 1 = 6

Array of function pointers

Define an array of function pointers: ([] has higher priority than *, so it's straightforward to change (*fp) to (*fp[3]))

  • int (*fp[3])(int, int) : fp is an array with 3 elements, each of which is a function pointer (of type int (*pf)(int, int))
  • int (*(fp[3])(int, int) : Equivalent to the above, fp is an array of
  • int ((*fp)[3])(int, int) : (Illegal, compilation will report an error: ```error: declaration of 'gp' as array of functions): fp is a pointer to an array which must have 3 elements, each element is a function, note that it is a function and not a function pointer, the * symbol is given to fp, so the compiler will think that You want to create an array of functions, which is not allowed, only arrays of function pointers are allowed.

Example 10: Array of function pointers:

#include <iostream>
using namespace std;

typedef int (*PFun)(int, int);

int add(int x, int y){
return x + y; }
}

int substract(int x, int y){
return x - y; } int substract(int x, int y){ return x - y; }
}

int main(){
// fp is an array with 2 elements, each element is a function pointer (of type int (*pf)(int, int))
int (*fp[2])(int, int);
fp[0] = add.
fp[1] = substract;
cout << fp[0](5,3) << endl;
cout << fp[1](5,3) << endl;

// The following will report an error, literally: gp is a pointer to an array of functions, and arrays of functions are illegal, arrays of function pointers are allowed
// int ((*gp)[2])(int, int); \\\ error: declaration of 'gp' as array of functions

return 0; }
}

Output:

8
2

Pointer to a member function (method) of a class

Similar to ordinary function pointers, static/non-static member function (method) pointers of a class have three steps: declaration, assignment, and invocation:

ordinary function (math.) Non-static member functions of a class
(class name A, instance object a, object pointer pa)
Static member function of class (class name A)
function declaration int fun(double x, double y); int A::fun(double x, double y); int A::static_fun(double, double);
Declaration of function pointers int (*fp)(double, double); int (A::*fp2)(double x, double y); int (*fp3)(double, double);
Assignment of function pointers fp = fun; or fp = &fun;. fp2 = A::fun; or fp2 = &A::fun; fp3 = A::static_fun;
Calls to function pointers fp(x, y); or (*fp)(x, y). (a.*fp2)(x,y);
(pa->*fp2)(x,y);
fp3(x, y); or (*fp3)(x, y).

A few notes:

  • Three new operators are introduced: declaration::*, call.*cap (a poem)->*
  • When declaring, only pointers to non-static member functions of a class are preceded by the class name.
  • When assigning, none of the function pointers are preceded by the class name, and only the non-static member function pointers of the class to the right of the equals sign are preceded by the class name.
  • Only the pointer to a non-member function of a class should be preceded by the object name when called, because the this pointer must be used to determine which instance object's function is being called. The parentheses in (a.*fp2)(x,y) and (pa->*fp2)(x,y) can't be omitted because the.*cap (a poem)->*have a lower priority than()

In short, the class of static member functions and ordinary functions are very similar, only in the assignment of the right side of the equals sign is required to class name:: static function form, the rest of the syntax is exactly the same. The non-static member function of the class in front of the declaration to add the class name, the assignment of the right side of the equal sign to add the name of the class, call the front to add a specific object name.

Example 11: Compare the declaration, assignment, and invocation of ordinary functions, non-static member functions of a class, and static member functions of a class:

#include <iostream>;
#include <string>
using namespace std.

class Mobile{
public.
Mobile(string number):_number(number){}
void from(string another_number){
cout << _number << " is receiving a call from " << another_number << endl;
}
void to(string another_number){
cout << _number << " is calling to " << another_number << endl;
}
static void printInfo(){
cout << "Static function: Mobile works good!" << endl;
}
private.
string _number; }
}; }

void fun(string number){
cout << "Normal function: number " << number << endl;
}

int main(){
// Pointer to normal function: void (*fp1)
void (*fp1)(string); // declaration
fp1 = fun; // assignment
fp1("999"); // call
(*fp1)("999"); // second equivalent of call

// Pointer to a non-static member function (method) of a class:
Mobile m("666888"), *mp = &m;
void (Mobile::*fp2)(string); // declaration, using ::* (parentheses can't be missing: writing Mobile::*fp2(string) will report an error)
fp2 = Mobile::from; // Assignment, which can also be written as fp2 = &Mobile::from; because ::precedence over &.
(m.*fp2)("12345"); // call, can't be written as m.*fp2("12345") because . * has a lower priority than ()
// (*(m.*fp2))("12345"); // Error, call doesn't have a second equivalent like a normal function (prefixed with *)
(mp->*fp2)("12345"); // call, can't be written as m->*fp2("12345") because ->* has lower precedence than ()
fp2 = Mobile::to; // assignment
(m.*fp2)("54321"); // call
(mp->*fp2)("54321"); // call

// Pointer to a static member function (method) of the class:
void (*fp3)(); // declaration: same as normal function
fp3 = Mobile::printInfo; // Assignment: use class name::static function on the right side of the equals sign, or write fp3 = &Mobile::printInfo;.
fp3(); // call: same as normal function
(*fp3)(); // second equivalent of call: same as normal function

return 0; }
}

Output:

Normal function: number 999
Normal function: number 999
666888 is receiving a call from 12345
666888 is receiving a call from 12345
666888 is calling to 54321
666888 is calling to 54321
Static function: Mobile works good!
Static function: Mobile works good!

bibliography

  • C++ Primer (5th Edition) Ch 6.7 Function Pointers

  • C++ Primer Plus (6th Edition) Ch 7.10 Function Pointers

  • A 10,000 word article to systematically sort out C++ function pointers - Knowledge ()

  • C++ function pointer & class member function pointer | ()