目录
在上一节指针中我们讨论了一系列和指针相关的知识,包括二级指针,指针数组,但光靠这些知识还无法完全理解指针,因为指针还有函数指针这样一极其关键的类型,而我们上一章也没有具体聊如何存储一个字符串,以及如何访问它。好了下面就随着小赵的步伐一起来看看这些知识吧。
首先我们回顾一下我们的字符指针的定义。
int main()
{
char m = 'a';//定义一个字符变量
char* ch = &m;//字符指针变量,接收m的地址
*ch = 'w';//对ch解指针,对m里面的值进行修改
return 0;
}
我们在上一章是讲过这种单个字符的指针的变量,但如果我们存入字符指针的是一个字符串而不是字符结果又会是怎么样的呢?
int main()
{
const char* pstr = "abcdfe";
printf("%s", pstr);
return;
}
那这里的运行原理又是怎么样的呢?是不是我们就是把一个字符串放入了指针里呢?其实不是,因为指针也无法存储这种数据,它存储的毕竟是地址,那究竟是咋回事呢?其实这里我们的指针存放的是我们这个字符串的首地址,?可以看这样的一个代码
这样就证明了我们的我字符指针存储的其实是我们的字符串的首字母的地址。那么知道了这些知识我们就可以去看一道极其有意思的题目
int main()
{
char str1[] = "hello world";
char str2[] = "hello world";
const char* str3 = "hello world";
const char* str4 = "hello world";
if (str1 == str2)
{
printf("str1 and str2 are same\n");
}
else
{
printf("str1 and str2 are not same\n");
}
if (str3 == str4)
{
printf("str3 and str4 are same\n");
}
else
{
printf("str3 and str4 are not same\n");
}
return 0;
}
大家可以先自己猜测一下这个代码运行的结果,然后和小赵这边的答案比对一下。
?从这个结果我们可以惊讶的发现我们的str3和str4居然是一样的,这又是为什么呢?
首先我们来说一下为什么str1和str2不一样,因为两个数组开辟的地址不一样,两个存了都在数组里存了一样的代码而已。
接着就是我们的str3和str4,首先是他们接收的地址来自字符串,而这个字符串其实是在我们的内存中开辟一块地区的,这块地区是这个字符串的,那么我们的地址去访问这个字符串的时候其实访问的地方是一样的,就是都用了一个字符串的首字母的地址。
上面这个题目来自我们的《剑指offer》这样一本书,各位如果有兴趣可以去看看哦。
在上一章小赵和大家聊了我们的指针数组,即在数组中存储指针,那么我们是否存在指针去存储我我们的数组呢?答案是有的那么我们来看看它长什么样子吧。
在这里我们可以对比一下我们的指针数组
在这里我们发现当我们的*与ptr结合时候,他就是个指针,那么其(*ptr)[10]也就是个数组指针,而另一个呢,我们发现*是与int结合那么它表示的就是我们数组里面存的是指针类类型也就是指针数组啦。
在上一章,小赵与大家重点聊了,一维数组传参的本质,说一维数组传参其实传的是首元素的地址,那么我们二维数组根据这个推广其实大家也可以猜到其实传的就应该是二维数组首元素的地址,那么二维数组的首元素是什么呢?首先我要带大家回想的就是二维数组是由什么组成的,在前面小赵重点和各位聊过,二维数组其实就是由一个个的一维数组组合而成,那其里面的元素就是我们的一维数组,那我们传入的首元素的地址其实不就是我们的二维数组里面首个一维数组的地址吗?那知道了这个,我们就可以试试去用我们上述的数组指针去代替我们原本的代码。
原本的代码
#include <stdio.h>
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;
}
用指针后的
#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));
}
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;
}
?其实区别并不大,小赵在这里只是解释了一里面的本质,方便大家更进一步的的理解之前我们的代码,在使用方面各位可以自由选择。
在开始我们的函数指针变量之前啊,我们要先看看我们的函数是否存在地址,然后就是它是否和数组一样是名字就是他们的地址呢?只有知道了这两点,我们才能更好地开展我们下面的函数指针。
void test()
{
printf("hehe\n");
}
int main()
{
printf("test:%p\n", test);
printf("&test:%p\n", &test);
}
在这里我们看到我们两个地址是一样的那其实也就说明了1函数有地址,2函数名就是他们的地址。
其实函数指针的创建我们可以参考一下数组指针,我们发现在数组指针的创建过程中大体并没有大的变化只是变化了数组的名字,改为了指针的名字,那么我们的函数指针创建也是如此。
void test()
{
printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x, int y)
{
return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的
?
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));
return 0;
}
好了学习了上面的一系列知识后,我们下面给各位上盘大菜,就是我们的这样两段代码,也是小赵发在我们的博客上求助的两段代码。
(*(void (*)())0)();
先看第一个如何理解这样一段代码呢
小赵在这里为大家画个图,这个图是由里面向外看的。对这个代码做个较为清晰的解释。
第二段代码是
void (*signal(int , void(*)(int)))(int);
在这里小赵也是给大家画一个图方便大家理解
这两段代码出自我们的《C陷阱与缺陷》这本书,有兴趣的小伙伴可以看看,这本书可以提升大家对于C语言的理解和认知,让大家更好地学习和使用我们的C语言。?
看了上面两段代码,我们有时候可能会感觉我们这个返回类型好大一块,还要分开写,这样好像并不方便我们的C语言读者的阅读和使用那有木有办法让我们的代码缩短呢,让返回类型只在函数名前面也好啊,答案是有的,这里就要引入我们的typedef这个关键字,这个关键字是干嘛的呢?我们可以先上网搜搜看,小赵这里用的网站是C 库函数 – srand() | 菜鸟教程
?
我们可以看到它的主要作用其实就是改名,把我们原本的类型改掉 名字有了这个我们就可以优化前面的代码了。
?
当然还可以用来修改其他的如int?
注意一下修改名字的位置就可以了。
上一章我们讨论了指针数组,那这一章我们学了函数指针是否可以定义一个函数指针数组呢?,来看下面。
int (*parr[3])();//parr 先和 [] 结合,说明 parr1是数组,而数组的元素就是外面这一圈int (*)() 类型的函数指针
接着我们可以试着创建自己的函数指针数组?
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int main()
{
int(*p[2])(int x, int y) = { add, sub };
}
这里要注意的是我们函数指针数组定义好的元素类型是不能改的,不能有的是传入一个元素有的是传入两个,也不能一个有返回值一个没有,或者返回类型不行。?
好了,小赵下面带大家做一个转移表,,同时使用一下我们的函数指针数组
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输?操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输?操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输?操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输?操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
在这个转移表中我们其实就是奖我们的各个功能能够灵活调用有点像我们的计算机,那我们能否用我们的函数指针数组对其进行改造呢?答案是可以的。
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输?操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("ret = %d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("输?有误\n");
}
} while (input);
return 0;
}
我们会发现我们是用我们的函数指针数组让我们的代码更加美观优化,更加好阅读。?
好了小赵今天的分享就到这里了,如果大家有什么不明白的地方可以在小赵的下方留言哦,同时如果小赵有什么地方说得不对也希望得到大家的指点,谢谢各位家人们的支持。你们的支持是小赵创作的动力,加油。
如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持小赵,如有不足还请指点,小赵及时改正,感谢大家支持!!!