前言:前言:开始之前先感谢一位大佬,清风~徐~来-CSDN博客,由于是时间久远,博主指针的系列忘的差不多了,所以有顺序部分借鉴了该播主的,同时也加入了博主自己的理解,有些地方如果解释的不到位,请翻看这位大佬的,感谢大家!!!!!!
目录
一,指针数组
二、指针数组和数组指针的区别
数组指针和指针数组的写法
数组指针的初始化
三、二维数组传参的本质
重点
二、函数指针
什么是函数指针变量呢?
函数指针变量的使用
两端有趣的代码:
1.( *( void ( * )( ) )0 ) ( );
2.void( *signle(int, void( * )(int) ) ) (int)
typedef重命名函数
四、函数指针数组
函数指针数组的实际应用:转移表
结尾祝福语:
一,指针数组
思考:我们上一系列已经知道什么是数组指针了,那么指针数组又是什么哪?
答案是:指针数组是一个数组是数组,储存的是地址(首元素),数组指针是数组还是指针哪?答案是--------指针变量
我们已经熟悉:
- 整形指针变量: int * pi; 存放的是整形变量的地址,能够指向整形数据的指针。
- 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
- 组指针:存放的是数组的地址,能够指向数组的指针变量
二、指针数组和数组指针的区别
数组指针和指针数组的写法
int*parr[10]//指针数组
解释:数组名先和[]结合,说明这是一个数组,数组中有10个元素,元素的类型是(int*)型,所以p2是一个数组,数组元素指针,叫做指针数组。
int(*p2)[10]//数组指针
解释:p2先和(*)结合说明p2是一个指针变量,然后指针指向的是一个大小为10个整形的数组,,所以p2是一个指针,叫做数组指针。
[]的优先级要高于*号,所以必须要叫()来提高优先级,确保p2与*结合
数组指针的初始化
数组指针是用来存放数组地址的,那怎么才能获得数组地址?这里就要用到&的符号,如果要存放数组地址,就得存放数组指针变量中
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;
return 0;
}
我们调试可以看到
我们调试也能看到 &arr 和 p 的类型是完全⼀致的。
三、二维数组传参的本质
我们将之前,先来回忆一下一维数组传参,一维数组传参为了避免开辟额外的空间,所以只需要传数组的首地址即可
我们再来看我们曾经写过的一段二维数组传参的一段代码:
void test(int a[3][5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
test(arr, 3, 5);
return 0;
}
这里实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
重点
- 二维数组在内存中是连续存储的
- 二维数组可以理解为一维数组的数组,二维数组的每一行都可以看成一个一维数组
- 二维数组名也是元素的首地址,这里的首地址是指第一行首元素,传过去的是第一行这个一维数组的地址,也就是arr[0]的地址。
- 第一行的一维数组的类型就是int[5],所以第一行的地址的类型就是int(*) [5]
二维数组传参,形参的部分可以写成数组形式,也可以写成指针形式,如下:
#include <stdio.h>
void test(int(*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));//等价于p[i][j]
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
test(arr, 3, 5);
return 0;
}
- p:数组首元素的地址,也就是一维数组arr[0]的地址。
- p+i:跳过 i 个 int[5] 这样的数组(p的类型是数组指针),指向arr[i],p+i 就是一维数组 arr[i] 的地址。
- *(p+i):访问一维数组arr[i],等价于一维数组arr[i],而 arr[i] 是数组名,又是数组首元素的地址,也就是 arr[i][0] 的地址。
- *(p + i) + j:由于 *(p+i)是 arr[i][0] 的地址,所以 +j 跳过 j 个整形(指向整形),也就是 arr[i][j] 的地址。
- *( *(p + i) + j):由于 *(p + i) + j 是 arr[i][j] 的地址,进行解引用操作,就是找到 arr[i][j]。
- 最终:*( *(p + i) + j) 等价于 arr[i][j]。
二、函数指针
什么是函数指针变量呢?
根据前面学习整型指针,数组指针的时候,类比一下,我们不难得出结论:
函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。那么函数是否有地址呢?我们做个测试:
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
函数的地址确实是有的,同时也验证了函数的地址也是可以用&调取出来的。同时我们观察发现,函数名和取出来的地址是一样的
函数指针变量的使用
如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针非常类似。如下:
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
//int a = 10;
//int* pa = &a;//整型指针变量
//int arr[5] = {0};
//int (*parr)[5] = &arr;//parr 是数组指针变量
//arr:数组首元素的地址 &arr:数组的地址
//&函数名和函数名都是函数的地址,没有区别
//printf("%p\n", &Add);
//printf("%p\n", Add);
//int(*pf3)(int, int) = Add;
//int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的
//int (*pf)(int,int) = &Add;//pf 函数指针变量,()不能省略
int (*pf)(int, int) = Add;//pf 函数指针变量
int ret1 = (*pf)(4, 5);
int ret2 = pf(4, 5);//pf等价于Add
printf("%d\n", ret1);
printf("%d\n", ret2);
int ret = Add(4, 5);
printf("%d\n", ret);
//int (*pf)(int x, int y) = &Add;//pf 函数指针变量
//int (*)(int,int) 函数指针类型
return 0;
}
- int (*pf)(int, int) = Add,*pf外的 () 不能省略。
- pf == (*pf) == Add == &Add。
两端有趣的代码:
1.( *( void ( * )( ) )0 ) ( );
2.void( *signle(int, void( * )(int) ) ) (int)
typedef重命名函数
typedef是用来重命名的,可以将复杂的名字简单化,规范化
比如我们命名了一个结构体叫做jinfsjajngijiasogjoiasjda(随便打的),我们每次调用都要写很长一段复杂的东西,但是有了typedef这个东西,我们可以将它重命名为js,对!就这两个字符,就可以表达这个结构体
typedef unsigned int uint;
//将unsigned int 重命名为uint
如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:
typedef int* ptr_t;
但是对于数组指针和函数指针稍微有点区别:
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
typedef int(*parr_t)[5];
函数指针类型的重命名也是⼀样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef void(*pfun_t)(int);//新的类型名必须在*的右边
那么要简化代码2,可以这样写:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
重点
给出typedef命名一个数组的例子:
#include<stdio.h> int main() { typedef int IntArray[5]//定义一个包含5个int类型元素的数组类型IntArray IntArray arr={1,2,3,4,5};// 使用 IntArray 声明一个数组 for(int i=0;i<5;i++) { printf("%d",arr[i]); } return 0; }
四、函数指针数组
定义:
int(*parr[3])();
解释:parr先和[]结合表明这是一个数组,数组的内容是什么,是int(*)()类型的函数指针
函数指针数组的实际应用:转移表
计算器的一般实现路径代码:
#define _CRT_SECURE_NO_WARNINGS 1
#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***********2:sub**\n");
printf("**3:mul***********4:div**\n");
printf("*********0:exit**********\n");
printf("*************************\n");
}
int main()
{
int x = 0;
int y = 0;
int input = 0;
int ret = 0;
do
{
menu();
printf("请输入:");
scanf("%d", &input);
switch (input)
{
case 0:
break;
case 1:
printf("请输入两个数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d+%d=%d\n", x, y, ret);
break;
case 2:
printf("请输入两个数:");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d-%d=%d\n", x, y, ret);
break;
case 3:
printf("请输入两个数:");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d*%d=%d\n", x, y, ret);
break;
case 4:
printf("请输入两个数:");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d/%d=%d\n", x, y, ret);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
用函数指针实现的:
#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***********2:sub**\n");
printf("**3:mul***********4:div**\n");
printf("*********0:exit**********\n");
printf("*************************\n");
}
int main()
{
int x = 0;
int y = 0;
int input = 0;
int ret = 0;
do
{
menu();
printf("请输入:");
scanf("%d", &input);
int(*arr[5])(int, int) = { 0,Add,Sub,Mul,Div };//转移表
if (input == 0)
{
break;
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个数:");
scanf("%d %d", &x, &y);
ret = arr[input](x, y);
printf("ret=%d\n", ret);
}
else
{
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
不仅仅可以实现加减乘除,还能实现按位与,或,异或,左移,右移等操作,只需在数组中追加函数地址即可,当然前提是将函数敲出来,这种就叫作转移表。
结尾祝福语:
指针系列三到这里就结束了,我们对指针的熟悉又近了一步,指针说是新手的噩梦,其实当你完全了解之后,指针也只不过是取经之路的一难而已,当我们取完经之后,我们会发现,关关难过关关过,斗罢艰险再出发!!!!!!!
最后:风会带来故事的种子,时间是指发芽,但也请不要忘记,旅途中你所看到的风景,人最不能忘的就是初心!!!!!
我们指针系列四再会!!!!!!!!!!!!!!!!!!!!!!!!