C语言:指针笔试题

指针笔试题

注意

在指针中有几种特殊情况需要注意

1.sizeof(数组名):这里的数组名表示整个数组所占用的内存空间的字节大小,计算的是整个数组的大小
2.&数组名:数组名表示整个数组,取地址数组名取出的是整个数组的地址
3.除此之外,所有数组名都表示数组首元素的地址
4.地址的大小取决于平台,32位平台地址大小是4字节,64位平台地址大小是8字节

1.一维数组练习

求打印出来的内容是多少

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));// 4*4=16
//sizeof(数组名)表示整个整个数组所占用的内存空间的字节大小4个元素,每个元素4个字节,4*4=16
printf("%d\n",sizeof(a+0));// 4/8
//a表示数组首元素地址,+0还是表示首元素也就是第一个元素的地址,32位地址4字节,64位8字节
printf("%d\n",sizeof(*a));// 4
//a表示数组首元素地址,解引用找到数组首元素,所以*a表示数组第一个元素,int型占4字节
printf("%d\n",sizeof(a+1));// 4/8
//a表示数组首元素地址,+1跳过一个元素,所以a+1表示数组第二个元素的地址
printf("%d\n",sizeof(a[1]));// 4
//表示拿到了数组a的第二个元素
printf("%d\n",sizeof(&a));// 4/8
//&数组名,表示取出了整个数组的地址,整个数组的地址的大小还是4/8
printf("%d\n",sizeof(*&a));// 4*4=16
//取地址和解引用抵消,sizeof(*(&a))=sizeof(a)sizeof(数组名)表示整个整个数组所占用的内存空间的字节大小4个元素,每个元素4个字节,4*4=16
printf("%d\n",sizeof(&a+1));// 4/8
//&数组名,表示取出了整个数组的地址,+1跳过整个数组,但还是地址,所以字节大小还是4/8
printf("%d\n",sizeof(&a[0]));// 4/8
//a[0]表示数组的第一个元素,&a[0],取出第一个元素的地址
printf("%d\n",sizeof(&a[0]+1));// 4/8
//a[0]表示数组的第一个元素,&a[0]取出数组第一个元素的地址,&a[0]+1取出第二个元素的地址

2.字符数组

求打印出来的内容是多少
在这里插入图片描述

注意arr[]这种情况不会补’\0’,当更改成arr[7] = {‘a’,‘b’,‘c’,‘d’,‘e’,‘f’}也就是开辟的数组比元素个数多一位时,后面会出现’\0’

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));// 6
//sizeof(数组名)表示整个整个数组所占用的内存空间的字节大小,6个元素每个元素1个字节,1*6=6;
printf("%d\n", sizeof(arr+0));// 4/8
//arr表示字符数组首元素地址,+0还是表示首元素也就是第一个元素的地址,32位地址4字节,64位8字节
printf("%d\n", sizeof(*arr));// 1
//arr表示字符数组首元素地址,解引用找到数组首元素,所以*arr表示字符数组第一个元素,char占1个字节
printf("%d\n", sizeof(arr[1]));// 1
//表示拿到了字符数组arr的第二个元素
printf("%d\n", sizeof(&arr));// 4/8
//&数组名,表示取出了整个数组的地址,整个数组的地址的大小还是4/8
printf("%d\n", sizeof(&arr+1));// 4/8
//&数组名,表示取出了整个数组的地址,+1跳过整个数组,但还是地址,所以字节大小还是4/8
printf("%d\n", sizeof(&arr[0]+1));// 4/8
//arr[0]表示数组的第一个元素,&arr[0]取出数组第一个元素的地址,&arr[0]+1取出第二个元素的地址

//strlen接收的是const char*的地址,根据地址查看是否有'\0'读取字符串长度

printf("%d\n", strlen(arr));//随机值
//随机值,arr数组中没有\0,所以strlen函数会继续往后找\0,统计\0之前出现的字符个数
printf("%d\n", strlen(arr+0));//随机值
//arr是数组首元素地址,arr+0还是数组首元素的地址,strlen从第一个元素向后找,还是找不到\0
printf("%d\n", strlen(*arr));//err
//arr是数组首元素的地址,*arr是数组的首元素,arr的首元素是字符a,'a'在计算机中用ASCII表示'a'-97
//97这个地址是非法访问的,因为想这种地址,都是给计算机操作系统或者内核才能调用的,编译器可能不报错
//但是你要知道你非法访问了
printf("%d\n", strlen(arr[1]));//err
//拿到的是数组的第二个元素,arr的第二个元素是字符'b','b'在计算机中用ASCII表示'a'-98
//非法访问
printf("%d\n", strlen(&arr));//随机值
//取出的是字符数组的地址,但是字符数组的地址和首元素地址一样,又因为arr数组中没有\0,所以strlen函数会继续往后找\0,求出的还是随机值
printf("%d\n", strlen(&arr+1));//随机值
//取出的是字符数组的地址,+1跳过一个数组,之后还是随机值,你也不能确定什么时候会出现\0
printf("%d\n", strlen(&arr[0]+1));//随机值
//arr[0]表示数组的第一个元素,&arr[0]取出数组第一个元素的地址,&arr[0]+1取出第二个元素的地址,但是还是不能确定第二个元素后面的哪个地方会出现\0

