【C语言学习】——指针Pointer

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;
  1. 指针变量的内容是地址(address);整型指针变量d内存存储的是整型变量a的地址;同理,浮点型指针变量e存储也是地址,是浮点型变量c的地址;
  2. 指针变量的值和指针变量指向变量的值不一样;指针存的是所指向变量的地址而不是值。
  3. 变量(variable)的值(value)是分配给这个变量内存位置所存储的数值
  4. ab存储整数值,而浮点型c在内存中也是整数,但是可以被解释为浮点数,关键在于使用这个值的方式;
  5. 编译器实现了"变量名称"和"内存地址"的连接;本质上在硬件层,还是需要通过地址访问内存;

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)
  • 使用操作符是单目操作符*;
表达式右值类型
a112int
b-1int
c3.14float
d100int *
e108float *
*d112int
*e3.14float

【注意】

  1. 若用箭头表示下面的变量的内存指示关系,则在一定程度上存在误解:
    在这里插入图片描述

  2. 修正后的如下图所示,“指示关系”是在解引用操作符*作用之后才存在
    在这里插入图片描述

2.3 const修饰指针变量

1. 关键字const的作用

  1. 声明常量:可以使用 const 声明一个常量;
  2. 防止无意修改const 可以用于防止在后续代码中无意间修改一个应该保持不变的值,提高代码的鲁棒性。
  3. 编译器优化:可以使用 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;
  • 上述声明了两个变量:chcpcp作为一个指针指向ch
  • 假设cp变量地址为100,ch变量的地址为108,即现在cp内存的数据为:108,ch内存的数据为字符a;

​ 分析如下:

序号表达式右值左值
1&ch字符型变量cp的地址信息,即“108”这个值非法 不可寻址,不可作为空间使用
2cp指针变量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后面的内存空间非法 不可寻址
8cp++先进行执行拷贝操作,指向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)解析这个新地址数据内容下一个地址空间

【关键】

  1. 理解指针变量的本质解引用操作符的本质
  2. 理解不同运算符的优先级和结合性

参考:《C和指针》

2.4 指针和函数

1. 指针做函数的参数

  1. 典型应用就是:指针变量做形参,操作地址,可以改变实参;
  2. 形参定义指针*,调用传入地址&
  3. 与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

可以看到:

  1. printf("add_func address = %p\n", add_func);printf("func_ptr = %p\n", func_ptr);的值一样,都是函数的地址;
  2. 通过创建函数指针来存储函数的入口地址和函数地址一样;
  3. printf("%d + %d = %d\n", a, b, add_func(a, b));printf("%d + %d = %d\n", a, b, func_ptr(a, b));的值一样
  4. 通过创建函数指针来调用函数和直接调用原函数结果一样;
  5. 函数名本身就可以表示该函数地址(指针),因此在获取函数指针时,可以直接用函数名,也可以取函数的地址
  6. func_ptr = add_func可以改成 func_ptr = &add_func
2. 指向函数的指针
  1. 本质是一个指针变量;又叫函数指针

  2. 指向函数的指针包含了函数的地址,可通过它来调用函数;

  3. 常用在回调函数,一个函数传入另外一个函数的地址,可在某个事件发生时调用特定函数;

  4. 格式:函数返回值类型 (* 指针变量名) (函数参数列表)

    • 函数返回值类型:该指针变量可以指向什么返回值类型的函数;

    • 函数参数列表:该指针变量可以指向什么参数列表的函数。这个参数列表中只需要写函数的参数类型即可;

    • 声明中的括号不能省略,省略后是函数返回值类型 * 指针变量名 (函数参数列表),变成一个返回值为指针型的函数;

    • 为了方便使用typedef 函数返回值类型 (* 指针变量名) (函数参数列表);

    • “指针变量名fptr”不等于“函数名”;

  5. 函数名本身就可以表示该函数地址(指针),因此在获取函数指针时,可以直接用函数名,也可以取函数的地址

    • fptr = &FuncName;或者fptr = FuncName;

