C 语言指针进阶

1.0 指针的定义

指针是内存中一个最小单元的编号(内存单元的编号称之为地址【地址就是指针指针就是地址】)指针通常是用来存放内存地址的一个变量。本质上指针就是地址:口语上说的指针起始是指针变量,指针变量就是一个变量,是一个用于存放地址的变量,指针指向的就是地址,通过这个地址可以找到对应的内存单元。

指针变量创建:

#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	// a 是一个整型变量,占用4个字节内存空间
	int a = 10;
	// pa 是一个指针变量,指针变量是用来存放地址的
	int* pa = &a;
	return 0;
}

总的来说,指针变量是用于存放地址的变量,(存放在指针中的值被当做是地址处理),在32位的平台下一个指针变量的大小是4个字节,在64位地址的机器上指针变量的大小是8个字节。

#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	char   *pc = NULL;
	short  *ps = NULL;
	int    *pi = NULL;
	double *pd = NULL;

	printf("%zu\n", sizeof(pc));
	printf("%zu\n", sizeof(ps));
	printf("%zu\n", sizeof(pi));
	printf("%zu\n", sizeof(pd));
	return 0;
}


2.0 指针与指针类型

指针类型的含义

1. 指针的类型决定的指针被解引用的时候被访问几个字节【所谓的解引用是官方的叫法,实际上就去获取地址当中的值,或者说是将地址当中的值取出来】,例如:int * 的指针,解引用访问4个字节,char * 的指针解引用是访问一个字节,推广到其他的参数时也是一样的。

#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;

	char* pc = (char*)&a;
	*pc = 0;

	return 0;
}

2. 指针的步长:理解指针步长的意义

#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	int a = 0x11223344;
	int* pa = &a;
	char* pc = (char *) & a;

	printf("pa = %p\n", pa);
	printf("pa = %p\n", pa + 1);

	printf("pa = %p\n", pc);
	printf("pa = %p\n", pc + 1);
	return 0;
}

指针类型决定了访问内存一次可以访问几个字节,如果是char * 的指针类型,那么访问内存只能一个字节一个字节的进行访问,如果是int * 类型的指针类型,那么访问内存就是以一次4个字节进行访问, int * pc如果是pc + 1 的话那么就是跳过前面的4个字节。


注:不同类型指针占用类型大小相同的指针是不能混用的,因为两者存储在内存中的空间是不同的,int 类型存储的是int类型,float类型存储的是float类型。

#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	int a = 0;
	int* pi = &a;  // pi 解引用访问4个字节,pi + 1 也是跳过4个字节
	float* pf =(float *)& a;// pf 解引用访问4个字节,pf + 1 也是跳过4个字节

	return 0;
}


3.0 野指针,指针,与指针运算

野指针的概念:指的是指针指向的位置是不可知的

int main
{
	// p没有初始化,就意味着没有明确的指向
	// 一个局部变量不初始化,放的是随机值
   int *p;
   // 这样做的方式就是非法的访问内存,这个时候p就是野指针
   *p = 10;
}

#define  _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>

