2 指针Pointer
2.1 内存、地址和指针
1. 内存Memory
- 计算机内存一般分为栈区、堆区和静态区等;为了有效的使用内存,就把内存划分成一个个小的内存单元;
- 可以将内存看成"一排排的房子,每间房子可以容纳一定的数据,并且通过房牌号来区分。"
- 内存通常以1字节(byte)为最小单位;
- 1个字节由8位(bit)组成,位是数据最小存储单元;
2. 地址Addresses
- “每个房子都有唯一的房牌号,这个号码就是地址”;
- 内存中的每个字节都有一个唯一的地址,这个地址用于定位和访问内存中的数据。
- 一个地址通常是一个整数值,它表示内存中的位置。例如,一个32位系统中的地址通常是一个32位整数,可以表示2^32个不同的字节位置。用来引用内存中的不同字节,从而读取或写入数据。
3. 指针Pointer
- “指针是每个房子房牌号的别名”;
- 指针变量代表内存中的一个地址;指针就是地址,可以用来访问或操作存储在该地址上的数据。
4. 三者关系
内存、地址和指针的三者关系见下图:
对上图代码解释如下:
int a = 112, b = -1;
float c = 3.14;
int *d = &a;
float *e = &c;
- 指针变量的内容是地址(address);整型指针变量
d
内存存储的是整型变量a
的地址;同理,浮点型指针变量e
存储也是地址,是浮点型变量c
的地址;- 指针变量的值和指针变量指向变量的值不一样;指针存的是所指向变量的地址而不是值。
- 变量(variable)的值(value)是分配给这个变量内存位置所存储的数值;
a
和b
存储整数值,而浮点型c
在内存中也是整数,但是可以被解释为浮点数,关键在于使用这个值的方式;- 编译器实现了"变量名称"和"内存地址"的连接;本质上在硬件层,还是需要通过地址访问内存;
2.2 使用指针
1. 指针变量的声明和初始化
- 指针初始化时,"="的右操作数必须为内存中数据的地址,不可以是变量,也不可以直接用整型地址值;
- (但是
int *p = 0;
除外,该语句表示指针为空)。此时,*p
只是表示定义的是个指针变量,并没有间接取值的意思。
- 正确示例
/*正确的实例1*/
int a = 25;
int* ptr = &a;
/*正确的实例2*/
int b[10];
int* point = b; // OK 数组名就代表数组的地址
int* p = &b[0];
/*正确的实例3*/
int k;
int* p;
p = &k; //给p赋值
*p = 7; //给p所指向的内存赋值,即k= 7
- 错误示例
int* p;
*p = 7;
- 指针p没有初始化(赋值),其指向的内存位置是随机的,7是一个右值;
- 没有初始化的指针(野指针)。
- 不规范示例
int *a, b, c;//实际上只声明一个指针a
//修改
int *a, *b, *c;
2. 指针变量的大小
- 指针变量的大小和系统有关;在32位系统(即x86)中,指针的大小为4字节。在64位系统(即x64)中,指针的大小为8字节。
- 指针有
int*型,char*型,double*型
等不同的类型,但是并不会因为是不同类型的指针,指针所占内存大小发生改变; - 指针变量的大小和指针变量指向内存的空间大小不是一回事;“门牌号决定不了房间的大小”。
输出变量或指针的大小,可以采用如下方法:
printf("%d",sizeof())
//在x86系统下:
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(char)); // 1 字节 char的大小
// 输出结果
4
1
3. 间接访问(解引用)
- 通过一个指针访问它所指向的地址的过程称为间接访问(indirection)或解引用指针(dereferencing the pointer)。
- 使用操作符是单目操作符
*
;
表达式 | 右值 | 类型 |
---|---|---|
a | 112 | int |
b | -1 | int |
c | 3.14 | float |
d | 100 | int * |
e | 108 | float * |
*d | 112 | int |
*e | 3.14 | float |
【注意】
-
若用箭头表示下面的变量的内存指示关系,则在一定程度上存在误解:
-
修正后的如下图所示,“指示关系”是在解引用操作符
*
作用之后才存在;
2.3 const
修饰指针变量
1. 关键字const
的作用
- 声明常量:可以使用
const
声明一个常量;- 防止无意修改:
const
可以用于防止在后续代码中无意间修改一个应该保持不变的值,提高代码的鲁棒性。- 编译器优化:可以使用
const
信息来进行优化,以提高程序的性能,编译器通常不为普通const
常量分配存储空间。
用const
定义常变量的方法很简单,通常在定义变量时前加上const
即可,下面两种写法等价;
const int a = 16; //常用
int const a = 16;
当const
修饰指针时,有两种情况:
- 指针变量本身变成常量;
- 它指向的东西(变量变成常量);
2. 修饰指针的值:const 在 * 右侧 (*const
)
const
放在*
右边:int *const ptr;
- 修饰指针变量,指针是常量;
- **指针的值(指针所指向的地址)**没办法修改,但是可以修改它指向实体的值;
int a = 10;
int b = 20;
int * const pi = &a;
/*指针变量p(指向)不可修改*/
pi = &b; //报错
/*指针指向的内容可修改*/
*pi = b;
3. 修饰指针指向内容的值:const 在 * 左侧 (const *
)
const
放在*
左边:const int *ptr;
- 修饰指针指向的值,指针指向的值是常量,没办法修改,但是可以修改指针的值;
int a = 10;
int b = 20;
const int * pi = &a;
/*指针变量p(指向)可修改*/
pi = &b;
/*指针指向的内容不可修改*/
*pi = b; //报错
注意:
const int *p; // 指向常量整数的指针 int const *p; // 与上一行等价,也是指向常量整数的指针
4. 二者同时
int const * const p
- 若
*
的左边和右边都被加上了const
,则*p
**(指针指向内容的值)和p(指针变量的值,指针的指向)**均不能被改变。
int a = 10;
int const * const pci = &a;
2.4 指针表达式
即指针变量做"左值"和“右值”。
char ch = “a”;
char *cp = &ch;
- 上述声明了两个变量:
ch
和cp
,cp
作为一个指针指向ch
;- 假设
cp
变量地址为100,ch
变量的地址为108,即现在cp
内存的数据为:108,ch
内存的数据为字符a;
分析如下:
序号 | 表达式 | 右值 | 左值 |
---|---|---|---|
1 | &ch | 字符型变量cp的地址信息,即“108”这个值 | 非法 不可寻址,不可作为空间使用 |
2 | cp | 指针变量ch的值,即“108”地址信息 | 可寻址,可作为空间使用 |
3 | &cp | 指针变量cp的地址信息,即“100”这个值 | 非法 不可寻址,不可作为空间使用 |
4 | *cp | 指针变量cp的值,即字符“a”这个值 | 表示ch 的地址空间 |
5 | *cp + 1 | 注意* 优先级高于+ ,先执行* 得到a字符的一个拷贝,a加1,得到字符b | 非法 不可寻址 |
6 | *(cp + 1) | (cp + 1) 先执行指针加法,得到下一个地址值,*(cp + 1) 解析这个新地址数据内容 | cp 下一个地址空间 |
7 | ++cp | 先执行+1 操作,指向ch 后面的内存空间;再进行执行拷贝操作,也指向ch 后面的内存空间 | 非法 不可寻址 |
8 | cp++ | 先进行执行拷贝操作,指向ch 原来的内存空间;再执行+1 操作,即指向ch 后面的内存空间 | 非法 不可寻址 |
9 | *++cp | 1. 指向解引用操作 2. 先执行+1 操作,指向ch 后面的内存空间;再进行执行拷贝操作,也指向ch 后面的内存空间; 3. 最终得到ch 后面的内存空间的数据; | 得到ch 后面的内存空间; |
10 | *cp++ | 1. 指向解引用操作 2. 先进行执行拷贝操作,指向ch 原来的内存空间;再执行+1 操作,即指向ch 后面的内存空间; 3. 拷贝操作得到ch 内存空间的数据,+1 得到ch 后面的内存空间的数据; | 拷贝操作得到ch 内存空间,+1 得到ch 后面的内存空间; |
11 | ++*cp | ++ 和* 都是自左向右的结合性,* 先执行 1. 指向解引用操作 2. 先执行+1 操作,指向ch 后面的内存空间;再进行执行拷贝操作,也指向ch 后面的内存空间; 3. 拷贝操作得到ch 内存空间的数据,+1 得到ch 后面的内存空间的数据; | 非法 不可寻址 |
12 | *(cp + 7) | (cp + 1) 先执行指针加法,得到下一个地址值,*(cp + 7) 解析这个新地址数据内容 | 下一个地址空间 |
【关键】
- 理解指针变量的本质和解引用操作符的本质;
- 理解不同运算符的优先级和结合性;
参考:《C和指针》
2.4 指针和函数
1. 指针做函数的参数
- 典型应用就是:指针变量做形参,操作地址,可以改变实参;
- 形参定义指针
*
,调用传入地址&
;- 与c++的引用用法有区别;
/*正确版本*/
void Swap2(int* px, int* py)
{
int temp = 0;
temp = *px;
*px = *py;
*py = temp;
}
int main()
{
int num1 = 12;
int num2 = 21;
Swap1(num1, num2);
printf("Swap1: :num1 = %d num2 = %d\n", num1,num2);
Swap2(&num1, &num2);
printf("Swap2: :num1 = %d num2 = %d\n",num1, num2);
return 0;
}
2. 指向函数的指针:函数指针
1. 函数名
函数名是什么?
函数名也是一种特殊的指针,函数名表示着函数的入口地址,标识函数在内存的位置;
具体而言:
函数名代表入口地址: 编译系统使用函数名来代表函数的入口地址,这个地址是函数的指针,可以用来访问函数的代码。
函数名是指针常量: 函数名本质上是一个函数指针常量,它代表了函数的入口地址,不可修改。
函数指针变量: 我们可以定义函数指针变量,这样就用指针变量存储函数的入口地址。这种变量叫做指向函数的指针变量,或函数指针。
直接调用和间接调用:
使用函数名调用函数是直接调用;
使用函数指针变量调用函数是间接调用。
函数指针变量使函数使用更加灵活,可以在运行时决定要调用哪个函数。
函数指针类型匹配: 函数指针变量只能指向与其函数指针类型匹配的函数。
即:函数指针变量和要调用的函数的参数个数、类型、顺序以及返回值类型必须一致。
函数指针和普通变量的区别:
本质都是地址;
普通变量的指针指向内存中的数据区;
函数指针实际上是指向内存中的函数指令区,即存储函数执行代码的内存区域。
示例代码如下:
#include<stdio.h>
int add_func(int x, int y);
int subtract_func(int x, int y);
int multiply_func(int x, int y);
int main(void)
{
int a = 6; int b = 10;
/*定义函数指针:有两个整数参数、返回值整数*/
int(*func_ptr)(int, int);
/*将func_ptr指向函数add_func*/
func_ptr = add_func; //或 func_ptr = &add_func;
printf("add_func address = %p\n", add_func);
printf("func_ptr = %p\n", func_ptr);
printf("%d + %d = %d\n", a, b, add_func(a, b));
printf("%d + %d = %d\n", a, b, func_ptr(a, b));
/*将func_ptr指向函数subtract_func*/
func_ptr = subtract_func; //或 func_ptr = &subtract_func;
printf("subtract_func address = %p\n", subtract_func);
printf("func_ptr = %p\n", func_ptr);
printf("%d - %d = %d\n", a, b, subtract_func(a, b));
printf("%d - %d = %d\n", a, b, func_ptr(a, b));
/*将func_ptr指向函数multiply_func*/
func_ptr = multiply_func; //或 func_ptr = &multiply_func;
printf("multiply_func address = %p\n", multiply_func);
printf("func_ptr = %p\n", func_ptr);
printf("%d × %d = %d\n", a, b, multiply_func(a, b));
printf("%d × %d = %d\n", a, b, func_ptr(a, b));
return 0;
}
//定义三个具有相同的形式参数和返回值的函数
int add_func(int x, int y)
{
return x + y;
}
int subtract_func(int x, int y)
{
return x - y;
}
int multiply_func(int x, int y)
{
return x * y;
}
运行结果:
add_func address = 009D1212
func_ptr = 009D1212
6 + 10 = 16
6 + 10 = 16
subtract_func address = 009D100A
func_ptr = 009D100A
6 - 10 = -4
6 - 10 = -4
multiply_func address = 009D1258
func_ptr = 009D1258
6 × 10 = 60
6 × 10 = 60
可以看到:
printf("add_func address = %p\n", add_func);
和printf("func_ptr = %p\n", func_ptr);
的值一样,都是函数的地址;- 通过创建函数指针来存储函数的入口地址和函数地址一样;
printf("%d + %d = %d\n", a, b, add_func(a, b));
和printf("%d + %d = %d\n", a, b, func_ptr(a, b));
的值一样- 通过创建函数指针来调用函数和直接调用原函数结果一样;
- 函数名本身就可以表示该函数地址(指针),因此在获取函数指针时,可以直接用函数名,也可以取函数的地址;
func_ptr = add_func
可以改成func_ptr = &add_func
;
2. 指向函数的指针
本质是一个指针变量;又叫函数指针;
指向函数的指针包含了函数的地址,可通过它来调用函数;
常用在回调函数,一个函数传入另外一个函数的地址,可在某个事件发生时调用特定函数;
格式:
函数返回值类型 (* 指针变量名) (函数参数列表)
函数返回值类型:该指针变量可以指向什么返回值类型的函数;
函数参数列表:该指针变量可以指向什么参数列表的函数。这个参数列表中只需要写函数的参数类型即可;
声明中的括号不能省略,省略后是
函数返回值类型 * 指针变量名 (函数参数列表)
,变成一个返回值为指针型的函数;为了方便使用:
typedef 函数返回值类型 (* 指针变量名) (函数参数列表);
“指针变量名fptr”不等于“函数名”;
函数名本身就可以表示该函数地址(指针),因此在获取函数指针时,可以直接用函数名,也可以取函数的地址;
fptr = &FuncName;
或者fptr = FuncName;
函数指针怎么使用呢?下面是几种常见方式:
① 通过函数指针调用函数
如上文所述,可以通过函数指针来调用函数。函数指针是一个指针变量,可以做左值使用;
那么,如何通过函数指针去调用函数呢?分为3步:
-
声明函数指针,参数列表不建议省略,两种写法;
-
可以使用:
函数返回值类型 (* 指针变量名) (函数参数列表);
-
也可以使用:
typedef 函数返回值类型 (* 指针变量名) (函数参数列表);
-
-
使指针指向函数,两种写法;
fptr = &FuncName;
- 或者
fptr = FuncName;
-
调用函数,根据声明的函数指针的返回值类型确定调用方式(解释如下面例子所示),也有两种方式。
返回值类型 = (*fptr)(函数参数列表);
,这种方式更好理解;- 或者
返回值类型 = (fptr)(函数参数列表);
举例如下:
现有一个求解两整数最大值的函数,声明如下。怎么定义函数指针来调用这个函数呢?
int Max(int x, int y) //定义Max函数
{
int z;
if (x > y)
{
z = x;
}
else
{
z = y;
}
return z;
}
根据:函数指针变量和要调用的函数的参数个数、类型、顺序以及返回值类型必须一致,声明函数指针如下:
int Max(int x, int y); //函数声明
/* 1. 直接声明 */
int(*fptr)(int, int); //定义一个函数指针
/* 2. 使用typedef */
typedef int(*FPTR)(int, int); //创建的函数指针别名
使指针指向函数:
int Max(int x, int y); //函数声明
/* 1. 使用直接声明 */
fptr = &Max;
//或
fptr = Max;
/* 2. 使用typedef */
FPTR MaxPtr = &Max;
//或
FPTR MaxPtr = Max;
调用函数:
#include <stdio.h>
int Max(int x, int y); //函数声明
typedef int(*FPTR)(int, int); //创建的函数指针别名
int main(void)
{
FPTR MaxPtr; //创建函数指针MaxPtr
int a, b, c;
//把函数Max赋给指针变量MaxPtr, 使MaxPtr指向Max函数
MaxPtr = Max;
//输入数值
printf("please enter a and b:");
scanf("%d%d", &a, &b);
//通过函数指针来调用其指向的函数,这里是调用Max函数,因为函数的返回值是int,方便输出,这里使用int c变量接收
c = (*MaxPtr)(a, b);
//c = (MaxPtr)(a, b);
printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
return 0;
}
int Max(int x, int y) //定义Max函数
{
//……
}
② 函数指针作为某个函数的参数
上述解释了如何通过函数指针变量来调用函数,可能这样的代码使用函数指针来调用函数显得过于复杂了。实际上,函数指针有以下优势和特点:
- 动态选择函数,实现多态性: C语言中,使用函数指针允许在运行时选择要调用的函数。当某些函数只有函数名不同,参数列表个数、类型和返回值都相同的情况下,这在需要在不同情况下调用不同函数的情况下非常有用。通过将不同的函数指针赋值给相同的函数指针变量,可以实现不同的行为。例如:排序函数可能有非常多种,如冒泡排序、选择排序、递归排序等等。需要根据实际情况选择调用,但是它们除了函数名不一样,其他都是一样的,这时就可以使用函数指针进行调用。
- 回调函数: 函数指针常用于实现回调函数的机制。在这种情况下,一个函数的地址被传递给另一个函数,以便在适当的时候调用。这是实现事件处理、用户界面和异步编程等的常见模式。
- 提高灵活性: 函数指针增加了代码的灵活性。通过使用函数指针,可以更容易地实现可扩展的、可配置的系统,而无需硬编码特定的函数调用
思考:既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。函数指针作为函数参数的真正的应用还是在回调函数中;
举例如下:
现需要实现两个整数的运算,包括简单的加、减、乘、除和自定义运算(乘方、取余等);
- 各种计算函数定义如下:
/* 加 */
int add_func(int x, int y)
{
return x + y;
}
/* 减 */
int subtract_func(int minuend, int subtrahend)
{
return minuend - subtrahend;;
}
/* 乘 */
int multiply_func(int factor1, int factor2)
{
return factor1 * factor2;
}
/* 除 */
int divide_func(int dividend, int divisor)
{
if (divisor != 0)
{
return (float)dividend / divisor; //将结果向零取整
}
else
{
// 返回错误值或进行适当处理
printf("error:divisor can not be 0! \n");
return 0;
}
}
/* 乘方函数 */
int power_func(int base, int exponent)
{
int result = 1;
for (int i = 0; i < exponent; ++i)
{
result *= base;
}
return result;
}
/* 取余 */
int modulo_func(int dividend, int divisor)
{
if (divisor != 0)
{
return dividend % divisor;
}
else
{
// 返回错误值或进行适当处理
printf("error:divisor can not be 0! \n");
return 0;
}
}
//自定义计算函数,参数列表、返回值类型和上述相同
int my_func(int x, int y)
{
return 5 * x + 8 * y;
}
-
总计算函数
定义一个总计算函数,用来调用上述函数。
//该函数有三个参数,函数指针func_ptr和整数a、b;与add_func,subtract_func,multiply_func等函数形式相同
int processing_func(int (*func_ptr)(int, int), int x, int y)
{
/* 通过func_ptr的指针执行传递进来的函数 */
return func_ptr(x, y);
}
//或者写成
typedef int(*FUNC_PTR)(int, int);
int processing_func(FUNC_PTR func_ptr, int x, int y)
{
/* 通过func_ptr的指针执行传递进来的函数 */
return (*func_ptr)(x, y);
}
- 调用函数
#include<stdio.h>
int add_func(int x, int y);
int subtract_func(int x, int y);
int multiply_func(int x, int y);
int modulo_func(int dividend, int divisor);
int divide_func(int dividend, int divisor);
int power_func(int base, int exponent);
int my_func(int x, int y);
/* 定义函数指针:有两个整数参数、返回值整数 */
//int(*func_ptr)(int, int);
typedef int(*FUNC_PTR)(int, int); //方便使用:定义一个名为FUNC_PTR函数指针类型
//int processing_func(int (*func_ptr)(int, int), int x, int y);
int processing_func(FUNC_PTR func_ptr, int x, int y);
int main(void)
{
int num1 = 6;
int num2 = 10;
//直接调用
printf("using specific function:\n");
printf("%d + %d = %d\n", num1, num2, add_func(num1, num2));
printf("%d - %d = %d\n", num1, num2, subtract_func(num1, num2));
printf("%d * %d = %d\n", num1, num2, multiply_func(num1, num2));
printf("%d / %d = %d\n", num1, num2, divide_func(num1, num2));
printf("%d raised to the power of %d = %d\n", num1, num2, power_func(num1, num2));
printf("%d modulo %d = %d\n", num1, num2, modulo_func(num1, num2));
printf("5*%d + 8*%d = %d\n", num1, num2, my_func(num1, num2));
printf("\n");
//通过调用compute_func函数计算
printf("using processing_func:\n");
printf("%d + %d = %d\n", num1, num2, processing_func(add_func, num1, num2));
printf("%d - %d = %d\n", num1, num2, processing_func(&subtract_func, num1, num2));
//前文所述:fptr = &FuncName;等价于fptr = FuncName;
printf("%d - %d = %d\n", num1, num2, processing_func(subtract_func, num1, num2));
printf("%d * %d = %d\n", num1, num2, processing_func(multiply_func, num1, num2));
printf("%d / %d = %d\n", num1, num2, processing_func(divide_func, num1, num2));
printf("%d raised to the power of %d = %d\n", num1, num2, processing_func(power_func, num1, num2));
printf("%d modulo %d = %d\n", num1, num2, processing_func(modulo_func, num1, num2));
printf("5*%d + 8*%d = %d\n", num1, num2, processing_func(my_func, num1, num2));
return 0;
}
运行结果如下:
using specific function:
6 + 10 = 16
6 - 10 = -4
6 * 10 = 60
6 / 10 = 0
6 raised to the power of 10 = 60466176
6 modulo 10 = 6
5*6 + 8*10 = 110
using compute_func:
6 + 10 = 16
6 - 10 = -4
6 - 10 = -4
6 * 10 = 60
6 / 10 = 0
6 raised to the power of 10 = 60466176
6 modulo 10 = 6
5*6 + 8*10 = 110
3. 回调函数
① 基本概念
回调函数(callback function)是一种特殊的函数,通过函数指针将一个函数传递给另一个函数,从而在某个特定事件发生时执行的机制。回调函数通常用于事件处理、异步编程和处理各种操作系统和框架的API;
- 事件处理:当某个事件发生时(例如按钮被点击、数据读取完成等),系统调用预先定义好的回调函数来处理相应的逻辑。
- 异步编程:在异步编程中,回调函数可以在异步任务完成时执行,以处理任务的结果或执行其他后续操作。
上述例子的add_func
等各个计算就是一个回调函数。
举例:
需要对一个整数数组进行某种处理,例如将每个元素加倍。这时,使用回调函数来定义处理的具体逻辑,使得该处理逻辑可以在主调用的函数中进行使用。
#include <stdio.h>
// 定义回调函数类型
typedef void (*ArrayProcessor)(int[], int);
// 主调函数,接受回调函数和数据作为参数
void processArray(int array[], int size, ArrayProcessor processor)
{
// 执行回调函数
processor(array, size);
}
// 回调函数1:将数组中的每个元素加倍
void doubleArray(int array[], int size)
{
for (int i = 0; i < size; ++i)
{
array[i] *= 2;
}
}
// 回调函数2:将数组中的每个元素平方
void squareArray(int array[], int size)
{
for (int i = 0; i < size; ++i)
{
array[i] *= array[i];
}
}
void printArray(int array[], int size)
{
printf("Array: ");
for (int i = 0; i < size; ++i)
{
printf("%d ", array[i]);
}
printf("\n");
}
int main()
{
int numbers[] = { 1, 2, 3, 4, 5 };
int size = sizeof(numbers) / sizeof(numbers[0]);
// 使用 doubleArray 回调函数,将数组元素加倍
processArray(numbers, size, doubleArray);
printArray(numbers, size);
// 使用 squareArray 回调函数,将数组元素平方
processArray(numbers, size, squareArray);
printArray(numbers, size);
return 0;
}
输出结果:
Array: 2 4 6 8 10
Array: 4 16 36 64 100
② 为什么要使用回调函数
回调函数是一种在特定事件发生时由另一个函数调用的函数。使用回调函数的主要目的是实现程序的灵活性和可扩展性。
- **模块化管理:**回调函数可以将不同功能的代码分离开来,使代码更加模块化和易于理解。主调函数负责执行基本操作,而回调函数则处理特定的任务。使用者哥调用者之间的代码更加好管理。
- **可扩展性:**通过使用回调函数,可以轻松地拓展新的功能,而无需修改主调函数,使得程序更容易扩展和维护。
- **适应不同场景:**回调函数允许在不同的情况下采取不同的行动。
4. 指针做函数返回值
- 本质是一个函数;又叫指针函数;
- 格式:
函数类型标识符 *函数名 (参数列表)
;- 函数返回类型是某一类型的指针,返回地址给调用者,通常是指针变量或者地址表达式;
举例:设计一个函数,返回数组元素中的最大值指针。
#include <stdio.h>
// 指针函数:返回数组中的最大元素的指针
int* findMaxInArray(int arr[], int size)
{
if (size <= 0)
{
return NULL; // 处理数组为空的情况
}
int maxIndex = 0;
for (int i = 1; i < size; ++i)
{
if (arr[i] > arr[maxIndex])
{
maxIndex = i;
}
}
return &arr[maxIndex];
}
int main()
{
int numbers[] = {12, 5, 8, 20, 15};
int size = sizeof(numbers) / sizeof(numbers[0]);
// 调用指针函数,获取数组中最大元素的地址
int* maxPtr = findMaxInArray(numbers, size);
// 输出最大元素的值
if (maxPtr != NULL)
{
printf("The maximum element in the array is: %d\n", *maxPtr);
}
else
{
printf("Array is empty.\n");
}
return 0;
}
举例:
在嵌入式开发中,使用指针函数来动态分配内存并返回分配的内存地址,以便在程序中处理各种数据结构,例如链表或缓冲区。
#include <stdlib.h>
#include<stdio.h>
// 定义结构体
typedef struct SensorData
{
int sensorId;
float value;
}*SensorData;
// 指针函数:动态分配内存并返回结构体指针
SensorData createSensorData(int id, float val)
{
//struct SensorData* sensorDataPtr = (struct SensorData*)malloc(sizeof(struct SensorData));
SensorData sensorDataPtr = (SensorData)malloc(sizeof(struct SensorData));
if (sensorDataPtr != NULL)
{
sensorDataPtr->sensorId = id;
sensorDataPtr->value = val;
}
return sensorDataPtr;
}
int main()
{
// 调用指针函数,获取动态分配的结构体指针
SensorData sensor1 = createSensorData(1, 23.5);
// 检查指针是否有效
if (sensor1 != NULL)
{
// 使用结构体指针访问数据
printf("Sensor ID: %d\n", sensor1->sensorId);
printf("Sensor Value: %.2f\n", sensor1->value);
// 释放动态分配的内存
free(sensor1);
}
else
{
printf("Failed to allocate memory for sensor data.\n");
}
return 0;
}
注意:
合理使用指针函数: 指针函数可以在某些情况下提供更灵活的返回选项,但也需要谨慎使用,并避免返回指向无效或已释放内存的指针。
如果指针函数返回的是指向局部变量的指针,应避免在函数外部使用这个指针,因为局部变量的生命周期在函数调用结束时结束。
参考:
- 《C和指针》和《C Primer Plus》
- 【精选】C语言回调函数详解(全网最全)-CSDN博客
- 【C语言进阶】⑥函数指针详解-CSDN博客