【C语言进阶(3)】高阶指针

指针的概念

  1. 内存单元是有编号的,编号 == 地址 == 指针
  2. 指针本质上是个用来存放地址的变量,地址是唯一的用来标识一块内容空间。
  3. 指针(变量)的大小固定为 4/8 个字节(32位系统/64位系统)。
  4. 指针也是有类型的,指针的类型决定好了指针 ± 整数的步长,以及指针解引用操作时的能操控的字节数。

Ⅰ 字符指针

字符指针的形式一般为 char*

  • 将一个 char 类型变量的地址赋给 char* 类型的指针。
char  ch = 'a';
char* pc = &ch;

有时候也可以让字符指针指向字符串

  1. 字符指针指向的字符串为常量字符串。
  2. 字符指针此时存储的是,指向的是字符串首字符的地址
  3. 字符指针指向的常量字符串不可通过指针解引用来修改
char* p = "abcdef";//此时 p 里存着的是 a 的地址

在这里插入图片描述

  • 因为 p 指向的是个常量字符串,为了避免指向的串被修改,指针需要加上 const。
const char* p = "abdef";//这样指向的字符串就不能通过 *p 进行修改了。

字符指针例题

  • 下面代码的输出结果为什么会是这样?
  • 注:代码中比较的是串之间的地址。str1234 指向的都是各个串的首元素的地址。

在这里插入图片描述

  • str1 和 str2 分别创建了两块独立的数组空间,这两块空间的起始地址肯定不会相同,只不过这两块空间正好都存储子着 hello word! 这串数据罢了。

在这里插入图片描述

  • 因为 str3 和 str4 指向的是同一个常量字符串,常量字符串因为没人能改它,所以这个串在内存中只会创建一份,即 str3 和 str4 指向的是同一块空间的 h 的地址。

在这里插入图片描述

Ⅱ 指针数组

指针数组是数组

  • 字符数组:存放字符的数组。
  • 整型数组:存放整型的数组。
  • 指针数字:存放指针的数组,存放在数组中得元素都是指针类型。
int*  arr[5];	//存放整型指针的数组
char* str[5];	//存放字符指针的数组

指针数组的定义格式

  • 存储的元素类型 数组名 [常量表达式]
int* p1[5];
  • [] 操作符的优先级要比 * 运算符的优先级高,所以 数组名先和 [5] 结合,p1 就被定义为一个具有 5 个元素的数组。
  • 再看 int* 说明数组 p1 里存着的 5 个是 int* 类型的指针变量。

⒈指针数组的使用

1. 模拟二维数组

  • 二维数组每行的元素个数是一样的,创建多个一维数组,将这几个一维数组当成二维数组的每一行。
  • 使用指针数组存储这多个一维数组的首元素地址。
int arr1[] = {1,2,3,4};
int arr2[] = {2,2,3,4};
int arr3[] = {3,2,3,4};

int* parr[] = {arr1,arr2,arr3};//parr 数组就是指针数组

在这里插入图片描述

  • 这样子想要拿出对应位置的元素的方式和二维数组就没两样了。

在这里插入图片描述

2. 指向字符指针

  • 可以用来存储多个字符串的首字符地址。

在这里插入图片描述
在这里插入图片描述

Ⅲ 数组指针

数组指针是指针

  • 数组指针是一个指针,它指向一个数组

数组名的理解

  • 除了下面两个例外之外,数组名都表示的是数组首元素的地址。
  1. sizeof(数组名):这里的数组名表示整个数组,计算整个数组的大小。
  2. &数组名:这里的数组名表示取出整个数组的地址。

数组指针的定义

  • 将一整个数组的地址取出来赋给数组指针。
int arr[10] = {0};
int (*parr)[10] = &arr;//parr 用来存放数组的地址,parr 就是数组指针。
  • 先看 parr,数组名 parr 先和 * 结合,说明 parr 是一个指针;
  • 往后一看,跟着一个 [10],说明 parr 指向的这个数组具有 10 个元素。
  • 往前一看,这个 int 说明了 parr 指向的这个数组的 10 个元素每个都是 int 类型的变量。

⒈数组指针的使用

  • 一般情况下,使用数组指针去访问数组其实是很不方便的。
int arr[] = {1,2,3,4,5};
int (*p)[5] = &arr;