/*
****************************
*  DEF    :  指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/14
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	int arr[10] = { 0 };
	// 数组名表示数组首元素地址arr[0]
	int* p = arr;
	// 指针的越界访问也会造成野指针
	for (int i = 0; i <= 10; i++) 
	{
		*p = i;
		p++;
	}
	return 0;
}


野指针:涉及到局部变量和全局变量知识

函数执行进入主函数:main

这个时候test() 函数被调用

通过return 返回a的地址地址返回后int a 由于数局部变量出函数之后被销毁

这个时候 int *p 通过地址可以找到a这个空间
但是这个时候a空间已经被销毁,p这个时候无法访问和使用这个空间,这个时候这个p属于是野指针。

int * test()
{
   int a = 10;
   return &a;
}

int main()
{
   int *p = test();
   return 0;
}


4.0 如何避免野指针

NULL 的值相当于是0 ,0 空间是没有办法被指针访问的,避免野指针的方式可以在初始化不知道赋值为什么的时候赋值为一个NULL也就是空指针,同时在使用之前先判断指针是否为空,如果指针为空就不使用,如果指针不为空的时候就对指针变量进行相应的操作,具体的示例如下所示。

避免野指针:

1: 指针初始化,

2:小心数组下标越界,

3:指针指向空间释放及时设置为NULL,

4:避免返回局部变量的地址,

5:使用指针之前先检查指针的有效性。

int * p = NULL;
int mian(void)
{
	if(p != NULL)
	{
		*p3 = 100;
	}
	return 0;

}

指针加减整数运算

#define N_VALUE 5
float values[N_VALUES];
float *vp

for(vp = &values[0]; vp < &values[N_VALUES];)
{
	*vp++ = 0;
}

int main(void)
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr)/sizeof(arr[0]);
	

	for(i = 0; i < sz; i++)
	{
		// 把数组里面的值全部赋值为 1
		arr[i] = 1;
	}
	// int 类型的指针变量p,数组名等于数组首元素的地址
	int *p = arr;
	for(i = 0; i < sz; i++)
	{
		// *P 解引用:也就是取值的意思
		*p = 1;
		p++;
	
	}
	// 使用指针步长的方式访问数组,进行数组操作
	for(i = 0; i < sz; i++)
	{
		*(p + 1) = 1;
	}
	return 0;

}

5.0 指针加减指针

int main(void)
{
    // 指针减去指针得到的绝对值得到的是指针之间元素的个数
	// 不是所有的指针都能相减,指向同一块空间的指针才能相减
	int arr[10] = { 0 };
	printf("%d\n",&arr[9] - &arr[0]); // 9
}

字符串长度:使用指针减去指针的方式

int my_strlen(char *str)
{*

   // str 刚开始起始地址是a
	int count = 0;
	while(*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}
// 指针 - 指针
int my_strlen(char *str)
{
	char *start = str;
	while(*str != '\0')
	{
		str++;
	}
	return (str - start);

}

int main(void)
{
	int len = my_strlen("abcdef");
	printf("%d\n",len);

}

指针的关系运算

#define N_VALUES 5
float values[N_VALUES]
float *vp;

for(vp = &values[N_VALUES]; vp > &values[0];)
{
	*--p = 0;
}

C 语言标准规定指向指针的元素与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。


指针遍历访问数组

数组的定义:数组是一组相同元素的集合,指针变量是一个变量,存放的是地址,数组的数组名是首元素的地址 。

int main(void)
{
	int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int *p = arr;
	int len = sizeof(arr)/sizeof(arr[0]);
	int i = 0
	for(i = 0; i < len; i++)
	{
		printf("%d ",*(p + i));
	}
    for(i = 0; i < len; i++)
    {
        // 打印输出的地址结果是相同的
        printf("%p-----------------%p\n",&arr[i],p + i);
    }
	return 0;
}

6.0 二级指针

引入一级指针的概念:

int a  = 10 ; a 是一个int 类型的变量初始值为10

int * pa = &a; pa是一个指针,pa是一个指向int 类型的指针变量,

&a 把 变量a在内存中的地址赋值给指针变量pa ,pa可以通过这个地址找到a并获取a在变量当中的值并修改同时打印输出。

int main()
{
	int a = 10;
	// pa 是一个一级指针变量
	int* pa = &a;
	*pa = 20;
	printf("%d ", a);

	return 0;
}

二级指针变量:程序输出的值是 30 注意理解:int **ppa 中

int main()
{
	int a = 10;
	// pa 是一个一级指针变量
	int* pa = &a;
	int** ppa = &pa;
	**ppa = 30;
	printf("%d ", a);
	return 0;
}

 二级指针存放的是一级指针变量的地址

int main()
{
	int a = 10;
	// pa 是一个一级指针变量
	int* pa = &a;
	int** ppa = &pa;
	**ppa = 30;
	printf("%d ", a);
	return 0;
}

7.0 指针数组

【指针数组:存放指针的数组就是指针数组】

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int arr[10];

	int* pa = &a;
	int* pb = &b;
	int* pc = &c;
	// parr 是存放指针的数组,称之为指针数组
	int* parr[10] = { &a, &b, &c };

	int i = 0;
	int len = sizeof(parr) / sizeof(parr[0]);
	for (i = 0; i < len; i++) 
	{
		printf("%d ", *(parr[i]));
	}
	return 0;
}

【补充:二维数组创建与遍历】

/*
****************************
*  DEF    :  二级指针    
*  参数   :  无参数
*  返回值 :  无返回值
*  时间   :  2024/7/15
*  作者   :  _沧浪之水_
****************************
*/

