C语言进阶学习笔记二指针的进阶(重点必看+代码演示+练习)
Posted 大家好我叫张同学
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言进阶学习笔记二指针的进阶(重点必看+代码演示+练习)相关的知识,希望对你有一定的参考价值。
本篇文章的内容仍然为指针进阶的相关内容,继续上一篇文章的内容。【C语言进阶学习笔记】二、指针的进阶(1)(重点必看+代码图解+练习)
这是一篇干货满满的文章,希望有所收获~
5、函数指针
我们创建函数的时候,就会在内存中开辟一块空间,既然占用了内存空间,那就有对应的内存空间地址。
函数指针,顾名思义就是指向函数的指针。
注意:
& 函数名 和 函数名均表示函数的地址!
数组名 != &数组名
函数名 == &函数名
既然函数有地址,那么函数的地址该存放哪里呢?
思考:函数指针如何使用呢?
通过函数指针,我们可以找到函数,然后去调用这个函数。 函数指针是 &
函数名,而我们函数调用的时候可以直接使用函数名,那么这里通过函数指针调用函数也可以这样写:
既然这个地方的 * 可以省略,那么我们在使用的时候 * 可以用多个,也可以不要, * 号在这里就是一个摆设,这个地方放 * 是为了方便理解+
学习指针。
阅读两个有意思的代码:
//代码1
(*(void (*) ())0)();
//代码2
void(*signal(int, void(*)(int)))(int);
这两个代码均是书籍《C陷阱与缺陷》中提及的内容,推荐阅读这本书。
这两个代码怎么阅读和理解呢?
//代码1
(*(void (*) ())0)()
1.先将 void () ()理解清楚,这个是一个函数指针,指针指向的函数返回类型是void的。
2.再理解(void () ())0 我们之前的学习中学到过强制类型转换,需要将强制转换之后的类型用括号()括起来,这个地方就是将 0 强制类型转换成void() ()类型。 为什么要将0强制类型转换成void() ()类型呢? 原因:想要将0当做某个函数的地址
深入扩展:如果一个数字想要当作一个地址,直接使用这个数字肯定是不行的,而是要将这个数字转换成也给地址编号的类型。这也是代码1中为什么要将0强制类型转换的原因。
3.接着再看((void () ())0),对一个指针加*, 就是对其进行解引用操作,对函数指针解引用就是找到这个函数
4.((void () ())0)() ,((void () ())0)找到函数后,对其使用(), 就是调用函数,所以((void () ())0)(); 是一个函数调用。 整体理解下来就是:将0强制类型转换成一个函数指针void(*)
(),再通过对这个函数指针进行解引用操作,找到这个函数,对其进行调用!
//代码2
void(*signal(int, void(*)(int)))(int);
1.signal是一个函数声明
2.signal函数的参数有两个,第一个是int类型,第二个是函数指针,该函数指针指向的函数的参数是int类型,返回类型是void(void()(int))
3.signal的返回类型是一个函数指针,该函数指针指向的函数的参数是int类型,返回类型是void(void()(int))
这种形式看起来就比较复杂和难以理解,我们可以用typedef类型重定义对其进行简化:
在之前的学习中,我们使用过typedef来定义过无符号整型 typedef unsigned int u_int;
但是我们并没有学过指针类型如何进行类型重定义,比如说 void()(int) ,如果我们要将其进行重定义,可以写成:
**typedef void()(int) pfun_t; 这种形式吗?**
我们尝试将其放到编译器下,就会发现编译器报错,显然这种方式是行不通的!
思考:那么可以将这个类型重定义后的名称类似与定义函数指针一样放到(* )里面吗?也就是typedef void(*pfun_t)(int);
写成这种形式后,编译器没有在报错或者警告,说明这种方式是对的,实际上正确的书写方式也正是这样!
//代码2
void(*signal(int, void(*)(int)))(int);
//那么这个类型可以简化成:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
虽然将一行代码变成了两行,但是简化后的代码更便于阅读和理解。
typedef 在进行类型重定义的时候,如果是函数指针类型,那么名称需要放到* 旁边,也就是说不能写成这种形式:typedef void(*)(int) pfun_t;
正确的形式是:typedef void(*pfun_t)(int);
深入扩展:当函数的返回类型是一个函数指针的时候,函数名需要放到函数返回类型-- - 函数指针内部,而不是直接放到返回类型— 函数指针后面。
void(signal(int, void()(int)))(int);
6、函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,比如︰
int* arr[10];
//数组的每个元素是int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢 ?
int (*parr1[10])();
int* parr2[10]();
int (*)() parr3[10];
答案是:parr1 parr1先和[结合,说明parr1是数组。
数组的内容是什么呢 ?
是int(*)()类型的函数指针。
#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;
}
int main()
{
//指针数组
//int* arr[5];
//需要一个数组,这个数组可以存放4个函数的地址---函数指针的数组
int (*pa)(int, int) = Add;
int (*p[4])(int, int) = { Add,Sub,Mul,Div };//函数指针的数组
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\\n", (*p[i])(2, 3));
}
return 0;
}
练习:
char* my_strcpy(char* dest, const char* src);
练习要求:
1.写一个函数指针pf,能够指向my——strcpy
2.写一个函数指针数组 pfArr,能够存放4个my_strcpy函数的地址
答案:
1.char* (pf)(char, const char*)
2.char* (pfArr[4])(char, const char*)
函数指针数组的用途:
转移表—《C和指针》这本书提及
请看下面简易计算器的例子:
#include<stdio.h>
void cal_menu()
{
printf("*****************************\\n");
printf("***** 1.Add 2.Sub ****\\n");
printf("***** 3.Mul 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;//根据菜单提示输入操作符
int x = 0;
int y = 0;
do
{
cal_menu();
printf("请选择操作符:>>\\n");
scanf("%d", &input);
printf("请输入两个操作数:>>\\n");
scanf("%d%d", &x, &y);
switch (input)
{
case 1:
printf("%d\\n", Add(x, y));
break;
case 2:
printf("%d\\n", Sub(x, y));
break;
case 3:
printf("%d\\n", Mul(x, y));
break;
case 4:
printf("%d\\n", Div(x, y));
break;
case 0:
printf("退出!\\n");
break;
default:
printf("选择错误!\\n");
break;
}
} while (input);
return 0;
}
这里我们用函数调用的方式实现两个数之间的加、减、乘、除操作,但是当我们想要退出程序的时候,选择0并不能之间退出游戏,还需要进行输入两个操作数,这种方式明显不符合我们的预期,对其稍微进行改造以下:
#include<stdio.h>
void cal_menu()
{
printf("*****************************\\n");
printf("***** 1.Add 2.Sub ****\\n");
printf("***** 3.Mul 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;//根据菜单提示输入操作符
int x = 0;
int y = 0;
do
{
cal_menu();
printf("请选择操作符:>>\\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:>>\\n");
scanf("%d%d", &x, &y);
printf("%d\\n", Add(x, y));
break;
case 2:
printf("请输入两个操作数:>>\\n");
scanf("%d%d", &x, &y);
printf("%d\\n", Sub(x, y));
break;
case 3:
printf("请输入两个操作数:>>\\n");
scanf("%d%d", &x, &y);
printf("%d\\n", Mul(x, y));
break;
case 4:
printf("请输入两个操作数:>>\\n");
scanf("%d%d", &x, &y);
printf("%d\\n", Div(x, y));
break;
case 0:
printf("退出!\\n");
break;
default:
printf("选择错误!\\n");
break;
}
} while (input);
return 0;
}
这种改造方式虽然符合我们的预期效果,但是switch语句内部的代码太过冗长,而且后续如果我们想要添加新的功能时,switch语句内部还会继续不断变长,这种方式显然不够简洁和具备良好的扩展能力。
方法一:
下面我们通过函数指针数组的方式来实现吧!
#include<stdio.h>
void cal_menu()
{
printf("*****************************\\n");
printf("***** 1.Add 2.Sub ****\\n");
printf("***** 3.Mul 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;//根据菜单提示输入操作符
int x = 0;
int y = 0;
int(*pArr[])(int, int) = { 0,Add,Sub,Mul,Div };
do
{
cal_menu();
printf("请选择操作符:>>\\n");
scanf("%d", &input);
switch (input)
{
case 1:
case 2:
case 3:
case 4:
printf("请输入两个操作数:>>\\n");
scanf("%d%d", &x, &y);
printf("%d\\n", (*pArr[input])(x, y));
break;
case 0:
printf("退出!\\n");
break;
default:
printf("选择错误!\\n");
break;
}
} while (input);
return 0;
}
利用函数指针数组的方式,不仅可以简化我们的代码,也可以方面后面的扩展,比如说后面我们添加一个取模功能或一个异或功能,整个代码仅仅需要进行细微的调整即可满足我们的要求:
#include<stdio.h>
void cal_menu()
{
printf("*****************************\\n");
printf("***** 1.Add 2.Sub ****\\n");
printf("***** 3.Mul 4.Div ****\\n");
printf("***** 5.Mod 6.Xor ****\\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 Mod(int x, int y)
{
return x % y;
}
int Xor(int x, int y)
{
return x ^ y;
}
int main()
{
int input;//根据菜单提示输入操作符
int x = 0;
int y = 0;
int(*pArr[])(int, int) = { 0,Add,Sub,Mul,Div,Mod,Xor };
do
{
cal_menu();
printf("请选择操作符:>>\\n");
scanf("%d", &input);
switch (input)
{
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
printf("请输入两个操作数:>>\\n");
scanf("%d%d", &x, &y);
printf("%d\\n", (*pArr[input])(x, y));
break;
case 0:
printf("退出!\\n");
break;
default:
printf("选择错误!\\n");
break;
}
} while (input);
return 0;
}
这个地方我们仅仅调整了打印的菜单栏,函数指针数组初始化的值以及switch语句中加上case 5,case6。即可满足我们的要求,后续如果进行更多的功能扩展也可以按照这种方式,非常简单。
方法二:
通过函数指针来实现
printf("请输入两个操作数:>>\\n");
scanf("%d%d"C语言进阶学习笔记二指针的进阶(练习篇)