1.2数组指针的使用
1.2.1数组指针的错误示范
void func(int (\*arr)[10],int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);//err
printf("%d ", \*(arr + i));//err
}
}
int main()
{
int arr[10] = {1,2,3,4,5,67,8,9,10};
int len = sizeof(arr) / sizeof(arr[0]);
func(&arr,len);
return 0;
}
根据前面的逻辑我们知道&arr取出的是数组的地址,int (*arr)[10] 是个数组指针,本质上还是一个指针,访问的是整个数组的地址,而数组的地址 + 1会偏移40个字节,随后访问的都是随机值。
1.2.2数组指针的正确示范示范
void func(int (\*arr)[10],int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
//以下几种方式均可以
printf("%d ", arr[0][i]);
printf("%d ",(\*arr)[i]);
printf("%d ",\*(\*arr + i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,67,8,9,10};
int len = sizeof(arr) / sizeof(arr[0]);
func(&arr,len);
return 0;
}
解读:
arr [0][i] 有了第一个元素的下标向后访问i下标的元素,因为数组是连续存储的,有了第一个元素的下标,自然也能找到其他下标位置处的元素
(*arr)[i],数组指针是指向数组的地址,对指针解引用就能找到指针指向的内容,*arr表示的是数组的首元素地址,有了首元素的地址就可以向后偏移i位,其实等价于arr[i]
*(*arr + i),数组指针是指向数组的地址,对指针解引用就能找到指针指向的内容,*arr表示的是数组的首元素地址,对首元素的地址(数组首地址),偏移i个长度,会跳过4 * i个字节,再将偏移后的指针解引用找到指针指向的内容
希望以上的解释对大家的理解有所帮助,这也是博主对这些知识在一定程度上的理解,对这些语法的解释
接下来再看看另外一种用法
/\* 常规写法 \*/
void func(int arr[3][5],int row,int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
/\* 使用数组指针 \*/
void func(int (\*arr)[5],int row,int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
/\* 以下两种方式都可以 \*/
printf("%d ",arr[i][j]);
printf("%d ", \*((\*arr + 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};
int row = sizeof(arr) / sizeof(arr[0]);
int col = sizeof(arr[0]) / sizeof(arr[0][0]);
func(&arr,row,col);
return 0;
}
主要还是说一下数组指针的使用方式
首先我们来看这是35的二维数组
当使用数组指针的时候获取的是它的下标为【0】的一维数组的地址,数组指针里面存放的就是它的地址
假设现在要找处arr[1][4]的元素,下面我们直接看图
在这里是通过让数组指针指向下标【1】的位置处,有了数组的起始地址向后偏移,方便找到其他的元素,不过这种int()[5]类型的数组指针只是针对一维数组的,存放的是一维数组的地址,这一点请大家务必注意
有了上面知识的理解,再来看一组,你现在知道它们代表着什么吗?
1、int arr[5]; --》 表示的是一个数组,数组的每一个元素都是一个int
2、int *parr1[10]; --》表示的是指针数组,数组中的每个元素都是int *类型的指针变量
3、 int (*parr2)[10]; --》数组指针,本质上是一个指针,指向的是一个数组,数组的元素个数是10,数组的元素类型是int
4、int (*parr3[10])[5]; 重点来了,先看操作符的优先级,()的优先级是最高的,所以先看()里的内容,其次是[ ]优先级是第二,arr3先和[ ] 结合就是一个数组,数组里面存放着10个元素,每一个元素的类型是 int( * )[5],所以每一个元素都是一个数组指针,该数组指针指向的数组是【5】个元素,每一个元素是int类型,
四、数组参数、指针参数
1.1一维数组传参
#include <stdio.h>
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int \*arr)//ok
{}
void test2(int \*arr[20])//ok
{}
void test2(int \*\*arr)//ok
{}
int main()
{
int arr[10] = {0};
int \*arr2[20] = {0};
test(arr);
test2(arr2);
}
1.2二维数组传参
void test(int arr[3][5])//ok
{}
void test(int arr[][])//err
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int \*arr)//err
{}
void test(int\* arr[5])//err
{}
void test(int (\*arr)[5])//ok
{}
void test(int \*\*arr)//err
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
1.3一级指针传参
#include <stdio.h>
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]);
//一级指针p,传给函数
print(p, sz);
return 0; }
1.4二级指针传参
#include <stdio.h>
void test(int\*\* ptr) {
printf("num = %d\n", \*\*ptr);
}
int main()
{
int n = 10;
int\*p = &n;
int \*\*pp = &p;
int \*arr[10];//指针数组
test(pp);//ok,二级指针传参给,二级指针接收
test(&p);//ok,二级指针存放一级指针的地址
test(arr);//ok,指针数组传参,传递的是首元素的地址,元素类型是int,元素地址是int \*,二级指针能存放一级指针的地址
return 0; }
五、函数指针
我们都知道函数指针变量是用来存放函数的地址的,那么函数的地址长什么样呢
1.1函数的地址和数组的地址有哪些地方不太一样
&数组名 - 数组的地址
数组名 - 数组首元素的地址函数名 - 函数的地址
&函数名 - 函数的地址
1.2函数的地址又要存放到哪里去
void test()
{
printf("hello word\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (\*pfun1)();
void \*pfun2();
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void,函数指针的类型是int ( *)()。
1.3函数指针的使用
int Add(int x,int y)
{
return x + y;
}
int main()
{
int ret = Add(10,20);
printf("%d\n", ret);
int (\*pf)(int x, int y) = NULL;//定义函数指针变量
pf = Add;//函数指针变量指向函数地址
//pf = &Add; 可以写成这种方式
ret = pf(20,30);//有了函数的地址,就可以调用该函数
//ret = (\*pf)(20,30); 可以写成这种形式,便于初学者理解
//ret = (\*\*\*\*\*\*pf)(20,30); 多加几颗\*也没有影响
printf("%d\n",ret);
}
1.4阅读两段有趣的代码:
代码来自《C陷阱和缺陷》
代码一
先看代码一,0是整形,类型是int,(void () ())外面的一层小括号表示的是强制类型转换,而void()()表示的是一个类型,函数指针类型(void(*) ()) 0拼接起来的意思不就是将int型的0强制类型转换为
void( * ) ()类型的函数指针吗,有了了函数地址,再对指针解引用就能调用该函数,被调用的函数是一个无参,返回值类型为void的函数
以上代码确实符合语法,因为0地址处是被操作系统调用的,而程序员无法调用
总结:这是一次函数调用
代码二
这是一次函数声明,声明的函数名是signal,signal函数有两个参数,第一个是int类型,第二个是void( * )(int)的函数指针类型,signal函数的返回类型是void( * )(int)的函数指针类型
1.4.1代码二的简化写法
typedef void(\*pfunc\_t)(int);//给函数指针类型取别名为pfunc\_t
pfunc\_t signal(int,pfunc\_t);
这样子写起来是不是很简明,方便理解,熟练的使用typedef也可以解决这一类的问题
六、函数指针数组
函数指针数组的主体是一个数组,该数组存放的每一个元素是一个函数指针类型
1.1函数指针数组的初始化
int Add(int x,int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Div(int x, int y)
{
return x / y;
}
int Mul (int x, int y)
{
return x \* y;
}
int main()
{
int(\*pfArr[4])(int x, int y) = { Add ,Sub ,Mul ,Div};
return 0;
}
怎么理解函数指针数组的语法呢?
int(*pfArr[4])(int x, int y)
首先【】的优先级是最高的,所以会先和pfArr结合,这一看就是一个数组嘛,数组的元素个数是4,数组的每一个元素类型就是int ( * )(int,int)类型的,那么我们可以知道数组的每一个元素都是一个函数指针,
这种初始化方式结合之前所学的函数指针和指针数组的知识相信屏幕前的你可以看懂,没错就是一个数组里面存放着的每一个元素都是一个函数指针,数组的元素个数是4,数组的每一个元素类型是int(*)(int,int)
1.2运用之前的知识
假设现在要实现一个简单的计算器程序
先按常规的写法实现一个计算器
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;
do
{
int x = 0;
int y = 0;
int ret = 0;
Menu();
printf("请输入你的选择\n");
scanf("%d",&input);
switch (input)
{
case 0:
printf("退出计算器\n");
break;
case 1:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = Add(x,y);
printf("ret = %d\n",ret);
break;
case 2:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("ret = %d\n",ret);
break;
case 3:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("ret = %d\n",ret);
break;
case 4:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("ret = %d\n",ret);
break;
default:
printf("输入错误请重新输入\n");
break;
}
} while (input);
return 0;
}
简化版本
int Add(int x,int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Div(int x, int y)
{
return x / y;
}
int Mul (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;
do
{
int x = 0;
int y = 0;
int ret = 0;
Menu();
printf("请输入你的选择\n");
scanf("%d",&input);
int(\*pfArr[5])(int x, int y) = { 0,Add ,Sub ,Mul ,Div };
if (!input)
{
printf("退出计算器\n");
break;
}
else if (input >= 1 && input < 5)
{
printf("请输入两个操作数\n");
scanf("%d %d",&x,&y);
ret = pfArr[input](x, y);
printf("ret = %d\n",ret);
}
else
{
printf("输入错误\n");
}
} while (input);
return 0;
}
相比于之前的常规写法,这样的组织代码的方式,只让相同功能的代码只保存一份
可以看到函数指针数组的使用极大的降低了代码的冗余,而且可扩展性也能有所提升,将来要实现其他的功能,比如 >> 、<<、^、&=、这些计算都是可以的,只需要在函数指针数组初始化的时候再添加一个函数进去,再把else if分走语句的条件判断范围再给扩大那就ok,这种函数指针数组的使用方法在专业术语上叫做转移表
1.3通过回调函数的方式实现另外一种方案
1.4何为回调函数呢?
回调函数就是一个通过函数指针调用的函数。.
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。.
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
int Add(int x,int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Div(int x, int y)
{
return x / y;
}
int Mul (int x, int y)
{
**一、Python所有方向的学习路线**
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。
![img](https://img-blog.csdnimg.cn/1d40facda2b84990b8e1743f5487d455.png)
![img](https://img-blog.csdnimg.cn/0fc11d4a31bd431dbf124f67f1749046.png)
**二、Python必备开发工具**
工具都帮大家整理好了,安装就可直接上手!![img](https://img-blog.csdnimg.cn/ff266f529c6a46c4bc28e5f895dec647.gif#pic_center)
**三、最新Python学习笔记**
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
![img](https://img-blog.csdnimg.cn/6d414e9f494742db8bcc3fa312200539.png)
**四、Python视频合集**
观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
![img](https://img-blog.csdnimg.cn/a806d9b941c645858c61d161aec43789.png)
**五、实战案例**
纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。![img](https://img-blog.csdnimg.cn/a353983317b14d3c8856824a0d6186c1.png)
**六、面试宝典**
![在这里插入图片描述](https://img-blog.csdnimg.cn/97c454a3e5b4439b8600b50011cc8fe4.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/111f5462e7df433b981dc2430bb9ad39.png)
###### **简历模板**![在这里插入图片描述](https://img-blog.csdnimg.cn/646863996ac44da8af500c049bb72fbd.png#pic_center)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化学习资料的朋友,可以戳这里无偿获取](https://bbs.csdn.net/topics/618317507)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**