int main()
{
	int arr[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++) 
	{
		for (j = 0; j < 4; j++) 
		{
			printf("%d  ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

指针模拟二维数组

int main()
{
	int arrOne  [4] = { 1, 2, 3, 4 };
	int arrTwo  [4] = { 2, 3, 4, 5 };
	int arrThree[4] = { 6, 7, 8, 9 };

	int* parr[3] = { arrOne,  arrTwo, arrThree };

	int i = 0;
	for (i = 0; i < 3; i++) 
	{
		int j = 0;
		for (j = 0; j < 4; j++) 
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}


字符指针

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

int main()
{
	char ch = 'c';
	char* p = &ch;
	*p = 'b';
	printf("%c\n", ch);

	const char* pc = "abcdef";
	printf("%s\n", pc);
	return 0;
}

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

int main()
{
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";

	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	if (p1 == p2) 
	{
		printf("p1 == p2\n");
	}
	else 
	{
		printf("p1 != p2\n");
	}
	if (arr1 == arr2) 
	{
		printf("arr1 == arr2\n");
	}
	else 
	{
		printf("arr1 != arr2\n");
	}
	return 0;
}

8.0 指针数组


int arr[10]; // 表示整形数组

char ch[5]; // 表示字符数组

int *arr[6]; // 存放整形指针的数组

char * arr[5] // 存放字符类型的数组


int* parr[MAX_ARR_NUM] = { arr1, arr2, arr3 };

以上这段代码的含义是 创建指针数组 parr

注: parr 是一个指针变量 ,指向的是一个数组,数组中存放了三个变量,也就是数组元素的首地址,每个变量的类型是int * 【也就是创建了一个int* 类型,变量名字叫做parr的数组指针,指向每个数组元素的首地址,每个数组元素的类型为 int 】

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

#define MAX_ARR_NUM 3

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,2,3,4,5 };
	int arr3[] = { 1,2,3,4,5 };
	// 数组名表示数组首元素地址,数组指针存放的是三个数组的地址
	int* parr[MAX_ARR_NUM] = { arr1, arr2, arr3 };
	int i = 0;
	for (i = 0; i < MAX_ARR_NUM; i++) 
	{
		int j = 0;
		for (j = 0; j < 5; j++) 
		{
             // *(parr + i ) == p [i];
             printf("%d ", parr[i][j]);
			printf("%d ", *(parr[i] + j));
		}
		printf("\n");
	}

	return 0;
}

注:*(parr + i)  等价于 parr[i] ,所以上面的代码有两种不同的遍历办法


以下是一些数组地址的补充阐释,为了更好的理解下面的知识

[数组指针 --- 指针-------指向数组的指针]

整型指针-------指向整型的指针

字符指针------指向字符的指针

int* p[10]         p 是指针数组

int (*p)[10]       p 是一个数组指针,p可以指向一个数组,该数组有10个元素,每个元素是int 类型。


数组名的理解

数组名通常表示数组首元素的地址,但是有两个例外,

1.sizeof(数组名),这里的数组表示的是整个数组,计算的是整个数组的大小,

2.&数组名,这里的数组名表示的依然是整个数组。所以取出的是整个数组的地址

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

#define MAX_ARR_NUM 3

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);

	int sz = sizeof(arr);
	printf("%d\n", sz);
	return 0;
}

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

#define MAX_ARR_NUM 3

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", arr + 1);

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0] + 1);

	// 这是一个数组的地址,&arr取出的是整个数组
	printf("%p\n", &arr);
	// &数组名 + 1 表示的是跳过整个数组
	printf("%p\n", &arr + 1);

	int sz = sizeof(arr);
	printf("%d\n", sz);
	return 0;
}


数组指针是用于存放数组的地址

int main()
{
	// 数组:名字是arr
	int arr[10] = { 0 };
	// 指针: 数组名arr表示首元素的地址,将首元素的地址放到一个int类型的arr指针当中
	int* p = arr;
	// 数组指针:存放数组的地址,表示的是一个int类型的数组指针
	int(*p)[10] = &arr;
	return 0;
}

注意:上面的代码中 int * p = arr; p 是一个指针,指针的类型是 int * . int (*p)[10] = &arr ; 这是一一个数组指针,存储的是整个数组的,指针的类型是 int *[] 。


9.0 数组指针

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++) 
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

