大家好,我是枫晨~,很抱歉这篇文章距离前篇这间隔了很久的时间,这段时间内我对指针反反复复的总结和思考,声怕自己理解不到位写出的文章也是没有条理,然后就是开启了新的系列大厂典例,会带着大家以复习知识点的方式刷一刷以往大厂或者常见的一些题型。
注:这篇文章全面分析C语言中指针重难点,并且是基于前篇基础上完成的,如果你指针进阶(前篇)还没有接触,请看完前篇再返回这里。
写代码调用函数时会将**【数组】或者【指针】**或者一个变量传递给函数,那函数的参数该如何设计呢?
//代码1
int main()
{
int arr[10]={0};
int *arr2[20]={0};
test(arr);
test2(arr2);
return 0;
}
//请问如何设计test和test2函数的参数?
①test函数参数形参设计
Ⅰ.参数写成数组形式
void test(int arr[10])
void test(int arr[]);
void test(int arr[100])
以上函数参数均为正确:
第一种的写法直观,一眼就可以分辨出从是一个怎样的数组传递给test函数;
第二种写法比较直观,可以告诉别人main函数传了一个数组过来;
第三种写法只能说是正确,但是绝对不要去这么写,因为有可能误导他人;
有人就问了,为什么三种写法都正确?耐心看完Ⅱ后给你解答
Ⅱ.形参写成指针
void test(int *p)
为什么参数可以写成指针的形式呢?,在主函数中,test(arr) 其中的arr代表的是数组首元素的地址,所以在函数的形参部分我们可以使用指针来接收这个数组的地址。在之前的文章中我们也有反复强调过这个。
既然test(arr)中arr是一个地址,那为什么可以写成Ⅰ的那三种形式呢?
原因在于设计c语言的设计者为了人们在学习时可以更加好的理解指针,更容易上手,减少学习成本,从函数中传递一个数组,那函数的形参写成一个数组不是让人一看就懂么,所以函数的形参也就可以写成一个数组的形式,更好的让学习者理解,但!本质上应该是应该指针来接受这个数组的地址!
②test2函数参数形参设计
Ⅰ.参数写成数组形式
void test2(int *arr2[20])
void test2(int *arr2[])
void test2(int *arr2[30])
以上函数参数均为正确:
第一种的写法直观,一眼就可以分辨出从是一个怎样的数组传递给test函数;
第二种写法比较直观,可以告诉别人main函数传了一个数组过来;
第三种写法只能说是正确,但是绝对不要去这么写,因为有可能误导他人;
Ⅱ.形参写成指针
void test2(int **parr2)
对于形参写成指针,我们首先要分析传递过来的数组是一个什么样的类型,从能写成函数参数的指针类型.
通过图片分析,int *arr[20]数组中存储的都是一级指针指针类型的数据,所以在test2函数参数中,我们要使用一个二级指针去接受一级指针的地址;
//代码2
int main()
{
int arr[3][5]={0};
test(arr);
}
//请问如何设计test函数的参数?
Ⅰ.形参写成数组形式
void test(int arr[3][5])
void test(int arr[][5])
以上函数参数均为正确:
第一种的写法直观,一眼就可以分辨出从是一个怎样的几行几列的二维数组传递给test函数;
第二种写法比较直观,可以告诉别人main函数传了一个5列的二维数组数组过来;
绝对不允许写成void test(int arr[][])
这样是绝对错误的!!!,二维数组只能省略行数,但是无论什么情况都不能省略列数!
Ⅱ.形参写成指针
void test(int (*pa)[5])
函数参数写成数组指针形式接受,具体解析在**指针进阶(前篇)**已经特别详细的分析过。
具体地址–》指针进阶(前篇)—》实战运用(②数组指针在二维数组中的应用)
总结:test在传递数组名字的时候,传递的是数组名的地址,但是为了初学者和程序员更加清晰的看程序传入的是什么,因此c语言设计者允许在这样的写法;对初学者的使用更加友好
//Ⅰ、已知主函数实参,求函数形参
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,0};
int *p = arr;
test(p);//p是一级指针
}
//如何设计test函数的参数?
//test函数参数设计
void test(int* ptr)
{
int i =0;
//打印arr数组内容
for(i=0;i<10;i++)
{
printf("%d ",*(ptr+i));
}
}
如果已知函数参数,可以传递什么给test函数?
//Ⅱ、已知函数形参,求主函数实参
void test(int *p)
{
//....
}
//解答:
int main()
{
test( )//( )括号可以填哪些呢?
//1.变量地址和一级指针名
int a = 0;
test(&a);
int* pa = &a;
test(pa);
return 0;
}
//Ⅰ、已知主函数实参,求函数形参
int main
{
char a = 'w';
char *pa = &a;
char **ppa = &pa;//ppa就是一个二级指针
test(ppa);
return 0;
}
//如何设计test函数的参数?
//test函数参数设计
void test(int** ppa)
{
//....
}
如果已知函数参数,可以传递什么给test函数?
Ⅱ、已知函数形参,求主函数实参
void test(int **ppa)
{
//....
}
//解答:
int main()
{
//一级指针地址
int a = 0;
int *pa = &a;
test(&pa);
//二级指针
int *ppa = &pa;
test(ppa);
//指针数组
int *parr[10];
test(parr);
return 0;
}
总结:
如果一个函数的参数是二级指针,那实参可以是什么?
1.一级指针地址;
2.二级指针
3.指针数组
函数指针:指向函数的指针
int Add(int x,int y)
{
return x+y;
}
int main()
{
printf("%p\n",&Add);//利用&取出函数的地址
printf("%p\n",Add);//如果不用&也可以取得Add函数的地址
}
总结:&Add和Add都可以取得Add函数的地址,意义是相同的
int Add(int x,int y)
{
return x+y;
}
int main()
{
//先回顾数组指针
int arr[10];
int (*parr)[10] = &arr;
//函数指针和数组指针相似
int (*pAdd)(int,int) = &Add;
return 0;
}
int Add(int x,int y)
{
return x+y;
}
int main()
{
int (*pAdd)(int,int) = &Add;
int ret = 0;
//正常调用函数
ret = Add(2,3);
printf("%d\n",ret);
//使用函数指针
ret = (*pAdd)(2,3);
printf("%d\n",ret);
return 0;
}
刚刚不是说Add也代表函数的地址么?那pAdd也代表的Add函数地址,为什么Add不需要解应用,但是pAdd需要,如果不解应用可以使用吗?
验证:
int Add(int x,int y)
{
return x+y;
}
int main()
{
int (*pAdd)(int,int) = &Add;
int ret = 0;
//解引用使用函数指针
ret = (*pAdd)(2,3);
printf("%d\n",ret);
//直接使用函数指针
ret = pAdd(2,3);
printf("%d\n",ret);
return 0;
}
//错误的使用方法
ret = *pAdd(2,3);//err,这样的用法是错误的!
总结:
使用函数指针,既可以加*进行解应用,也可以直接使用函数指针调用函数
其中 * 是否使用对函数指针调用函数无太大的影响
但是如果使用了 * 必须带上括号(*pAdd)(2,3)
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
没错,这两个代码就是之前在blink里面的那俩题,现在就带你们手把手分析~
话不多说,直接上写法:
int Add(int x,int y)
{
return x+y;
}
int main()
{
//首先看看指针数组
int *arr[20];
//函数指针数组
int (*pf[4])(int,int);
//1.pf先与[]结合,代表这是一个数组;
//2.去掉pf[4],剩余int (*)(int,int)为数组的类型--函数指针类型
return 0;
}
Ⅰ.定义加减乘除函数
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 input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出程序\n");
break;
case 1:
ret = Add(x, y);
printf("输入两个数:>");
scanf("%d %d", &x, &y);
printf("%d\n", ret);
break;
case 2:
ret = Sub(x, y);
printf("输入两个数:>");
scanf("%d %d", &x, &y);
printf("%d\n", ret);
break;
case 3:
ret = Mul(x, y);
printf("输入两个数:>");
scanf("%d %d", &x, &y);
printf("%d\n", ret);
break;
case 4:
ret = Div(x, y);
printf("输入两个数:>");
scanf("%d %d", &x, &y);
printf("%d\n", ret);
break;
default:
printf("输入错误,重新输入:>");
break;
}
} while (input);
return 0;
虽然实现了,但是太过于冗余了这样的写法,这时我们可以应用函数指针数组;
Ⅲ.利用函数指针数组简化
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 input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
//为了下标更好的对于菜单内容,所以下标为0的位置放置了0
int (*pfarr[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 = pfarr[input](x, y);
printf("%d\n",ret);
}
else
{
printf("重新输入\n");
}
} while (input);
return 0;
}
int Add(int x, int y)
{
return x + y;
}
int main()
{
//函数指针
int (*p)(int,int) = &Add;
//函数指针数组
int (*pfarr[4])(int,int);
//指向函数指针数组的指针
int (*(*p3)[4])(int,int) = &pfarr;
return 0;
}
今天的内容特别丰富,明天紧接着还有指针进阶的应用部分,非常抱歉将近5天没有发文章,这段时间我没有停止脚步,仍然在学习新知识,对于指针这部分,为了文章能够写的更加清晰,我特此花费许多时间去整理大纲!
谢谢大家的支持,指针知识部分的介绍还剩下最后一部分-回调函数-也将在近期更新完毕!
惯例:图片一张,激起你的斗志!!!