printf("%d\n",(*p)[2]);//*p <==> arr
  • 所以,在一维数组上基本不会使用数组指针,在二维数组上才有使用数组指针的意义。

管理二维数组

  • 数组名是首元素的地址,那么函数的形参就可以写成指针的形式。
  • 使用数组指针做形参的时候,数组指针就是用来接收二维数组第一行的地址的。
//parr 存储第一行的地址,[4] 表示每行有 4 列
void print(int(*parr)[4],int row,int col)
{
	int i = 0;
	int j = 0;

	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			//printf("%d ", parr[i][j]);
			printf("%d ", *(*(parr + i) + j));//这两种写法等价
		}
		putchar('\n');
	}
}

int main()
{
	int arr[4][4] = { {1,2,3,4},{2,2,3,4},{3,2,3,4},{4,2,3,4} };

	print(arr, 4, 4);//传第一行的地址过去

	return 0;
}

在这里插入图片描述

在这里插入图片描述

分析下面代码的的意思

int (*parr[10])[5];
  • parr 先和 [10] 结合,说明 parr 是一个具有 10 个元素的数组。
  • 将 parr[10] 拿掉之后,剩下了 int (*) [5],这是个数组指针,该指针指向的数组具有 5 个元素,每个元素为 int。
  • 所以,int (*parr[10])[5] 的意思就是,parr 是个拥有 10 个元素的数组,每个元素都是一个指向 int [5] 数组的数组指针。

在这里插入图片描述

Ⅳ 数组传参和指针传参

  • 很多时候,难免需要把(数组)或者(指针)作为参数传给函数,此时函数的参数又应该如何设计?

⒈一维数组传参

  • 下面代码的所有函数中,形参是否设计合理?
void test(int arr[])	//1:√
{}
void test(int arr[10])	//2:√
{}
void test(int* arr)		//3:√
{}
void test2(int* arr[20])//4:√
{}
void test2(int** arr)	//5:√
{}

int main()
{
	int arr[10] = { 0 };	//每个元素都是 int 类型的变量
	int* arr2[20] = { 0 };	//每个元素都是 int* 类型的变量

	test(arr);
	test2(arr2);

	return 0;
}
  1. 数组传参,形参的是可以写成数组形式的。
  2. 数组传参,作为形参的数组是可以写上数组大小的,反正也不会去用它。
  3. 数组传参的本质是传递数组首元素的地址,所以形参可以是指针类型。
  4. 数组传参,传了个指针数组给函数,自然可以用指针数组的形式接收。
  5. 传了个首元素的地址过去,arr2 的首元素是个 一级指针 ,一级指针的地址自然得用二级指针接收。

⒉二维数组传参

void test(int arr[3][5])	//1:√
{}
void test(int arr[][])		//2:×
{}
void test(int arr[][5])		//3:√
{}
void test(int* arr)			//4:×
{}
void test(int* arr[5])		//5:×
{}
void test(int(*arr)[5])		//6:√
{}
void test(int** arr)		//7:×
{}

int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}
  1. 传递二维数组过去,自然可以用二维数组的形式接收。
  2. 二维数组作为参数不能和正常的定义二位数组的形式一样,只能省略行,不能省略列。
  3. 二维数组作为形参,可以省略行数。
  4. 二维数组的首元素是第一行的地址,也就是传了个一维数组的地址给函数,不能用 int* 这种整形指针接收一个数组的地址。
  5. arr 是个指针数组,不能用来接收传过来的第一行的地址。
  6. arr 是个数组指针,可以用来接收传过来的二维数组第一行的地址。
  7. arr 是个二级指针,只能接收一级指针的地址,不能用来接收数组的地址。

⒊一级指针传参

  • 一级指针传参时,形参部分写成一级指针即可。
void print(int* p, int sz)//用一级指针接收传过来的数组首元素地,可行。
{
	int i = 0;

	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);

	print(p, sz);//一级指针p,传给函数

	return 0;
}

⒋二级指针传参

void test(int** ptr)	//ptr 是用来接收一级指针的地址的
{
	printf("num = %d\n", **ptr);
}

int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;

	test(pp);	//将 pp 里存着的 p 的地址传过去
	test(&p);	//直接将 p 的地址传过去

	return 0;
}

Ⅴ 函数指针

  • 指向函数的指针,称之为函数指针。

