摘要:所以是数组指针,而是指针数组。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。当二维数组数组名传参,形参接收时,数组的行可以省略,列不能省略,如果省略了列,我们就无法知道当指针加减跳过几个字节。
大家对于指针恐怕都不陌生!
没学过C语言那也一定听过指针吧,指针是C
最强的优势,学不明白也就成了劣势!大家不必害怕,指针并没有那么恐怖,掌握了指针,让你的C语言更上一层楼!
bug郭和你一起将指针进阶学习一遍,一起加油!
可能有伙伴就要问了,咋一来就进阶指针!
不要慌问题不大,bug郭之前就写个一篇博客,介绍指针基础知识!
有兴趣的伙伴可以点击查看C语言指针,楼下大爷都能学会的小细节(和bug郭一起学C系列),建议收藏!
大家都复习完了指针基础吧,那我们就开始指针进阶的学习吧!
指针基础的一些概念:
4/8
个字节(32
位平台/64
位平台)。+-
本章重点
6. 字符指针
7. 数组指针
8. 指针数组
9. 数组传参和指针传参
10. 函数指针
11. 函数指针数组
12. 指向函数指针数组的指针
13. 回调函数
14. 指针和数组面试题的解析
字符指针顾名思义就是一个指针变量,指针指向的空间存放的是一个字符!
char*
字符指针
//基本用法int main(){ char ch = "c"; char* pc = &ch; *pc = "w"; return 0;}
这种基本的用法,bug就不介绍了,相信大家都会!
//进阶int main(){ char* pstr = "abcdef"; //pstr字符指针存了字符串,第一个字符(a)的地址 printf("%s",pstr); return 0;}
代码char* pstr = "abcdef";
特别容易让我们以为是把字符串abcedf
放到字符指针pstr
里了,但是本质是把字符串abcdef
首字符的地址放到了pstr
中。
我们可以知道通过字符指针pstr
我们可以找到字符串abedef
。
为啥我们不直接创建一个字符串变量,而要用这种方式,有何不同呢?
//测试#include int main(){ char str1[] = "hello world."; char str2[] = "hello world."; char *str3 = "hello world."; 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;}
输出结果
可以看到,字符数组str1
和str2
不相同,因为str1
和str2
是数组名,而数组名就是第一个数组的地址。str1
和str2
分别开辟了两个数组空间,只不过它们存放的内容一样而已!
而str3
和str4
它们都是指向的同一块空间,因为它们指向的是字符串常量hello world.
这里
str3
和str4
指向的是一个同一个常量字符串。C/C++
会把常量字符串存储到多带带的一个内存区域,
当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1
和str2
不同,str3
和str4
不同。
数组指针,我们首先要明确的就是,数组指针是指针而不是数组,它是指向数组的指针!
//判断数组指针int* arr1[3]; //指针数组int (*arr2)[3]; //数组指针
我们之前学过C语言操作符,建议收藏,我们知道操作符[]
的优先级高于*
。
所以arr2
是数组指针,而arr1
是指针数组。
int (arr2*)[3]
:arr2
是一个指针,指向的对象是整型数组,数组的元素个数为3
#include int main(){ int arr[4] = { 1,2,3,4 }; int(*parr)[4] = &arr; //数组指针存放数组arr的地址! return 0;}
大家肯定很少见代码这么写吧,数组指针很少这样使用!
我们已经知道了数组名就是,数组的首元素地址,而取地址数组名是数组的地址 。
那&arr
和arr
有啥区别呢?
#include int main(){ int arr[4] = { 1,2,3,4 }; int(*parr)[4] = &arr; printf("arr :%p/n",arr); printf("&arr:%p/n", &arr); return 0;}
居然都是第一个元素的地址!
但是我们知道,指针的类型决定了指针加减的步长!
#include int main(){ int arr[4] = { 1,2,3,4 }; int(*parr)[4] = &arr; printf("arr :%p/n",arr); printf("&arr:%p/n", &arr); printf("arr+1 :%p/n", arr+1); //整型指针加1,加一个整型类型大小 printf("&arr+1:%p/n", &arr+1);//数组指针加1,加一个数组类型大小 return 0;}
可以看到,数组指针和首元素地址,指针的类型不同
数组名
arr
:指针类型是整型 指针加减1
,步长为整型大小(4bit)
&
数组名:指针类型是数组 指针加减1
,步长为数组大小(16bit)
数组指针正确使用
#include void print_arr1(int arr[3][5], int row, int col){ int i = 0; for(i=0; i<row; i++) { for(j=0; j<col; j++) { printf("%d ", arr[i][j]); } printf("/n"); }}void print_arr2(int (*arr)[5], int row, int col){ int i = 0; for(i=0; i<row; i++) { for(j=0; j<col; j++) { printf("%d ", arr[i][j]); } printf("/n"); }}int main(){ int arr[3][5] = {1,2,3,4,5,6,7,8,9,10}; print_arr1(arr, 3, 5); //数组名arr,表示首元素的地址 //但是二维数组的首元素是二维数组的第一行 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址 //可以数组指针来接收 print_arr2(arr, 3, 5); return 0;}
学了数组指针,是不是发现有点懵了!
//捋一捋int *arr1[3]; //指针数组//数组个数是3,元素是int指针类型的数据int (*arr2)[3];//数组指针//指针,指向数组,且数组的类型是int类型,且元素个数为3int* (*arr3)[3]; //数组指针//指针,指向数组,数组元素是int*类型,且元素个数为3int (*arr4[3])[3]; //数组指针指针//指针,指向一个数组指针,数组指针的类型是int(*) [3] 指向数组且为为int类型,元素个数为3......
就捋到吧,再捋下去就更懵了,兄弟们慢慢学,你可以了的!
在写代码的时候难免要把数组或者指针传给函数,那函数的参数该如何设计呢?
#include void test(int arr[])//ok?{} //int arr[] 接收就是以int * arr形式接收,因为*arr等价与 arr[]void test(int arr[10])//ok?{} //int arr[10] 同上在形参中都是一个整型指针,形参中的数组长度无意义void test(int* arr)//ok?{} //整型指针接收数组名就是首元素地址也就是整型指针void test2(int* arr[20])//ok?{} //int* arr[20]等价于 int* arr[]等价于 int**arr 即二级指针 //而实参就是一个指向整型指针的指针也就是二级指针void test2(int** arr)//ok?{} //二级指针接收int main(){ int arr[10] = { 0 }; int* arr2[20] = { 0 }; test(arr); test2(arr2);}
void test(int arr[3][5])//ok?{} //二维数组传参二维数组接收void test(int arr[][])//ok?{} //error 不知道二维数组中一维数组中元素个数void test(int arr[][5])//ok?{} //可以省略行不能省略列//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。//这样才方便运算。void test(int *arr)//ok?{} //error 二维数组的数组名就是首元素地址,即一维数组的地址,// 也就是数组指针,应该用数组指针接收 void test(int* arr[5])//ok?{} //error,指针数组,实参是数组指针void test(int (*arr)[5])//ok?{} //实参为数组指针与形参类型相同void test(int **arr)//ok?{} //error int main(){ int arr[3][5] = {0}; test(arr);}
我们来总结一下!
- 二维数组的数组名就是首元素地址,而二维数组的元素就是一维数组,所以数组名的类型就是数组指针。
- 当二维数组数组名传参,形参接收时,数组的行可以省略,列不能省略,如果省略了列,我们就无法知道当指针加减跳过几个字节。
#include void print(int *p, int sz) //一级指针传参,一级指针接收{ int i = 0; for(i=0; i<sz; i++) { printf("%d/n", *(p+i)); }}//void print(int p[],int sz) //数组接收,也即一级指针接收,不提倡这样写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;}
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
//以int型指针为例void test(int* p){}int main(){ int x=0; int* px=&x; int arr[10]; test(&x);//整型地址 test(px);//一级指针 test(arr);//一维数组名,即首元素地址,int* return 0;}
void test(char** p ){}int main(){ char ch = "c"; char* pc = &ch; char* *ppc = &pc; char* arr[3]; test(&pc); //一级指针的地址,即二级指针 test(ppc); //二级指针 test(arr); //数组名,首元素地址,首元素为一级指针,所以为二级指针 return 0;}
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
其实和上面一样,你们可以思考一下!
首先看一段代码:
#include void test(){ printf("hehe/n");}int main(){ printf("%p/n", test); //函数名 就是函数地址 printf("%p/n", &test); //&函数名 也是函数地址 return 0;}
运行结果
那么如何将test()
函数指针保存起来呢?
void test(){ printf("hehe/n");}//下面pfun1和pfun2哪个有能力存放test函数的地址?void (*pfun1)(); //函数指针类型void *pfun2(); //函数,函数的返回值是void*
函数指针类型
指针都是有类型的
整型指针int*
数组指针int (*)[]
函数指针返回值 (*)(参数....)
#include int Add(int x, int y){ return x + y;}int main(){ int (*pf)(int, int) = Add; int sum = (*pf)(3, 5); //对函数指针解引用 printf("sum = %d", sum); sum = pf(3, 5); //因为 Add和&Add相同,即Add等价于 pf printf("sum = %d", sum); return 0;}
有趣的代码
//代码1(*(void (*)())0)(); //void (*)()为函数指针//(void (*)())0 将0强制类型抓换成函数指针的地址//(*(void (*)())0)() *地址,调用0地址处的这个函数//函数的返回值空,参数为空
//代码2void (*signal(int , void(*)(int)))(int);//void(*)(int) 函数指针,返回值void,参数int //void (*signal(int , void(*)(int)))(int)// signal是函数名 //返回值是void(*)(int)// 参数int 和函数指针 void(*)(int)//这是一个函数的声明
当我们看到代码2很难看懂这个代码!
可以简化吗?
void (*signal(int , void(*)(int)))(int);//既然这个函数的返回值类型是 void(*)(int) //那我们可以写成// void(*)(int) signal(int , void(*)(int));//但是这样会语法错误 error
函数指针类型重命名
简化
typedef void(*ptr_t) (int); //正确的类型重命名 ptr_t signal(int, ptr_t); //简化
上面的代码出自《C陷阱和缺陷》
有兴趣的伙伴可以尝试阅读!
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组
比如:
int* arr[10]; //整型指针数组
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10]])(); //int *parr2[10]();
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/122185.html
摘要:因为指针指向的是整个数组,所以它的类型是数组指针,所以我们在它的前面进行强制类型转换,把它转换为类型,然后再存放到指针变量内部。 前言 通过8道指针笔试题的解析,可以充分的复习到指针的相关知识,并且题目中会结合许多之前的相关知识,希望通过本篇文章,对大家所学的知识进行一个复习。 提示:以下...
摘要:结尾有关这四道经典的指针笔试题讲解就到此结束了,如果觉得文章对自己有所帮助,欢迎大家多多点赞收藏 ?前言 : 今天博主来讲解4道经典的指针笔试题,很多朋友没有深刻理...
摘要:故使用无具体类型,又称通用类型,即可以接收任意类型的指针,但是无法进行指针运算解引用,整数等。求指针所占字节而不是解引用访问权限大小。数组就是整个数组的大小,数组元素则是数组元素的大小,指针大小都为。 ...
摘要:之所以这样说不要认为学就不需要学语言,是因为一味的只学而没有语言等这些基础语言的支撑,是很难深入理解的很多东西的。 之所以这样说不要认为学PHP就不需要学C语言,是因为一味的只学PHP而没有C语言等这些基础语言的支撑,是很难深入理解PHP的很多东西的。 这样的例子其实很多,这里我就举这个例子吧:PHP的数组和C语言的数组的区别和联系。 学过C语言的朋友当然知道C语言里有数组; PHP里...
阅读 2222·2021-11-22 09:34
阅读 1334·2021-10-11 10:59
阅读 4425·2021-09-22 15:56
阅读 3269·2021-09-22 15:08
阅读 3400·2019-08-30 14:01
阅读 772·2019-08-30 11:16
阅读 1128·2019-08-26 13:51
阅读 2905·2019-08-26 13:43