指针各类应用及大厂面试真题解析
文章目录
前言
初学者或普通高校所学习的指针大多浅显,即明白指针是地址,通过解引用操作可以操作地址内容,往往仅限于简单变量与数组,但要对指针有明确进一步的认识提高,其中有许多知识我们尚未涉足。本文将对指针进行简单系统的概括总结,且附上数道面试真题,帮助大家理解与掌握。干货满满,求大家支持一下。
一、我们目前掌握的指针
1.字符指针
指针类型为char*
使用方法
intmain(){char ch ='w';char* pc =&ch;*pc ='w';return0;}
另一种
int main()
{
const char* ps = "hello";
printf("%s\n", ps);
return 0;
}
运行结果
这里实际上是讲‘hello’首元素地址存入了指针变量ps中。
以%s字符串打印即可通过地址找到首元素向后寻址打印字符串数组
相关面试题
intmain(){char str1[]="hello";char str2[]="hello";constchar* str3 ="hello";constchar* str4 ="hello";if(str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if(str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return0;}
str1 与str2 是数组名,数组名表示数组首元素地址但str1与str2是两个不同的数组,只不过是其内所有元素相同,开辟的是不同的地址空间。故其二者不相同
str3与str4虽然是不同变量名,但都指向了‘hello’的字符常量,C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。故其二者相同
拓展:const修饰指针
const修饰指针无非有三种情况
const在*左边
const int* pi;
int const* pi;
//这种类型讲*pi即指针变量常定义,*pi即地址所指向的内容不能改变,但内容所指向的地址可以改变
const在*右边
int * const h;
//类别上文,地址h不可改变,但h解引用*h可改变
const在*左右都有
const int * const k;//两者等价
int const * const k;
2.数组指针
定义方法
int (*p)[10]//p是一个地址,地址指向有10个元素的整形数组
//指针数组
int *p[10]//p中有十个元素类型为int*
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合
顾名思义,指向数组的指针,我们都知道,数组名是数组首元素地址,数组指针也固然指向数组首元素。那数组名与数组指针有什么区别呢
注意:&数组名取出的是整个数组的地址,与数组首元素不同,数组名+1前进一个元素,但&数组名+1跳过整个数组
指针数组常用于二维数组
voidprint_arr(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]);//arr[i] = *(arr+1) arr[i][j] = *(*(arr+i)+j)}printf("\n");}}intmain(){int arr[3][5]={1,2,3,4,5,6,7,8,9,10};//数组名arr,表示首元素的地址//但是二维数组的首元素是二维数组的第一行//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址//可以数组指针来接收print_arr(arr,3,5);return0;}
数组传参
一维数组
voidtest(int arr[]){}voidtest(int arr[10])//为方便初学者学习,设计时可以这样传入{}voidtest(int* arr)//数组名本为首元素地址,用指针接收合乎情理{}voidtest2(int* arr[20]){}voidtest2(int** arr)//arr2本为指针,指针用二级指针接收{}intmain(){int arr[10]={0};int* arr2[20]={0};test(arr);test2(arr2);}
二维数组
voidtest(int arr[3][5]){}voidtest(int arr[][5])//这里不同,其他与一维数组可以类比{}//二维数组传参,函数形参的设计只能省略第一个[]的数字。//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。//这样才方便运算。voidtest(int* arr){}voidtest(int* arr[5]){}voidtest(int(*arr)[5])//第一行指针,可以用来模拟二维数组{}voidtest(int** arr){}intmain(){int arr[3][5]={0};test(arr);}
一级指针
#include<stdio.h>voidprint(int* p,int sz){int i =0;for(i =0; i < sz; i++){printf("%d\n",*(p + i));}}intmain(){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);return0;}
函数指针
void (*p)(int,float)//可与数组指针类比p先与*结合,后有函数调用操作符,即p作为一个指针指向函数,函数类型void(int,float)
与函数指针相关有许多套娃操作,不在本文重点,随手带过
函数指针数组
int (*p[10])();
数组中存放着10个函数指针
函数指针数组所对应的指针
void(*(*p)[5])(int)
重点
二、指针和数组笔试题解析
数组名的两个例外
数组名是数组首元素地址,单独&数组名和sizeof(数组名)是取出的是数组的地址
铺垫
1.一维数组
intmain(){int a[]={1,2,3,4};printf("%d\n",sizeof(a));//数组名a单独放在sizeof内部,计算的整个数组的大小,单位是字节,4*4 = 16printf("%d\n",sizeof(a +0));//a表示的首元素的地址,a+0还是数组首元素的地址,是地址大小4/8printf("%d\n",sizeof(*a));//a表示的首元素的地址,*a就是对首元素的地址的解引用,就是首元素,大小是4个字节printf("%d\n",sizeof(a +1));//a表示的首元素的地址,a+1是第二个元素的地址,是地址,大小就4/8个字节printf("%d\n",sizeof(a[1]));//a[1]是数组的第二个元素,大小是4个字节printf("%d\n",sizeof(&a));//&a 表示是数组的地址,数组的地址也是地址,地址大小就是4/8字节printf("%d\n",sizeof(*&a));//可以理解为*和&抵消效果,*&a相当于a,sizeof(a)是16 &a -> int(*)[4] &a是数组的地址,它的类型是int(*)[4]数组指针,如果解引用,访问的就是4个int的数组,大小是16个字节printf("%d\n",sizeof(&a +1));//&a是数组的地址,&a+1 跳过整个数组后的地址,是地址就是4/8printf("%d\n",sizeof(&a[0]));//&a[0]取出数组第一个元素的地址,是地址就是4/8printf("%d\n",sizeof(&a[0]+1));//&a[0]+1就是第二个元素的地址,是地址大小就是4/8个字节return0;}
sizeof只关注占用空间的大小,单位是字节
/sizeof不关注类型
sizeof是操作符
strlen
#include<string.h>//strlen关注的字符串中\0的为止,计算的是\0之前出现了多少个字符//strlen只针对字符串//strlen是库函数intmain(){char arr[]={'a','b','c','d','e','f'};printf("%d\n",strlen(arr));//arr是首元素的地址,但是arr数组中没有\0,计算的时候就不知道什么时候停止,结果是:随机值printf("%d\n",strlen(arr +0));//arr是首元素的地址,arr+0还是首元素的地址,结果是:随机值printf("%d\n",strlen(*arr));//err,strlen需要的是一个地址,从这个地址开始向后找字符,直到\0,//统计字符的个数。但是*arr是数组的首元素,也就是'a',这是传给strlen的就是'a'的ascii码值97,//strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突printf("%d\n",strlen(arr[1]));//err 和上一个一样,内存访问冲突printf("%d\n",strlen(&arr));//&arr是arr数组的地址,虽然类型和strlen的参数类型有所差异,//但是传参过去后,还是从第一个字符的位置向后数字符,结果还是随机值。printf("%d\n",strlen(&arr +1));//随机值printf("%d\n",strlen(&arr[0]+1));//随机值printf("%d\n",sizeof(arr));//arr作为数组名单独放在sizeof内部,计算的整个数组的大小,单位是字节,6printf("%d\n",sizeof(arr +0));//arr就是首元素的地址,arr+0还是首元素的地址,地址大小就是4/8printf("%d\n",sizeof(*arr));//arr就是首元素的地址,*arr就是首元素,是一个字符,大小是一个字节,1printf("%d\n",sizeof(arr[1]));//arr[1]就是数组的第二个元素,是一个字符,大小是1个字节printf("%d\n",sizeof(&arr));//&arr取出的是数组的地址,数组的地址也是地址,地址就是4/8个字节printf("%d\n",sizeof(&arr +1));//&arr取出的是数组的地址,&arr+1,跳过了整个数组,&arr+1还是地址,地址就是4/8个字节printf("%d\n",sizeof(&arr[0]+1));//&arr[0]是第一个元素的地址,&arr[0]+1就是第二个元素的地址,地址就是4/8个字节return0;}
#include<stdio.h>#include<string.h>intmain(){char arr[]="abcdef";printf("%d\n",strlen(arr));//"abcdef"后隐藏\0,strlen计算\0前的字符个数,结果是6printf("%d\n",strlen(arr +0));//arr是字符串首元素地址,arr+0仍然是,结果是+printf("%d\n",strlen(*arr));//arr是数组首元素地址,*arr即是‘a’,a的ASCII码为97,strlen会以97作为地址向后寻址\0,会形成内存访问冲突printf("%d\n",strlen(arr[1]));//arr[1]同上,形成内存访问冲突printf("%d\n",strlen(&arr));//&arr取出整个数组地址,数据仍与数组首元素地址相同,但传入strlen后仍然以字符为单位向后寻找\0;还是随机值printf("%d\n",strlen(&arr +1));//传入了数组\0后的地址,向后寻找\0,还是随机值printf("%d\n",strlen(&arr[0]+1));//传入了b的地址,5printf("%d\n",sizeof(arr));//sizeof中arr单独存在表示整个数组,6printf("%d\n",sizeof(arr +0));//arr+0是数组首元素,1printf("%d\n",sizeof(*arr));//arr是数组首元素地址,*arr即是a,1printf("%d\n",sizeof(arr[1]));//是‘b’,1printf("%d\n",sizeof(&arr));//&arr即使是整个数组地址,但还是一个指针变量,4/8printf("%d\n",sizeof(&arr +1));//指针变量,4/8printf("%d\n",sizeof(&arr[0]+1));//指针变量,4/8return0;}
intmain(){char* p ="abcdef";printf("%d\n",strlen(p));//p中存放的是'a'的地址,strlen(p)就是从'a'的位置向后求字符串的长度,长度是6printf("%d\n",strlen(p +1));//p+1是'b'的地址,从b的位置开始求字符串长度是5printf("%d\n",strlen(*p));//'a',errprintf("%d\n",strlen(p[0]));//errprintf("%d\n",strlen(&p));//取出另一个地址,随机值printf("%d\n",strlen(&p +1));//同上,随机值printf("%d\n",strlen(&p[0]+1));//p[0] -> *(p+0) -> *p ->'a' ,&p[0]就是首字符的地址,&p[0]+1就是第二个字符的地址//从第2 字符的位置向后数字符串,长度是5printf("%d\n",sizeof(p));//p是一个指针变量,sizeof(p)计算的就是指针变量的大小,4 / 8个字节printf("%d\n",sizeof(p +1));//p是指针变量,是存放地址的,p+1也是地址,地址大小就是4/8字节printf("%d\n",sizeof(*p));//*p访问的是1个字节printf("%d\n",sizeof(p[0]));//p[0]--> *(p+0) -> *p 1个字节printf("%d\n",sizeof(&p));//&p也是地址,是地址就是4/8字节,&p是二级指针printf("%d\n",sizeof(&p +1));//&p是地址, + 1后还是地址,是地址就是4 / 8字节//&p + 1,是p的地址+1,在内存中跳过p变量后的地址printf("%d\n",sizeof(&p[0]+1));//p[0]就是a,&p[0]就是a的地址,&p[0]+1就是b的地址,//是地址就是4/8字节return0;}
2.二维数组
intmain(){int a[3][4]={0};printf("%d\n",sizeof(a));//数组名单独放在sizeof内部,计算的是整个数组的大小,48printf("%d\n",sizeof(a[0][0]));//一个元素的大小,是int类型,4printf("%d\n",sizeof(a[0]));//a[0]表示第一行的数组名,a[0]作为数组名单独放在sizeof内部,计算的是第一行的大小。,16printf("%d\n",sizeof(a[0]+1));//a[0]作为数组第一行,且单独存在,即第一行第二个元素的地址,是指针变量4/8printf("%d\n",sizeof(*(a[0]+1)));//接上,是一个元素,4printf("%d\n",sizeof(a +1));//第二行地址,指针变量 4/8printf("%d\n",sizeof(*(a +1)));//接上 16printf("%d\n",sizeof(&a[0]+1));//&一整行,+1即第二行地址 4/8printf("%d\n",sizeof(*(&a[0]+1)));//接上 是一行 16printf("%d\n",sizeof(*a));//取到了第一行,与sizeof结合。16printf("%d\n",sizeof(a[3]));//感觉越界,但没关系,16return0;}
三,面试真题
intmain(){int a[5]={1,2,3,4,5};int*ptr =(int*)(&a +1);//取到了5后的地址并强制类型转化为int*printf("%d,%d",*(a +1),*(ptr -1));//*(ptr-1)向前寻回了5return0;}
2.
structTest{int Num;char*pcName;short sDate;char cha[2];short sBa[4];}*p;//假设p 的值为0x100000。 如下表表达式的值分别为多少?//已知,结构体Test类型的变量大小是20个字节intmain(){printf("%p\n", p +0x1);//结构体类型加一跳过20字节,转化16进制为14printf("%p\n",(unsignedlong)p +0x1);//转换为无符号的长整形,即加了数字1用%p的形式打印printf("%p\n",(unsignedint*)p +0x1);//强制类型转换为无符号整形指针,+1跳过4,已%p形式打印return0;}
3.
intmain(){int a[4]={1,2,3,4};int*ptr1 =(int*)(&a +1);//取到4后地址并强制类型转化为int*int*ptr2 =(int*)((int)a +1);//a是数组首元素地址,转化为int类型,如图。printf("%x,%x", ptr1[-1],*ptr2);//都以16进制打印return0;}
以vs2019小端字节序为例
讲地址转化为int类型,+1就是地址+1,数字1,即一个字节,又转化为int*,如图所示,又为小端排序,低位放在低地址,高位放在高地址,02是高位
02000000
4.
#include<stdio.h>intmain(){int a[3][2]={(0,1),(2,3),(4,5)};//大坑,坑,是括号不是大括号,是逗号表达式!!//实际上 int a[3][2] = {1,3,5};int*p;
p = a[0];//第一行printf("%d", p[0]);//首元素return0;}
5.
intmain(){int a[5][5];int(*p)[4];//注意,只有[4],草稿纸画图建议横着画
p = a;printf("%p,%d\n",&p[4][2]-&a[4][2],&p[4][2]-&a[4][2]);//地址相减为元素个数,小地址-大地址为负即-4return0;}
将-4以%p打印时
原码 10000000000000000000000000000100
反码 11111111111111111111111111111011
补码 11111111111111111111111111111100
转化为16进制 ff ff ff fc
6.
intmain(){int aa[2][5]={1,2,3,4,5,6,7,8,9,10};int*ptr1 =(int*)(&aa +1);int*ptr2 =(int*)(*(aa +1));printf("%d,%d",*(ptr1 -1),*(ptr2 -1));return0;
7.
#include<stdio.h>intmain(){char*a[]={"work","at","alibaba"};char**pa = a;
pa++;printf("%s\n",*pa);return0;}
8.
intmain(){char*c[]={"ENTER","NEW","POINT","FIRST"};char**cp[]={c+3,c+2,c+1,c};char***cpp = cp;printf("%s\n",**++cpp);printf("%s\n",*--*++cpp+3);printf("%s\n",*cpp[-2]+3);printf("%s\n", cpp[-1][-1]+1);return0;}
总结
以上就是今天要讲的内容,本文仅仅简单介绍了指针的使用,而指针提供了对数据强有力的处理方法。还是比较重要
本文仅仅以学习笔记梳理为目的,如有建议以及错误还请指正.
码文不易,期待三连
版权归原作者 琅時壹 所有, 如有侵权,请联系我们删除。