​ 函数指针怎么使用呢?下面是几种常见方式:

① 通过函数指针调用函数

​ 如上文所述,可以通过函数指针来调用函数。函数指针是一个指针变量,可以做左值使用

​ 那么,如何通过函数指针去调用函数呢?分为3步:

  1. 声明函数指针,参数列表不建议省略,两种写法;

    • 可以使用:函数返回值类型 (* 指针变量名) (函数参数列表);

    • 也可以使用:typedef 函数返回值类型 (* 指针变量名) (函数参数列表);

  2. 使指针指向函数,两种写法;

    • fptr = &FuncName;
    • 或者fptr = FuncName;
  3. 调用函数,根据声明的函数指针的返回值类型确定调用方式(解释如下面例子所示),也有两种方式。

    • 返回值类型 = (*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语言中,使用函数指针允许在运行时选择要调用的函数。当某些函数只有函数名不同,参数列表个数、类型和返回值都相同的情况下,这在需要在不同情况下调用不同函数的情况下非常有用。通过将不同的函数指针赋值给相同的函数指针变量,可以实现不同的行为。例如:排序函数可能有非常多种,如冒泡排序、选择排序、递归排序等等。需要根据实际情况选择调用,但是它们除了函数名不一样,其他都是一样的,这时就可以使用函数指针进行调用。
  • 回调函数: 函数指针常用于实现回调函数的机制。在这种情况下,一个函数的地址被传递给另一个函数,以便在适当的时候调用。这是实现事件处理、用户界面和异步编程等的常见模式。
  • 提高灵活性: 函数指针增加了代码的灵活性。通过使用函数指针,可以更容易地实现可扩展的、可配置的系统,而无需硬编码特定的函数调用

思考:既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。函数指针作为函数参数的真正的应用还是在回调函数中

​ 举例如下:

​ 现需要实现两个整数的运算,包括简单的加、减、乘、除和自定义运算(乘方、取余等);

  1. 各种计算函数定义如下:
/* 加 */
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;
}
  1. 总计算函数

    定义一个总计算函数,用来调用上述函数。

//该函数有三个参数,函数指针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);
}
  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 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

  1. 事件处理:当某个事件发生时(例如按钮被点击、数据读取完成等),系统调用预先定义好的回调函数来处理相应的逻辑。
  2. 异步编程:在异步编程中,回调函数可以在异步任务完成时执行,以处理任务的结果或执行其他后续操作。

​ 上述例子的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
② 为什么要使用回调函数

在这里插入图片描述

​ 回调函数是一种在特定事件发生时由另一个函数调用的函数。使用回调函数的主要目的是实现程序的灵活性和可扩展性。

  1. **模块化管理:**回调函数可以将不同功能的代码分离开来,使代码更加模块化和易于理解。主调函数负责执行基本操作,而回调函数则处理特定的任务。使用者哥调用者之间的代码更加好管理。
  2. **可扩展性:**通过使用回调函数,可以轻松地拓展新的功能,而无需修改主调函数,使得程序更容易扩展和维护。
  3. **适应不同场景:**回调函数允许在不同的情况下采取不同的行动。

4. 指针做函数返回值

  1. 本质是一个函数;又叫指针函数
  2. 格式:函数类型标识符 *函数名 (参数列表)
  3. 函数返回类型是某一类型的指针,返回地址给调用者,通常是指针变量或者地址表达式

举例:设计一个函数,返回数组元素中的最大值指针。

#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;
}

注意:

合理使用指针函数: 指针函数可以在某些情况下提供更灵活的返回选项,但也需要谨慎使用,并避免返回指向无效或已释放内存的指针。

如果指针函数返回的是指向局部变量的指针,应避免在函数外部使用这个指针,因为局部变量的生命周期在函数调用结束时结束。

参考:

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值