以上创建了一个10个元素的数组,将数组首元素的地址赋值给一个指向数组的指针,p是一个指针变量,指向的是arr的数组 。所以p 是指向int 类型的数组指针,arr即表示一个数组,也表示一个数组首元素的地址,p + i 相当于是取到数组对应的地址*(p + i)取出地址对应的元素。


数组指针的使用

void print(int arr[3][5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++) 
	{
		int j = 0;
		for (j = 0; j < c; j++) 
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,4,5,6,7,8 };
	print(arr, 3, 5);
	return 0;
}

以上是为了引出数组指针用法的案例


void print(int arr[3][5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++) 
	{
		int j = 0;
		for (j = 0; j < c; j++) 
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

void print1(int(*p)[5], int r, int c) 
{
	int i = 0;
	for (i = 0; i < r; i++) 
	{
		int j = 0;
		for (j = 0; j < c; j++) 
		{
			printf("%d ", *(*(p + i) + j));
			// 第二种不同的写法
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}

}

int main()
{
	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,4,5,6,7,8 };
	print1(arr, 3, 5);
	return 0;
}


10.0 数组指针的使用

int main()
{
	// 这是一个int类型的名为arr的数组
	int arr[5];
	// parr1是整形指针数组
	int* parr1[10];
	//  parr2是数组指针:(parr2 和 * 结合是一个指针),
	// (指针指向的是数组,数组有10个元素,每个元素是int)
	// parr2 是数组指针
	int(*parr2)[10];
	// parr3是一个存放数组指针的数组
	// 数组有10个元素,每个元素存放的是数组指针的数组
	int(*parr3[10])[5];
	return 0;
}

以下是我对每一个变量,每一个元素的理解

int arr[5];
创建一个int类型的数组,数组名为arr数组包含5个元素,每个元素的类型是int

int* parr1[10];
创建一个指针数组 parr是一个数组,数组里面包含10个元素,每个元素的类型是int *


int (*parr2)[10];
    parr2 是一个指针。
    这个指针 parr2 指向的是一个数组。
    它指向的数组包含10个元素。
    这些元素的类型是 int。

int(*parr3[10])[5];
  parr3 是一个包含10个元素的数组,其中每个元素都是一个指针,每个指针指向一个具有5个 int 类型元素的数组。以下的图片对应的是最后一个的理解



数组参数与指针参数

一级指针参数传递实际分析

传递过去参数的本质是一个指针,就可以作为一个参数传递过去

void test(int arr[]) {};

void test(int arr[10]) {};

void test(int* arr) {};

void test2(int* arr[20]) {};

void test2(int** arr) {};



int main()
{
	int arr1[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
	return 0;
}

二级指针参数传递

// 可以以这种方式传递参数
void test(int arr[3][5]) {};

// 这种方式是不可以的,形参二维数组行可以省略,列不可以省略
void test(int arr[][]) {};

// 这个方式传递参数也是可以的
void test(int* arr[][5]) {};

void test2(int* arr[20]) {};

void test(int(*arr)[5]) {};



int main()
{
	// 二维数组参数传递
	int arr[3][5] = { 0 };
	// 注意:数组参数传递传的是数组名
	test(arr);
	return 0;
}

二级指针传递参数使用二级指针接收,函数的形式参数是二级指针,调用二级指针的时候传递什么实参?

传递过去的参数要是形式参数的地址一致即可


函数指针

数组指针----指向数组的指针就是数组指针,指向函数的指针--就是函数指针

对于函数来说,&函数名和函数名取到的值都是函数的地址

函数指针调用

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>


int main()
{

	int (*pf)(int, int) = &Add;
	int ret = (*pf)(2, 3);
	// 函数的指针是指向函数的指针,&函数名是函数的地址
	printf("%d\n", ret);

	return 0;
}

为什么要使用函数指针?

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

void calc(int (*pf)(int,int)) 
{
	int a = 3;
	int b = 5;
	int ret = pf(a, b);
	printf("%d\n", ret);
}

int main()
{
	calc(Add);
	return 0;
}

函数指针知识点回顾

int main()
{
    // 指针数组
	int* arr[4];
	// char 型的指针数组
	char* ch[5];
	// 数组指针
	int arr2[5];
	// pa 指向一个数组,数组有五个元素,每个元素是int
	int(*pa)[5] = &arr2;
	char* arr3[6];
	// p3是一个指针,指向一个数组,数组里面6个元素,每个元素是char *
	char* (*p3)[6] = &arr3;
	return 0;
}

int test(const char* str) 
{
	return 0;
}

int main()
{
	// 函数指针-也是一种指针-指向函数的指针
	printf("%p\n", test);
	printf("%p\n", &test);

	
	return 0;
}

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

int test(const char* str) 
{
	printf("test()\n");
	return 0;
}

int main()
{
	// 函数指针-也是一种指针-指向函数的指针
	printf("%p\n", test);
	printf("%p\n", &test);
	// pf 是一个指针,指向的是一个函数,函数的参数是const char* 返回值类型是int
	int (*pf)(const char*) = test;
	(*pf)("abc");
	test("abc");
	pf("abc");
	return 0;
}

以上的代码是一次函数调用,调用的是0作为地址处的函数,把0转换为无参返回值类型是void函数指针的函数的地址,调用0地址处的函数《这段代码出现C陷阱与缺陷》



11.0 函数指针的用法案例

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>


void menu() 
{
	printf("******************************\n");
	printf("*********1.add and 2.sub******\n");
	printf("*********3.nul and 4.div******\n");
	printf("*********   0.exit     *******\n");
	printf("******************************\n");
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do 
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input) 
		{
			
			case 1:
				printf("请输入两个操作数:>");
				scanf("%d %d", &x, &y);
				ret = Add(x, y);
				printf("%d\n", ret);
				break;
			case 2:
				printf("请输入两个操作数:>");
				scanf("%d %d", &x, &y);
				ret = Sub(x, y);
				printf("%d\n", ret);
				break;
			case 3:
				printf("请输入两个操作数:>");
				scanf("%d %d", &x, &y);
				ret = Mul(x, y);
				printf("%d\n", ret);
				break;
			case 4:
				printf("请输入两个操作数:>");
				scanf("%d %d", &x, &y);
				ret = Div(x, y);
				printf("%d\n", ret);
				break;
			case 0:
				printf("请输入两个操作数:>");
				scanf("%d %d", &x, &y);
				printf("退出计算器\n");
				break;
			default:
				printf("选择错误\n");
		}
	} while (input);
	return 0;
}

以上的代码是冗余的,可以将这个代码抽取出来,减少代码的冗余


回调函数的案例

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

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

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

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

void menu() 
{
	printf("******************************\n");
	printf("*********1.add and 2.sub******\n");
	printf("*********3.nul and 4.div******\n");
	printf("*********   0.exit     *******\n");
	printf("******************************\n");
}

// 回调函数,通过函数指针回头指向的函数
// 参数接收的是函数的地址,函数的地址放到函数指针中
void calc(int (*pf)(int, int)) 
{
	int x = 0;
	int y = 0;
	int ret = 0;

	printf("请输入两个操作数:>");
	scanf("%d %d", &x, &y);
	ret = (*pf)(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	do 
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input) 
		{
			case 1:
				calc(Add);
				break;
			case 2:
				calc(Sub);
				break;
			case 3:
				calc(Mul);
				break;
			case 4:
				calc(Div);
				break;
			case 0:
				printf("退出计算器\n");
				break;
			default:
				printf("选择错误\n");
		}
	} while (input);
	return 0;
}

