指针各类应用及大厂面试真题解析
文章目录
前言
初学者或普通高校所学习的指针大多浅显,即明白指针是地址,通过解引用操作可以操作地址内容,往往仅限于简单变量与数组,但要对指针有明确进一步的认识提高,其中有许多知识我们尚未涉足。本文将对指针进行简单系统的概括总结,且附上数道面试真题,帮助大家理解与掌握。干货满满,求大家支持一下。
一、我们目前掌握的指针
1.字符指针
指针类型为char*
使用方法
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'w';
return 0;
}
另一种
int main()
{
const char* ps = "hello";
printf("%s\n", ps);
return 0;
}
运行结果
这里实际上是讲‘hello’首元素地址存入了指针变量ps中。
以%s字符串打印即可通过地址找到首元素向后寻址打印字符串数组
相关面试题
int main()
{
char str1[] = "hello";
char str2[] = "hello";
const char* str3 = "hello";
const char* str4 = "hello";
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是两个不同的数组,只不过是其内所有元素相同,开辟的是不同的地址空间。故其二者不相同
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跳过整个数组
指针数组常用于二维数组
void print_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");
}
}
int main()
{
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr(arr, 3, 5);
return 0;
}
数组传参
一维数组
void test(int arr[])
{}
void test(int arr[10])//为方便初学者学习,设计时可以这样传入
{}
void test(int* arr)//数组名本为首元素地址,用指针接收合乎情理
{}
void test2(int* arr[20])
{}
void test2(int** arr)//arr2本为指针,指针用二级指针接收
{}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
二维数组
void test(int arr[3][5])
{}
void test(int arr[][5])//这里不同,其他与一维数组可以类比
{}
//二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)
{}
void test(int* arr[5])
{}
void test(int(*arr)[5])//第一行指针,可以用来模拟二维数组
{}
void test(int** arr)
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
一级指针
#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;
}
函数指针
void (*p)(int,float)//可与数组指针类比p先与*结合,后有函数调用操作符,即p作为一个指针指向函数,函数类型void(int,float)
与函数指针相关有许多套娃操作,不在本文重点,随手带过
函数指针数组
int (*p[10])();
数组中存放着10个函数指针
函数指针数组所对应的指针
void(*(*p)[5])(int)
重点
二、指针和数组笔试题解析
数组名的两个例外
数组名是数组首元素地址,单独&数组名和sizeof(数组名)是取出的是数组的地址
铺垫
1.一维数组
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//数组名a单独放在sizeof内部,计算的整个数组的大小,单位是字节,4*4 = 16
printf("%d\n", sizeof(a + 0));//a表示的首元素的地址,a+0还是数组首元素的地址,是地址大小4/8
printf("%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/8
printf("%d\n", sizeof(&a[0]));//&a[0]取出数组第一个元素的地址,是地址就是4/8
printf("%d\n", sizeof(&a[0] + 1));//&a[0]+1就是第二个元素的地址,是地址大小就是4/8个字节
return 0;
}
sizeof只关注占用空间的大小,单位是字节
/sizeof不关注类型
sizeof是操作符
strlen
#include <string.h>
//strlen关注的字符串中\0的为止,计算的是\0之前出现了多少个字符
//strlen只针对字符串
//strlen是库函数
int main()
{
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内部,计算的整个数组的大小,单位是字节,6
printf("%d\n", sizeof(arr + 0));//arr就是首元素的地址,arr+0还是首元素的地址,地址大小就是4/8
printf("%d\n", sizeof(*arr));//arr就是首元素的地址,*arr就是首元素,是一个字符,大小是一个字节,1
printf("%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个字节
return 0;
}
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n", strlen(arr));//"abcdef"后隐藏\0,strlen计算\0前的字符个数,结果是6
printf("%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的地址,5
printf("%d\n", sizeof(arr));//sizeof中arr单独存在表示整个数组,6
printf("%d\n", sizeof(arr + 0));//arr+0是数组首元素,1
printf("%d\n", sizeof(*arr));//arr是数组首元素地址,*arr即是a,1
printf("%d\n", sizeof(arr[1]));//是‘b’,1
printf("%d\n", sizeof(&arr));//&arr即使是整个数组地址,但还是一个指针变量,4/8
printf("%d\n", sizeof(&arr + 1));//指针变量,4/8
printf("%d\n", sizeof(&arr[0] + 1));//指针变量,4/8
return 0;
}
int main()
{
char* p = "abcdef";
printf("%d\n", strlen(p));//p中存放的是'a'的地址,strlen(p)就是从'a'的位置向后求字符串的长度,长度是6
printf("%d\n", strlen(p + 1));//p+1是'b'的地址,从b的位置开始求字符串长度是5
printf("%d\n", strlen(*p));//'a',err
printf("%d\n", strlen(p[0]));//err
printf("%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 字符的位置向后数字符串,长度是5
printf("%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字节
return 0;
}
2.二维数组
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//数组名单独放在sizeof内部,计算的是整个数组的大小,48
printf("%d\n", sizeof(a[0][0]));//一个元素的大小,是int类型,4
printf("%d\n", sizeof(a[0]));//a[0]表示第一行的数组名,a[0]作为数组名单独放在sizeof内部,计算的是第一行的大小。,16
printf("%d\n", sizeof(a[0] + 1));//a[0]作为数组第一行,且单独存在,即第一行第二个元素的地址,是指针变量4/8
printf("%d\n", sizeof(*(a[0] + 1)));//接上,是一个元素,4
printf("%d\n", sizeof(a + 1));//第二行地址,指针变量 4/8
printf("%d\n", sizeof(*(a + 1)));//接上 16
printf("%d\n", sizeof(&a[0] + 1));//&一整行,+1即第二行地址 4/8
printf("%d\n", sizeof(*(&a[0] + 1)));//接上 是一行 16
printf("%d\n", sizeof(*a));//取到了第一行,与sizeof结合。16
printf("%d\n", sizeof(a[3]));//感觉越界,但没关系,16
return 0;
}
三,面试真题
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);//取到了5后的地址并强制类型转化为int*
printf( "%d,%d", *(a + 1), *(ptr - 1));//*(ptr-1)向前寻回了5
return 0;
}
2.
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);//结构体类型加一跳过20字节,转化16进制为14
printf("%p\n", (unsigned long)p + 0x1);//转换为无符号的长整形,即加了数字1用%p的形式打印
printf("%p\n", (unsigned int*)p + 0x1);//强制类型转换为无符号整形指针,+1跳过4,已%p形式打印
return 0;
}
3.
int main()
{
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进制打印
return 0;
}
以vs2019小端字节序为例
讲地址转化为int类型,+1就是地址+1,数字1,即一个字节,又转化为int*,如图所示,又为小端排序,低位放在低地址,高位放在高地址,02是高位
02000000
4.
#include <stdio.h>
int main()
{
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]);//首元素
return 0;
}
5.
int main()
{
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]);
//地址相减为元素个数,小地址-大地址为负即-4
return 0;
}
将-4以%p打印时
原码 10000000000000000000000000000100
反码 11111111111111111111111111111011
补码 11111111111111111111111111111100
转化为16进制 ff ff ff fc
6.
int main()
{
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));
return 0;
7.
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
8.
int main()
{
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);
return 0;
}
总结
以上就是今天要讲的内容,本文仅仅简单介绍了指针的使用,而指针提供了对数据强有力的处理方法。还是比较重要
本文仅仅以学习笔记梳理为目的,如有建议以及错误还请指正.
码文不易,期待三连