函数指针的定义格式

返回类型 (*指针变量名)(指向的函数的参数类型)

如何得到函数的地址?

在这里插入图片描述

  • &函数名就是函数的地址;
  • 函数名也是函数的地址。
  • 函数名 == &函数名 == 函数的地址

定义函数指针

int (*pf)(int,int) = &add;
int (*pf)(int,int) =  add;
//这两种形式都能将 add 的地址赋给函数指针 pf
  • pf 先和 * 结合,说明 pf 是个指针;
  • 第一个 int 表示 pf 指向的函数的返回类型是个 int;
  • (int,int) 表示 pf 指向的函数的形参为两个 int 类型。

函数指针的使用

  • 对函数指针解引用就相当于找到了原来的函数,
    • 如:*pf 等价于 add。
  • 所以也可以使用函数指针来调用函数。

在这里插入图片描述

Ⅵ 函数指针数组

⒈函数指针数组的介绍

函数指针数组的概念

  • 函数指针数组也是指针变量,是变量就可以存放到数组中区。
  • 存放同类型函数指针的数组称之为函数指针数组。

函数指针数组的定义

  • 将下面参数、返回类型都相同的 4 个函数的地址存放在数组中。
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(*pfarr[])(int, int) = { add,sub,mul,div };//pfarr 就是函数指针数组
  • 让 pfarr 与 [ ] 先结合确定 pfarr 为一个数组;
  • 将 pfarr[ ] 拿走之后,剩下的函数指针 int (*)(int,int) 就是数组的每个元素的类型。

在这里插入图片描述

函数指针数组的调用

  • 使用 pfarr[下标] 就能访问到数组中存储的函数;
  • 所以想要调用数组内部的函数就 数组名[下标] (参数) 即可。

在这里插入图片描述

⒉函数指针数组的用途

  • 函数指针数组可以应用于转移表

使用函数指针数组实现计算器

void menu(void)
{
	printf("|----------------------------------|\n");
	printf("|1.加法 2.减法 3.乘法 4.除法 0.退出|\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;
}

void calc(int (*pfarr[])(int, int), int input)
{
	int a = 0;
	int b = 0;

	printf("输入要计算的两个数\n");
	scanf("%d %d", &a, &b);
	printf("%d\n", pfarr[input](a, b));
}

int main()
{
	int i = 0;
	int input = 0;
	int(*pfarr[])(int, int) = { 0,add,sub,mul,div };
	//塞个 0 放到下标为 0 的位置,确保输入和实际函数的位置相同
	int sz = sizeof(pfarr) / sizeof(pfarr[0]);

	do
	{
		menu();
		printf("选择你的操作\n");
		scanf("%d", &input);

		if (input >= 1 && input < sz)
		{
			calc(pfarr, input);//根据输入的数调用数组对应位置的计算器功能
		}
		else if (0 == input)
		{
			printf("退出计算器\n");
			break;
		}
		else
		{
			printf("选择错误,请重试\n");
		}

	} while (input);

	return 0;
}
  • 使用函数指针数组来调用对应的计算器功能函数后,之后需要再往这个计算器内添加新功能,只需要完成对应的函数然后将这个函数塞进数组内即可。

Ⅶ 指向函数指针数组的指针

  • 可以用数组指针指向一个普通的数组;
  • 自然也可以使用一个函数指针数组指针来指向一个函数指针数组。

函数指针数组指针的定义

int main()
{
	int (*pfarr)(int,int) = {0,add,sub,mul,div};//函数指针数组
	int (*(*ppfarr)[5])(int,int) = &pfarr;		//指向 (函数指针数组) 的指针

	return 0;
}
  • ppfarr 先和 * 结合证明它是个指针,向右一看有个人 [5],说明 ppfarr 指向的是个指针;
  • 将 (*ppfarr)[5] 拿走,剩下的 int (*)(int,int) 是 ppfarr 这个指针所指向的数组的元素类型。

Ⅷ 回调函数

回调函数的概念

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

举个栗子

void calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	scanf("%d %d", &x, &y);
	printf("%d\n", pf(x, y));
}

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

int main()
{
	calc(add);

	return 0;
}
  • 将 add 函数的地址传给 calc 函数,在 calc 函数内部通过 pf 这个函数指针来调用 add 这个函数,这就叫回调函数。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值