在这里插入图片描述

char arr[] = "abcdef";//a b c d e f \0
printf("%d\n", sizeof(arr));// 7
//sizeof(数组名)表示整个整个数组所占用的内存空间的字节大小,其中有\0了,所以是7个元素,7个元素每个元素1个字节,1*7=7;
printf("%d\n", sizeof(arr+0));// 4/8
//arr表示字符数组首元素地址,+0还是表示首元素也就是第一个元素的地址,32位地址4字节,64位8字节
printf("%d\n", sizeof(*arr));// 1
//arr表示字符数组首元素地址,解引用找到数组首元素,所以*arr表示字符数组第一个元素,char占1个字节
printf("%d\n", sizeof(arr[1]));// 1
//表示拿到了字符数组arr的第二个元素
printf("%d\n", sizeof(&arr));// 4/8
//&数组名,表示取出了整个数组的地址,整个数组的地址的大小还是4/8
printf("%d\n", sizeof(&arr+1));// 4/8
//&数组名,表示取出了整个数组的地址,+1跳过整个数组,但还是地址,所以字节大小还是4/8
printf("%d\n", sizeof(&arr[0]+1));// 4/8
//arr[0]表示数组的第一个元素,&arr[0]取出数组第一个元素的地址,&arr[0]+1取出第二个元素的地址


printf("%d\n", strlen(arr));//6
//arr传过去地址,strlen记录\0前面的字符个数,\0前一共6个字符
printf("%d\n", strlen(arr+0));//6
//arr是首元素地址,arr+0还是首元素地址
printf("%d\n", strlen(*arr));//err
//arr是数组首元素的地址,*arr是数组的首元素,arr的首元素是字符a,'a'在计算机中用ASCII表示'a'-97
//97这个地址是非法访问的,因为想这种地址,都是给计算机操作系统或者内核才能调用的,编译器可能不报错
//但是你要知道你非法访问了
printf("%d\n", strlen(arr[1]));//err
//拿到的是数组的第二个元素,arr的第二个元素是字符'b','b'在计算机中用ASCII表示'a'-98
//非法访问
printf("%d\n", strlen(&arr));//6
//取出的是字符数组的地址,但是字符数组的地址和首元素地址一样
printf("%d\n", strlen(&arr+1));//随机值
//取出的是字符数组的地址,+1跳过一个数组,之后是随机值,你也不能确定什么时候会出现\0
printf("%d\n", strlen(&arr[0]+1));//5
//arr[0]表示数组的第一个元素,&arr[0]取出数组第一个元素的地址,&arr[0]+1取出第二个元素的地址,从第二个元素的地址算起,到\0中有5个元素

在这里插入图片描述

char *p = "abcdef";//a b c d e f \0
printf("%d\n", sizeof(p));// 4/8
//p是指针变量,计算的是指针变量的大小
printf("%d\n", sizeof(p+1));// 4/8 
//p+1是'b'的地址
printf("%d\n", sizeof(*p));//1
//*p 其实就是字符'a'
printf("%d\n", sizeof(p[0]));//1
//p[0]-> *(p+0)-> *p 拿到了第一个元素,其实就是字符'a'
printf("%d\n", sizeof(&p));// 4/8 
//&p是指针变量p在内存中的地址,也就是二级指针
printf("%d\n", sizeof(&p+1));// 4/8
//&p是指针变量p在内存中的地址,也就是二级指针,+1也就是跳一个类型的指针的数值这里是char类型,跳一个字节
printf("%d\n", sizeof(&p[0]+1));// 4/8
//p[0]找到'a',&p[0]是'a'的地址,&p[0]+1就是b的地址