以上是通过回调函数的方式简化代码减少代码的冗余


函数指针数组

/*
   Add ... Div 都是函数的地址
*/
int main()
{
    // 函数指针的写法,这里pf是一个函数指针
    int (*pf)(int, int) = Add;
    // 这是一个函数指针数组的写法:数组里面是可以存放函数指针的
    int (*arr[4])(int,int) = { Add,Sub,Mul,Div };
    // 参数相同,返回值类型相同的数据放到一个数组中,就放到函数指针数组中
    return 0;
}

函数指针数组的用法

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

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

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

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

/*
   Add ... Div 都是函数的地址
*/
int main()
{
	// 函数指针的写法,这里pf是一个函数指针
	int (*pf)(int, int) = Add;
	// 这是一个函数指针数组的写法:数组里面是可以存放函数指针的
	int (*arr[4])(int,int) = { Add,Sub,Mul,Div };
	int i = 0;
	for (i = 0; i < 4; i++) 
	{
		int ret = arr[i](4,8);
		printf("%d\n", ret);
	}
	return 0;
}


函数指针数组的使用

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>


void menu()
{
	printf("******************************\n");
	printf("*********1.add and 2.sub******\n");
	printf("*********3.nul and 4.div******\n");
	printf("*********   0.exit     *******\n");
	printf("******************************\n");
}

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

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

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		// 转移表
		int (*pfArr[5])(int, int) = { 0 , Add, Sub,Mul, Div };
		if (input == 0) 
		{
			printf("退出计算器\n");
		}else if(input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else 
		{
			printf("选择错误\n");
		}

	} while (input);

	return 0;
}

