Author:PhyJack
https://www.cnblogs.com/phyjack/p/18445368
Overview
This article provides a detailed introduction to pointers of ordinary functions and member functions of classes in C/C++. It explains practical techniques for using function pointers as inputs to other functions, return values, and how typedef can enhance code readability, illustrated with C++ code examples. For member function pointers of classes, the discussion is divided into static and non-static cases. Finally, it summarizes the C++ syntax for declaring, assigning, and defining ordinary functions, non-static member functions, and static member functions for reference.
Pointers to Ordinary Functions
Declaration, Definition, and Assignment
First, let us distinguish the following four declaration statements:
int x; // x is an int variable
int *x; // x is a pointer to an int variable
int *x(); // x is a function returning a pointer to int
int (*x)(); // x is a function pointer, with no input parameters, returning a pointer to int
Thus, to convert the declaration statement of a function named fun into the declaration statement of a function pointer variable pfun, simply change **fun to (*pfun)**, keeping the rest 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 its elements (storing the address of the first element); similarly, the name of a function also represents a pointer constant to that function (storing the entry address of the function):
int array[5]; // array is a pointer constant to int
int *p = array; // p is a pointer variable to int, p = array is valid
p[3]; // equivalent to array[3]
void test(int a){ cout << "a=" << a << endl;} // test is a pointer constant to a function, input parameter int, return type void
void (*pf)(int); // Declare function pointer pf: pf is a pointer to a function with input parameter int and return type void
pf = test; // Assign the address of test to pf (the input parameter list and return type must match)
pf(123); // equivalent to test(123)
Several points need clarification:
// Declaration and assignment can be combined:
void (*pf)(int) = test;
// Two assignment methods: (equivalent)
pf = test; // Direct function name assignment
pf = &test; // Function name address assignment, equivalent to pf = test
// Two calling methods: (equivalent)
pf(123); // Direct call with parameters
(*pf)(123); // Dereference the function pointer first, then call with parameters, 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); // Define function pointer variable: fp
fp = add; // Assign same type function add to fp
cout << fp(1, 3) << endl;
cout << (*fp)(1, 3) << endl; // equivalent to fp(1,3)
fp = &substract; // Assign same type function substract to fp, the address operator "&" can be added or omitted
cout << fp(1, 3) << endl;
cout << (*fp)(1, 3) << endl; // equivalent to fp(1,3)
return 0;
}
Output:
4
4
-2
-2
Automatic Type Matching for Overloaded Functions
If there are overloaded functions with the same name, the compiler will automatically select one that matches the type:
#include <iostream>
using namespace std;
int add(int x, int y){
return x + y;
}
int add(int x, int y, int z){ // Overloaded
return x + y + z;
}
int main(){
int (*fp)(int, int) = add; // The compiler will automatically select add(int x, int y) to match fp
cout << "1 + 2 = " << fp(1,2) << endl;
int (*fp2)(int, int, int) = add; // The compiler will automatically select add(int x, int y, int z) to match fp2
cout << "1 + 2 + 3 = " << fp2(1,2,3) << endl;
return 0;
}
Output:
1 + 2 = 3
1 + 2 + 3 = 6
Complex Function Pointers and the Use of Typedef
When a function’s input list and return list are complex, especially when parameters/return values are nested with function pointers, the readability of the code will significantly decrease.
1. Input Parameters Containing Function Pointers
Function pointers as input parameters (formal parameters) can be written in two ways: changing the input parameter **int to int (*pf)(int, int)**, indicating that the input parameter is a function pointer pf (pointing to a function type int (*pf)(int, int)).
- void test(int fun(int, int)) // Directly write the declaration of fun in the parameter position, in test fun acts as a formal parameter variable, indicating a function pointer
- void test(int (*pf)(int, int)) // Write the function pointer declaration in the parameter position, in test pf acts as a formal parameter variable, equivalent to the above writing
Example 2: The add2() and add3() in the following example are two equivalent ways of writing input parameters as function declarations/function pointers.
#include <iostream>
using namespace std;
int add(int x, int y){ // Two-parameter function add2: adds two numbers
return x + y;
}
int add2(int (*fun)(int, int), int x, int y, int z){ // Using function declaration as a formal parameter, construct a three-parameter function add2()
return fun(fun(x,y),z);
}
int add3(int fun(int, int), int x, int y, int z){ // Equivalent to the above, using function pointer as a formal parameter, construct a three-parameter function add3()
return fun(fun(x,y),z);
}
int main(){
int (*fp)(int, int);
fp = add;
cout << "1 + 2 = " << fp(1,2) << endl;
int (*fp2)(int (*fun)(int, int), int, int, int); // The first parameter is a double-parameter function pointer, the second to fourth parameters are int
fp2 = add2; // fp2 is assigned the function name of the same type
cout << "1 + 2 + 3 = " << fp2(fp,1,2,3) << endl;
fp2 = add3; // Equivalent to the above, add3 and add2 are actually the same, both can be received by fp2
cout << "4 + 5 + 6 = " << fp2(fp,4,5,6) << endl;
return 0;
}
Output:
1 + 2 = 3
1 + 2 + 3 = 6
4 + 5 + 6 = 15
2. Return Value as Function Pointer
The writing of function pointers as return values: simply changeint fun(int) to int (*fun(int))(double, double), indicating that fun(int) returns a function pointer pf (pointing to type int (*pf)(double, double)).
- int (*createAdd())(int, int); // Define a function named createAdd() with no input parameters, returning a function pointer pTemp (its type is int (pTemp)(int, int))
- int (*createAlgorithm(int type))(int, int); // Define a function named createAlgorithm(int) with input parameter int, returning a function pointer pTemp corresponding to the operator (its type is int (*pTemp)(int, int))
Example 3: The createAdd() function with no input parameters returns a function pointer:
#include <iostream>
using namespace std;
int add(int x, int y){
return x + y;
}
// Define a function named createAdd()
// with no input parameters
// returning a function pointer pTemp (its type is int (*pTemp)(int, int))
int (*createAdd())(int, int){
return add;
}
int main(){
int (*fp)(int, int); // Declare a function pointer fp
fp = createAdd(); // fp receives the return value of createAdd(), both are function pointers of the same type, matching
cout << "1 + 2 = " << fp(1,2) << endl;
return 0;
}
Output:
1 + 2 = 3
Example 4: The createAlgorithm(int type) function with input parameters returns a function pointer:
#include <iostream>
using namespace std;
// Define three operations: no operation, addition, 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;
}
// Define a function named createAlgorithm(int):
// input parameter is int, indicating which operator to choose (1 for addition, 2 for subtraction)
// return value is the function pointer corresponding to the operator pTemp (its type is int (*pTemp)(int, int))
int (*createAlgorithm(int type))(int, int){
switch(type){
case 1:
return add;
break;
case 2:
return substract;
break;
default:
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, matching
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, matching
cout << "5 - 1 = " << fp(5,1) << endl;
return 0;
}
Output:
1 + 2 = 3
5 - 1 = 4
Example 5: Input parameters and return parameters both 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;
}
// Define a function named calculateAndTransfer(int): executes a calculation internally and returns the input function pointer to pass it on
// Input parameters are a function pointer (its type is int (*pTemp)(int, int)) and two int variables
// Return value is the same function pointer pTemp (its 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;
return fun;
}
int main(){
int (*fp)(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;
return 0;
}
Output:
fun(x,y) = 3
1 + 2 = 3
fun(x,y) = 4
5 - 1 = 4
As can be seen, when function pointers appear in input parameters or return values, the readability of the code will significantly decrease, which is not conducive to project development and maintenance.
3. Using Typedef to Improve Code Readability
3.1 Typedef Custom Type Aliases
From the above examples, it is found that when function input parameters or return values contain function pointers, the readability of the code will significantly decrease. To solve this problem, C++ allows the use of typedef to predefine an alias for a certain type, making the code easier to understand.
Function/function pointer usage with typedef can be done in two ways:
- 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))
The first way defines Fun as a custom function alias, while the second way defines PFun as a custom function pointer alias. When used as formal parameter inputs, both methods are equivalent (the compiler will automatically convert Fun to a function pointer), but the second method is still recommended for clarity without implicit conversion.
3.2 Rewriting with Typedef: Function Pointer as Input Parameter
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); // Type alias: PFun represents a class of function pointers
int add(int x, int y){ // Two-parameter function add2: adds two numbers
return x + y;
}
int add2(int (*fun)(int, int), int x, int y, int z){ // Using double-parameter input fun(), construct a three-parameter function add2()
return fun(fun(x,y),z);
}
int add4(PFun fun, int x, int y, int z){ // Equivalent to add2, using typedef to improve readability, construct a three-parameter function add3()
return fun(fun(x,y),z);
}
int add5(Fun fun, int x, int y, int z){ // Equivalent to add4, function names will be automatically converted to function pointers as formal parameters
return fun(fun(x,y),z);
}
int main(){
// Function pointer used directly:
int (*fp)(int, int);
fp = add;
cout << "1 + 2 = " << fp(1,2) << endl;
// Same as above, but using typedef to improve readability
PFun fp2 = add; // Same type as fp
cout << "1 + 2 = " << fp2(1,2) << endl;
// When function pointers are used as input parameters (formal parameters), the code readability of add4 and add5 is higher than that of add2:
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;
return 0;
}
3.3 Rewriting with Typedef: Function Pointer as Return Value
The following two are equivalent ways of writing: both define a function named createAlgorithm(int) with input parameter int, returning a function pointer pTemp (its type is int (*pTemp)(int, int))
- int (createAlgorithm(int type))(int, int){ …} // Without using typedef, the return type writing is hard to read
- typedef int (*PFun)(int, int);PFun createAlgorithm(int type){ … } // Using typedef defined type PFun makes the writing of return type easier to understand
Example 7: Rewriting with typedef: function pointer as return value:
#include <iostream>
using namespace std;
// PFun is a type alias for a function pointer, pointing to a function of type int (*pTemp)(int, int)
typedef int (*PFun)(int, int);
// Define three operations: no operation, addition, 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;
}
// Define a function named createAlgorithm(int):
// input parameter is int, indicating which operator to choose (1 for addition, 2 for subtraction)
// return value is the function pointer corresponding to the operator pTemp (its type is int (*pTemp)(int, int))
// Equivalent to: int (*createAlgorithm(int type))(int, int){ xxx }
PFun createAlgorithm(int type){
switch(type){
case 1:
return add;
break;
case 2:
return substract;
break;
default:
return none;
}
}
int main(){
PFun fp = createAlgorithm(1); // fp receives the return value of createAlgorithm(1), both are function pointers of the same type, matching
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, matching
cout << "5 - 1 = " << fp(5,1) << endl;
return 0;
}
Output:
1 + 2 = 3
5 - 1 = 4
Note: Type aliases can also be defined using using and decltype, which can achieve function pointer alias definitions. For example, the following three statements define PFun1, PFun2, and PFun3 as equivalent.
Example 8: Equivalent Writing with typedef, using, and decltype:
#include <iostream>
using namespace std;
int add(int x, int y){
return x + y;
}
// The following are three equivalent ways of defining type aliases:
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; // Type 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;
return 0;
}
Output:
1 + 2 = 3
1 + 2 = 3
1 + 2 = 3
3.4 Rewriting with Typedef: Input Parameters and Return Parameters Both Contain Function Pointers: The following two writings 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: Rewriting Example 5 with typedef for better code 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;
}
// Define a function named calculateAndTransfer(int): executes a calculation internally and returns the input function pointer to pass it on
// Input parameters are a function pointer (its type is int (*pTemp)(int, int)) and two int variables
// Return value is the same function pointer pTemp (its type is int (*pTemp)(int, int))
// The following is equivalent to: 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;
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;
return 0;
}
Output:
fun(x,y) = 3
1 + 2 = 3
fun(x,y) = 4
5 - 1 = 6
Arrays of Function Pointers
Define an array consisting of function pointers: (the priority of [] is higher than *, so directly change (*fp) to (*fp[3])
<span>int (*fp[3])(int, int)</span>: fp is an array with 3 elements, each element is a function pointer (pointing to int (*pf)(int, int) type)<span>int (*(fp[3])(int, int)</span>: equivalent to the above, fp is an array<span>int ((*fp)[3])(int, int)</span>: (illegal, compilation will report an error: “`error: declaration of ‘gp’ as array of functions): fp is a pointer to an array, the number of elements in this array must be 3, each element is a function, note that it is a function and not a function pointer, the * symbol is given to the head of fp, so the compiler will think you want to create an array of functions, which is not allowed, only an array of function pointers is 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 main(){
// fp is an array with 2 elements, each element is a function pointer (pointing to int (*pf)(int, int) type)
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 understood: gp is a pointer to an array of functions, while an array of functions is illegal, an array of function pointers is allowed
// int ((*gp)[2])(int, int); \ error: declaration of 'gp' as array of functions
return 0;
}
Output:
8
2
Pointers to Class Member Functions (Methods)
Similar to ordinary function pointers, pointers to static/non-static member functions (methods) of classes also have three steps: declaration, assignment, and calling:
| Ordinary Function | Non-Static Member Function of 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); |
| Function Pointer Declaration | int (*fp)(double, double); | int (A::*fp2)(double x, double y); | int (*fp3)(double, double); |
| Function Pointer Assignment | fp = fun; or fp = &fun; | fp2 = A::fun; or fp2 = &A::fun; | fp3 = A::static_fun; |
| Function Pointer Call | fp(x, y); or (*fp)(x, y); | (a.*fp2)(x,y); (pa->*fp2)(x,y); | fp3(x, y); or (*fp3)(x, y); |
Several points to note:
- Three new operators are introduced: declaration
<span>::*</span>, calling<span>.*</span>and<span>->*</span>. - In declaration, only the pointer to the non-static member function of the class is prefixed with the class name.
- In assignment, the function pointer is not prefixed with the class name, only the right side of the assignment for the non-static member function pointer needs to include the class name.
- In calling, only the pointer to the non-member function needs to be prefixed with the object name, as it must determine which instance object’s function to call through the this pointer. The parentheses around (a.*fp2)(x,y) and (pa->*fp2)(x,y) cannot be omitted, as
<span>.*</span>and<span>->*</span>have lower precedence than<span>()</span>.
In summary, static member functions of classes are very similar to ordinary functions, with the only difference being that during assignment, the right side of the equal sign must be in the form of class name::static function, while the other syntax is completely consistent. Non-static member functions of classes require the class name in declaration, the class name on the right side of assignment, and the specific object name in calling.
Example 11: Comparing the declaration, assignment, and calling of ordinary functions, non-static member functions of classes, and static member functions of classes:
#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 ordinary function:
void (*fp1)(string); // Declaration
fp1 = fun; // Assignment
fp1("999"); // Call
(*fp1)("999"); // Second equivalent way of calling
// Pointer to non-static member function (method) of class:
Mobile m("666888"), *mp = &m;
void (Mobile::*fp2)(string); // Declaration, using ::* (parentheses cannot be omitted: writing Mobile::*fp2(string) will cause an error)
fp2 = Mobile::from; // Assignment, can also be written as fp2 = &Mobile::from; because :: has higher precedence than &
(m.*fp2)("12345"); // Call, cannot be written as m.*fp2("12345") because .* has lower precedence than ()
// (*(m.*fp2))("12345"); // Error, calling does not have a second equivalent way like ordinary functions (adding *)
(mp->*fp2)("12345"); // Call, cannot 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 static member function (method) of class:
void (*fp3)(); // Declaration: same as ordinary function
fp3 = Mobile::printInfo; // Assignment: right side uses class name::static function, can also be written as fp3 = &Mobile::printInfo;
fp3(); // Call: same as ordinary function
(*fp3)(); // Second equivalent way of calling: same as ordinary 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!
Recommended Reading Click the title to jump
1. C++: Practical Programming with RAII
2. Internal Working Principles of Cursor
3. Explosive! 70-Year-Old Gates and 56-Year-Old Linus in the Same Frame for the First Time, a Photo Ignites the Tech Circle