printf("%d\n", strlen(p));//6
//后面有\0
printf("%d\n", strlen(p+1));//5
//从b的位置开始向后数字符
printf("%d\n", strlen(*p));//err
//*p拿到的是首元素字符'a','a'在计算机中用ASCII表示'a'-97
//97这个地址是非法访问的,因为想这种地址,都是给计算机操作系统或者内核才能调用的,编译器可能不报错
//但是你要知道你非法访问了
printf("%d\n", strlen(p[0]));//err
//同上
printf("%d\n", strlen(&p));//随机值
//二级指针所存的地址的值作为地址后我也不知道指向了哪
printf("%d\n", strlen(&p+1));//随机值
//二级指针跳过一个char类型的字节数,所存的地址的值作为地址后我也不知道指向了哪
printf("%d\n", strlen(&p[0]+1));//5
//p[0]表示数组的第一个元素,&p[0]取出数组第一个元素的地址,&p[0]+1取出第二个元素的地址,从第二个元素的地址算起,到\0中有5个元素

3.二维数组

//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));//3*4*4 = 48
//计算的是整个数组的大小,单位是字节3*4*4 = 48
printf("%d\n",sizeof(a[0][0]));//4
//取出二维数组第一行第一列的元素
printf("%d\n",sizeof(a[0]));//4*4=16
//a[0]是第一行的数组名,sizeof(a[0])就是第一行的数组名单独放在sizeof内部,计算的是第一行的大小
printf("%d\n",sizeof(a[0]+1));// 4/8
//4/8 a[0]作为第一行的数组名,并没有单独放在sizeof内部,也没有被取地址
//所以a[0]就是数组首元素的地址,就是第一行第一个元素的地址,a[0]+1就是第一行第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1)));// 4
//*(a[0] + 1))表示的是第一行第二个元素
printf("%d\n",sizeof(a+1));// 4/8
//a表示首元素的地址,a是二维数组,首元素的地址就是第一行的地址
//所以a表示的是二维数组第一行的地址,a+1就是第二行的地址
printf("%d\n",sizeof(*(a+1)));// 16
//对第二行的地址解引用访问到就是第二行
//*(a+1) -> a[1]
//sizeof(a[1])
printf("%d\n",sizeof(&a[0]+1));// 4/8
//a[0]是第一行的数组名,&a[0]取出的就是第一行的地址
//&a[0] + 1 就是第二行的地址
printf("%d\n",sizeof(*(&a[0]+1)));// 16
//对第二行的地址解引用访问到就是第二行
printf("%d\n",sizeof(*a));//16
//a就是首元素的地址,就是第一行的地址,*a就是第一行
//*a - > *(a+0) -> a[0]
printf("%d\n",sizeof(a[3]));//16
//int(*)[4]数组指针
//虽然跳到数组后面了,但还是能知道占几个字节

4.练习题

第一题

在这里插入图片描述

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	//&a取出整个数组的地址,+1跳过整个数组,(int*)将地址强制类型转化为int型赋值给ptr,所以ptr是一个int型的指	针,指向a数组的最后面
	printf("%d,%d", *(a + 1), *(ptr - 1));
	                //*(a+1)指的是第二个元素,第二个元素是2
	                //*(ptr-1),因为ptr是int型指针,-1向前跳一个整形,而ptr指的是数组紧挨着的后面那个地址,						向前一个整形,指的是5
	return 0;
}

第二题

//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
//要注意%p打印的是16进制,而且它并不能去帮你找到你传入的地址,也就是说他不能帮你找某个东西的地址
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;

int main()
{
	{
		p = (struct Test*)0x100000;

		printf("%p\n", p + 0x1);
		//0x1是16进制的1,地址p+1也就是0x100000+1加1一次跳一个类型,这里的一个类型是一个结构体,此结构体是20个字节,所以p的地址应该+20
		//但0x100000是16进制,20的16进制表示0x000014
		//0x100000+0x000014是0x100014,%p打印的是16进制所以打印的是0x100014		
		printf("%p\n", (unsigned long)p + 0x1);
		//(unsigned long)p,将p存入的数据,强制类型转换为unsigned long
		//也就是转换为一个正常的无符号数字
		//(unsigned long)p + 0x1,也就是0x100000正常加0x1,直接就是普通加减法了,因为p不是地址
		//%p打印正好是16进制,0x100000+0x1=0x100001,输出0x100001
		printf("%p\n", (unsigned int*)p + 0x1);
		//(unsigned int*)p,将p这个指针的类型强制类型转换为unsigned int*型,一次跳过四个字节
		//(unsigned int*)p + 0x1,也就是跳过1个指针类型,因为p的类型是unsigned int*型
		//所以+4,0x100000+4=0x100004

		return 0;
	}
}

第三题