11.0 指向函数指针数组的指针

int main()
{
	// 函数指针数组
	int (*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div };

	// 指向【函数指针数组】的指针
	int (*(*ppfArr)[5])(int, int) = &pfArr;

	return 0;
}

// 函数指针数组

int (*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div };

// 指向【函数指针数组】的指针
int (*(*ppfArr)[5])(int, int) = &pfArr;

注:(*ppfArr) 是一个指针,指向一个数组,数组里面有5个元素,每个元素的返回值是函数指int (*)(int, int) = &pfArr; (*(*ppfArr)[5]) 是一个指针,指向一个函数,函数有两个int类型的参数,参数的返回值是int。


定义了一个名为 ppfArr 的指针,它指向一个包含5个函数指针的数组。这些函数指针各自指向一个接受两个 int 类型参数并返回一个 int 类型值的函数。

让我们逐步分解:

  1. int (*(*ppfArr)[5])(int, int):

    • (*ppfArr) 是一个指向数组的指针。
    • [5] 表示这个数组有5个元素。
    • int (*)(int, int) 是数组中每个元素的类型,即每个元素都是一个指向函数的指针,该函数接受两个 int 参数并返回一个 int 值。
  2. = &pfArr;:

    • &pfArr 是取 pfArr 的地址。pfArr 必须是一个具有相同类型的数组,即一个包含5个 int (*)(int, int) 类型的函数指针的数组。

所以,ppfArr 可以被用来访问和调用 pfArr 中的任何一个函数。例如,要调用数组中的第一个函数,你可以这样做:


指向函数指针数组的指针,继续往下套的话就是执行函数指针数组的函数的指针


12.0 回调函数

回调函数的概念:

回调函数就是通过函数指针调用的函数,如果你把函数的指针地址作为参数传递给另外一个函数,当这个指针被用来调用其指向的函数时,当这个指针被用来调用其所指向的函数时,这个就是我们所说的回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件进行响应。


冒泡排序:的原理

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>

// 两个相邻的元素进行比较
void bubble_sort(int arr[], int sz) 
{
	int i = 0;
	//决定冒泡排序排序的趟数
	for (i = 0; i < sz - 1; i++) 
	{
		int flag = 1;
		// 一趟冒泡排序的过程:相邻两个元素的比较
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++) 
		{
			if (arr[j] > arr[j + 1]) 
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 0;
			}
		}
		if (flag == 1) 
		{
			break;
		}
	}
}


int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++) 
	{
		printf("%d ", arr[i]);
	}

	return 0;
}


通过冒泡排序引出回调函数的用法:使用回调函数的方式实现冒泡排序

#define  _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>


// 比较两个整形元素e1指向一个整数,e2指向另外一个整数
int cmp_int(const void* e1, const void* e2) 
{
	// void * 的指针是无法进行解引用操作的,在解引用之前先进行强制类型转换
	if(*(int *)e1 > *(int*)e2)
	{
		return 1;
	}
	else if (*(int*)e1 == *(int*)e2)
	{
		return 0;
	}
	else 
	{
		return -1;
	}
}


// 两个相邻的元素进行比较
void bubble_sort(int arr[], int sz) 
{
	int i = 0;
	//决定冒泡排序排序的趟数
	for (i = 0; i < sz - 1; i++) 
	{
		int flag = 1;
		// 一趟冒泡排序的过程:相邻两个元素的比较
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++) 
		{
			if (arr[j] > arr[j + 1]) 
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 0;
			}
		}
		if (flag == 1) 
		{
			break;
		}
	}
}


int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	// bubble_sort(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++) 
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

使用回调函数的方式调用这个数组


回调函数在项目中的用法,通常使用在项目的分层设计中,用于下层间接的调用上层的代码,在嵌入式中通过这种方式让底层的驱动和应用层进行分离

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值