int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int* ptr1 = (int*)(&a + 1);
    //&a取出的是整个数组的地址+1跳过一个数组,并强制类型转换为int*型,一次跳过4个字节
    int* ptr2 = (int*)((int)a + 1);
    //a代表元素首地址,强制转换为int型也就是强制转换成一个正常的数字,然后进行加减运算的+1
    //也就是将元素首地址的值+1
    //加完后强制类型转换为(int*)指针
    //整个操作下来,也就是让数组a元素首地址向后跳了一个字节
    //因为vs是小端存储,会后特殊变化,可见下方图
    printf("%x,%x", ptr1[-1], *ptr2);
    //ptr[-1]也就是让ptr1向前跳一个元素,%x输出的是16进制,输出的是0x4
    //*ptr2是拿出ptr2中指向的内容,因为是int型并且是小端存储,打印16进制应该是0x2000000
    return 0;
}

在这里插入图片描述

第四题

int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    //这里是逗号表达式,(0, 1)只表示1,(2, 3)只表示3,(4, 5)只表示5,所以数组里面只定义了三个元素1,3,5
    //其他都是0
    // 1 3
    // 5 0
    // 0 0
    int* p;
    p = a[0];
    //因为a是二维数组,a[0]是二维数组的第一行,所以p拿到的是第一行数组的首地址
    printf("%d", p[0]);
    //p[0]代表拿到第一行数组的第一个元素,也就是1
    return 0;
}

第五题

int main()
{
    int a[5][5];
    int(*p)[4];
    //p是数组指针因为p是int(*)[4]类型,也就是一次跳过4个int型的元素
    p = a;
    //把a的地址赋给p
    printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    //指针-指针可以求出两个指针中的元素个数
    //大指针-小指针,求出来到数大于0
    //小指针-大指针,求出来到数小于0
	//所以&p[4][2] - &a[4][2]=-4
	//因为要打印%p
	//-4在内存中存的是补码
	//0000000000000000000000000000100 原码
	//1111111111111111111111111111011 反码
	//1111111111111111111111111111100 补码
	//二进制转换为16进制得出FF FF FF FC
	//%d打印就直接是-4
    return 0;
}

在这里插入图片描述

第六题

int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int *ptr1 = (int *)(&aa + 1);
	//&aa取出的是整个数组的地址,+1跳过一整个数组,并强制类型转换成int*,步长为4字节,也就是一次只能跳过4个字节
	int *ptr2 = (int *)(*(aa + 1));
	//*(aa+1)=aa[1],aa[1]指的是数组中第个横向的数组,并强制类型转换成int*,步长为4字节,也就是一次只能跳过4个字节
	printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	//因为一次只能跳过4个字节,所以*(ptr1 - 1)指的是aa[1][4]这个元素,也就是10
	//*(ptr2 - 1)指的是5
	return 0;
}

在这里插入图片描述

第七题

int main()
{
	char *a[] = {"work","at","alibaba"};
	//a是一个指针数组,每个指针指向一个字符串,且每个指针因为是用数组开辟的,其指针都是紧密相连,字符串是把字符串的首地址放给了指针中
	char**pa = a;
	//pa是一个二级指针,每次可以跳过一个char*型的指针
	pa++;
	//pa++指向了a[1]的地址
	printf("%s\n", *pa);
	//解引用pa,也就是拿到了a[1],也就是拿到了at
	return 0;
}

这里的指针数组很特殊,因为char*其实就是可以存字符串的,而字符串常量的本质是地址,所以看似里面存的不是指针,其实是指针,这里一定不要混淆,它不是字符串数组,char str[7] = “abc123”;这才是字符串数组

在这里插入图片描述


第八题

int main()
{
	char *c[] = {"ENTER","NEW","POINT","FIRST"};
	char**cp[] = {c+3,c+2,c+1,c};
	char***cpp = cp;
	printf("%s\n", **++cpp);
	//因为是前置++所以先++,指向cp[1]再解引用拿到cp[1]也就是c+2,再解引用拿到c+2也就是POINT
	printf("%s\n", *--*++cpp+3);
	//因为是前置++所以先++,本来指向cp[1]再++指向cp[2],解引用拿到cp[2]也就是c+1
	//然后前置--,再--,c+1变成c,然后再解引用拿到ENTER
	//因为字符串常量的本质是地址,所以再+3,拿到ER
	printf("%s\n", *cpp[-2]+3);
	//cpp[-2],退后两个char**,cpp指向cp[0]的地方,但这个动作不和++--一样,不会更改cpp指向cp[2]
	//因为[]等于去解引用了,所以拿到了c+3,然后再解引用拿到c+3也就是FIRST
	//+3拿到了ST
	printf("%s\n", cpp[-1][-1]+1);
	//cpp[-1]拿到了cp[1],也就是c+2
	//c+2指向的是POINT再[-1]就是拿到了NEW
	//再去+1就是指EW
	return 0;
}

优先级 ++/- -大于 *(解引用) 大于+/-
注意要区分前置++/- -和后置++/- -,虽然无论前置还是后置都要先运行++/- -,但是效果不一样

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值