C指针专项练习


本章主要讲解13道关于指针的经典练习。

数组名的意义

首先回顾一下关于数组名的相关知识:

1.sizeof(数组名):这里数组名表示整个数组;
2.&数组名:这里数组名表示整个数组;
3.其他情况下,数组名都表示数组首元素地址

strlen库函数:求字符串长度
sizeof操作符:求变量或者类型创建的变量所占空间的大小(单位是字节)

练习

题1

//代码1
int main()
{
	//一维数组
	int a[] = { 1,2,3,4 };
	
	//sizeof(a) - 表示整个数组
	printf("%d\n", sizeof(a));//16
	
	//a+0表示首元素的地址
	printf("%d\n", sizeof(a + 0));// 4/8
	
	//*a表示第一个元素
	printf("%d\n", sizeof(*a));//4

	//a+1表示第二个元素的地址
	printf("%d\n", sizeof(a + 1));// 4/8
	
	//a[1]是第二个元素
	printf("%d\n", sizeof(a[1]));//4

	//&a表示整个数组的地址
	printf("%d\n", sizeof(&a));// 4/8

	//&a表示数组的地址,对数组地址解引用得到数组
	printf("%d\n", sizeof(*&a));//16

	//&a+1表示跨过整个数组后的地址
	printf("%d\n", sizeof(&a + 1));// 4/8

	//&a[0]表示首元素的地址
	printf("%d\n", sizeof(&a[0]));//  4/8
	
	//&a[0]+1表示第二个元素的地址
	printf("%d\n", sizeof(&a[0] + 1));// 4/8
	return 0;
}

解析:
(1) sizeof(a):数组名放在sizeof中,表示整个数组,计算整个数组的大小,16个字节;

(2) sizeof(a + 0):这里数组名不是直接放在sizeof中,所以这里数组名表示数组首元素的地址,a+0仍然表示首元素的地址,所以sizeof(a+0)是计算地址的大小,在32位平台是4字节,在64位平台是8字节;

(3) sizeof(*a):这里a表示数组首元素地址,*a对首元素地址解引用得到首元素1,int类型,所以是4个字节;

(4) sizeof(a + 1):这里a表示首元素地址,a+1表示第二个元素的地址,指针是4个字节或者8个字节;
在这里插入图片描述
(5) sizeof(a[1]):a[1]表示第二个元素,也就是2,int类型,4个字节

(6) sizeof(&a):&a表示数组的地址,指针的大小4个字节或者8个字节;

(7) sizeof(*&a):&a得到数组的地址,对数组地址解引用得到数组,也就是数组名,*&a 等价于a,所以sizeof(数组名)是计算整个数组的大小,16个字节;

(8) sizeof(&a + 1):&a得到数组地址,&a+1跨过一个数组后的地址,地址大小4个字节或者8个字节;
在这里插入图片描述
(9) sizeof(&a[0]):a[0]表示首元素,&a[0]表示首元素的地址,地址大小是4个字节或者8个字节;

(10) sizeof(&a[0] + 1):&a[0]+1表示第二个元素的地址,地址大小是4个字节或者8个字节;

题2

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//6
	printf("%d\n", sizeof(arr + 0));//4/8
	printf("%d\n", sizeof(*arr));//1
	printf("%d\n", sizeof(arr[1]));//1
	printf("%d\n", sizeof(&arr));//4/8
	printf("%d\n", sizeof(&arr + 1));//4/8
	printf("%d\n", sizeof(&arr[0] + 1));//4/8

	printf("%d\n", strlen(arr));//越界访问,只是访问没有改值,但是也存在一定的问题,随机值
	printf("%d\n", strlen(arr + 0));//随机值

	//strlen就以为传进来的'a'的ascii码值97是地址
	printf("%d\n", strlen(*arr));//报错  'a'-97,把97作为地址开始数字符串长度,野指针,非法访问内存
	
	printf("%d\n", strlen(arr[1]));//'b'-98  报错

	//strlen的参数是const char*
	printf("%d\n", strlen(&arr));//&arr是数组的地址,类型不匹配,但是不影响

	printf("%d\n", strlen(&arr + 1));//随机值

	printf("%d\n", strlen(&arr[0] + 1));//随机值

	return 0;
}

解析:
(1) sizeof(arr):数组名放在sizeof中表示整个数组,这里是求数组的大小,6个字节;

(2) sizeof(arr + 0):arr不是直接放在sizeof中,表示数组首元素的地址,a+0仍然表示首元素的地址,地址大小是4个字节或者8个字节;

(3) sizeof(*arr):*arr表示对数组首元素地址解引用,得到首元素,也就是字符’a’,1个字节;

(4) sizeof(arr[1]):arr[1]表示第二个元素,字符’b’,1个字节;

(5) sizeof(&arr):&arr表示数组的地址,地址是4个字节或者8个字节;

(6) sizeof(&arr + 1):&arr表示数组地址,&arr+1表示跨过整个数组的地址,4个字节或者8个字节;
在这里插入图片描述
(7) sizeof(&arr[0] + 1):&arr[0]表示首元素的地址,&arr[0]+1表示第二个元素的地址,4个字节或者8个字节;
在这里插入图片描述
(8) strlen(arr):数组名表示首元素的地址,这里strlen函数是求字符串长度,遇到’\0’停止,’\0’不算作字符串长度,字符数组arr中只存储了6个字符,并没有存储’\0’,所以什么时候会遇到’\0’并不清楚,会越界访问,只是访问,没有改变值,但是也存在一定的问题,结果是随机值;

(9) strlen(arr + 0):同(8),结果是随机值;

(10) strlen(*arr):strlen函数的形参是const char*类型的指针,此处,*arr得到的是首元素,字符’a’,对应的ASCII码是97,所以这里strlen函数会把97看做地址,然后访问该地址空间,由于指针指向不明,野指针,造成非法访问内存,会报错;

(11) strlen(arr[1]):解释同(10),将字符’b’的ASCII码值98看做地址,非法访问内存;

(12) strlen(&arr):&arr是数组的地址,类型为char (*)[6],strlen函数的形参是const char*类型,类型不匹配,会报警告,但是不影响,结果是随机值,但是结果同(8)、(9);
在这里插入图片描述
(13) strlen(&arr + 1):&arr表示数组地址,&arr+1跨过数组之后的地址,越界访问,但是没有改变值,结果是随机值,但是结果要比(12)小6;
在这里插入图片描述
(14) strlen(&arr[0] + 1):&arr[0] + 1表示第二个元素的地址,结果比(8)小1;
在这里插入图片描述

题3

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));//7
	printf("%d\n", sizeof(arr + 0));//4/8
	printf("%d\n", sizeof(*arr));//1
	printf("%d\n", sizeof(arr[1]));//1
	printf("%d\n", sizeof(&arr));//4/8
	printf("%d\n", sizeof(&arr + 1));//4/8
	printf("%d\n", sizeof(&arr[0] + 1));//4/8

	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0));//6
	printf("%d\n", strlen(*arr));//'a'-97作为地址  野指针,非法访问内存
	printf("%d\n", strlen(arr[1]));//'b'-98作为地址  野指针,非法访问内存
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//5

	return 0;
}

解析:
(1)sizeof(arr):数组名直接放在sizeof中,表示整个数组,所以结果为7

(2)sizeof(arr + 0):这里数组名表示首元素地址,arr+0仍然表示首元素地址,地址是个指针,所以结果为4/8

(3)sizeof(*arr):这数组名表示首元素地址,对arr解引用,得到首元素,所以结果为1

(4)sizeof(arr[1]):arr[1] = *(arr+1),得到第二个元素,结果为1

(5)sizeof(&arr):这里arr表示整个元素,&arr表示数组的地址,是个指针,所以结果为4/8

(6)sizeof(&arr + 1):&arr+1跨过这个数组,所以结果为4/8

(7)sizeof(&arr[0] + 1):&arr[0]得到首元素地址,&arr[0]+1表示第二个元素的地址,结果为4/8

(8)strlen(arr):srr表示首元素地址,这里计算字符串的长度,从数组首元素地址开始算起,直到遇到第一个’\0’结束,’\0’不算作字符串长度,结果为6

(9)strlen(arr + 0):结果同上

(10)strlen(*arr):*arr得到数组首元素,这里把字符’a’对应的ASCII码值97作为首地址,向后查找第一个’\0’,因为地址为97的空间,指向不明,造成野指针,非法访问内存,程序崩溃

(11)strlen(arr[1]):arr[1]表示数组第二个元素,这里把第二个元素’b’对应的ASCII码值98作为地址,计算字符串长度,因为地址为98的那块空间指向不明,造成野指针,非法访问内存,程序崩溃

(12)strlen(&arr):&arr是数组的地址,计算字符串长度结果为6

(13)strlen(&arr + 1):&arr+1是个指针,跨过整个数组,结果为随机数,因为’\0’的位置不明确

(14)strlen(&arr[0] + 1):&arr[0]+1,结果为数组第二个元素的地址,计算字符串的长度,结果为5

在这里插入图片描述

题4

int main()
{
	char* p = "abcdef";	
	printf("%d\n", sizeof(p));//4/8
	printf("%d\n", sizeof(p + 1));//4/8
	printf("%d\n", sizeof(*p));//1
	printf("%d\n", sizeof(p[0]));//1
	printf("%d\n", sizeof(&p));//4/8
	printf("%d\n", sizeof(&p + 1));//4/8
	printf("%d\n", sizeof(&p[0] + 1));//4/8

	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5
	printf("%d\n", strlen(*p));//'a'-97作为地址,非法访问内存
	printf("%d\n", strlen(p[0]));//'a'-97作为地址,非法访问内存
	printf("%d\n", strlen(&p));//随机值
	printf("%d\n", strlen(&p + 1));//随机值
	printf("%d\n", strlen(&p[0] + 1));//5
	return 0;
}

解析:
(1)sizeof§:p是指针,结果为4/8

(2)sizeof(p + 1):p+1仍为指针,结果为4/8

(3)sizeof(*p):*p得到字符’a’,结果为1

(4)sizeof(p[0]):p[0] = *(p+0),为字符’a’,结果为1

(5)sizeof(&p):&p得到指针p的地址,也就是二级指针,结果为4/8

(6)sizeof(&p + 1):&p得到指针p的地址,二级指针,类型为char**,结果为4/8,指针+1跨过的字节数等于指针指向的类型的大小

在这里插入图片描述
(7)sizeof(&p[0] + 1):&p[0] + 1 = &*(p+0)+1 = p+0+1,仍为一个指针,结果是4/8

(8)strlen§:计算字符串长度,结果为6

(9)strlen(p + 1):p+1指向字符’b’,结果为5

(10)strlen(*p):*p得到字符’a’,以字符’a’对应的ASCII码值97作为地址,计算字符串的长度,该指针指向不明,是野指针,造成非法访问内存,程序崩溃

(11)strlen(p[0]):结果同上

(12)strlen(&p):&p得到指针的指针,也就是二级指针,如下图所示
在这里插入图片描述
strlen函数的形参是const char*类型
以&p作为首地址,计算字符串长度,&p类型是char**,放入strlen函数中,会报警告,如下

在这里插入图片描述
但是仍会把它看做char*类型指针来计算,这里结果是随机数,因为p的内容不确定,并不知道第一个’\0’在哪里

(13)strlen(&p + 1)):&p是二级指针,如下图所示,结果为随机值,因为\0的位置不明确
在这里插入图片描述
(14)strlen(&p[0] + 1):&*(p+0)+1 = p+0+1,结果为5

题5

int main()
{
	//二维数组
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//48
	printf("%d\n", sizeof(a[0][0]));//4

	//a[0] = *(a+0),a是首元素地址,a+0也是首元素地址,*(a+0)是首元素,也就是第一个一维数组,
	//sizeof(数组名):整个数组的大小
	printf("%d\n", sizeof(a[0]));//16

	//a[0]是第一行数组的数组名  -  数组首元素的地址,
	//a[0]+1是第一行第二个元素的地址
	printf("%d\n", sizeof(a[0] + 1));//4/8
	
	//第一行第二个元素
	printf("%d\n", sizeof(*(a[0] + 1)));//4


	//二维数组的数组名,表示首元素的地址,第一行的地址
	//第二个一维数组的地址
	//数组名a并没有单独放在sizeof内部,也没有取地址,所以a表示数组首元素的地址
	printf("%d\n", sizeof(a + 1));//4/8

	printf("%d\n", sizeof(*(a + 1)));//16

	//a[0]是第一行的数组名
	//&a[0]是是第一行的地址
	//&a[0]+1得到第二行的地址
	printf("%d\n", sizeof(&a[0] + 1));//4/8

	//*(&a[0] + 1))得到第二行的数组名
	printf("%d\n", sizeof(*(&a[0] + 1)));//16

	//*a得到第一行的数组名
	printf("%d\n", sizeof(*a));//16

	//a[3]相当于第四行的数组名,但是sizeof内部不做运算,并不会真的越界访问
	printf("%d\n", sizeof(a[3]));//16


	//*&arr:得到arr,也就是数组名,至于arr到底是整个数组还是数组首元素地址,要看放在哪里

	//对一维数组的地址解引用得到一维数组的数组名
	return 0;
}

(1)sizeof(a):数组名单独放在sizeof内部,表示整个数组

(2)sizeof(a[0][0]):这里数组名代表首元素地址,a[0][0] = * (*(a+0)+0),a[0][0]=0,结果为4

(3)sizeof(a[0]):a[0]表示首元素,这里表示第一个一维数组的数组名,数组名单独放在sizeof内部,表示整个数组

(4)sizeof(a[0] + 1):a[0]表示首元素,这里表示第一个一维数组数组名,这里数组名表示首元素地址,a[0]+1表示第一个一维数组中,第二个元素的地址,结果为4/8
在这里插入图片描述

(5)sizeof(*(a[0] + 1)):a[0]表示一个一维数组的数组名,a[0]+1表示第一个一维数组的第二个元素的地址,*(a[0]+1)表示第一行第二个元素,结果为4

(6)sizeof(a + 1):a+1表示数组第二个元素的地址,结果为4/8
在这里插入图片描述
(7)sizeof(*(a + 1)):*(a+1)表示第二个一维数组,结果为16

(8)sizeof(&a[0] + 1):&a[0]表示首元素的地址,&a[0]+1表示第二个元素的地址,结果为4/8

在这里插入图片描述

(9)sizeof(*(&a[0] + 1)):&a[0]+1表示第二个元素的地址,*(&a[0]+1)表示第二个元素,这里表示第二个一维数组,结果为16

(10)sizeof(*a):*a表示第一个元素,也就是第一个一维数组的数组名,结果为16

(11)sizeof(a[3]):a[3]表示第4个元素,结果为16,虽然越界访问,但是没有改变内存中的数据,所以没有关系。
在这里插入图片描述

题6

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));//2,5
	return 0;
}

在这里插入图片描述

题7

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;

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

	//p是结构体类型指针,p+0x1属于指针+1 ,越过一个指针指向元素的类型,这里越过20个字节
	printf("%p\n", p + 0x1);//0x100014

	//p被强制类型转换为unsigned long类型,整型+1 
	printf("%p\n", (unsigned long)p + 0x1);//0x100001

	//p被强制类型转换为unsigned int*类型指针,指针+1
	printf("%p\n", (unsigned int*)p + 0x1);//0x100004
	return 0;
}

将地址0x100000强制类型转换为struct Test*类型,那么指针p+1跨过的字节数为20,16进制表示是14,所以p+0x1 = 0x100014

(unsigned long)p + 0x1将p强制类型转换成unsigned long类型,所以p是一个无符号整数,整数+1就是单纯加1,结果为0x100001

(unsigned int*)p + 0x1把p强制类型转换为无符号int型,p+1跨过4个字节,所以结果为0x100004

注意:%p是打印地址,地址是无符号数。

题8

int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int *ptr1 = (int *)(&a + 1);
	int *ptr2 = (int *)((int)a + 1);
	printf( "%x,%x", ptr1[-1], *ptr2);
	return 0;
}

在这里插入图片描述
注意:
1.大小端问题,对于一个数据(大于一个字节),存储方式有两种,大端存储、小端存储;
2.对于数组,随着数组元素下标的递增,地址是变大的。

计算机一般是小端存储,所以如图所示,数组每个元素按小端字节序存储,(int)a中a是数组首元素的地址,将a强制类型转换为int类型,整数+1,然后再强制类型转换为int*指针,所以ptr2的指向如图所示。从内存中取数据时,也按照小端存储方式取数据,将低地址的数据放在低字节,将高地址的数据放在高字节。

题9

#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int *p;
	p = a[0];
	printf( "%d", p[0]);
	return 0;
}

a[0] = *(a+0):a是数组首元素地址,a的每个元素都是一个一维数组,所以a也就是第一个一维数组的地址,*(a+0)得到第一个一维数组,也就是第一个一维数组的数组名(除了两种情况外,数组名都表示数组首元素的地址),所以指针p表示第一个一维数组的首元素的地址,那么p[0]就得到第一个一维数组的首元素,结果是1。
在这里插入图片描述

题10

int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}

数组名a是首元素的地址,也就是第一个一维数组的地址,类型为int(*)[5],将该地址赋值给p,但是p的类型为int(*)[4](会报警告);&p[4][2] = &*( *(p+4)+ 2) = (p+4)+2
因为p是int(*)[4]类型指针,p+1跨过一个有4个int元素的数组,所以
(p+4)得到如下图中蓝色框中的数组(数组名),数组名是首元素的地址,所以*(p+4)+2得到的是该数组第3个元素的地址,同理&a[4][2]位置如下图所示,(指针-指针)的绝对值是指针之间元素的个数,有4个元素。

&p[4][2] < &a[4][2],&p[4][2] - &a[4][2] = -4

原码:

//-4
//原码
10000000 00000000 00000000 00000100
//反码
11111111 11111111 11111111 11111011
//补码
11111111 11111111 11111111 11111100

%p:打印地址,地址是无符号数,不可能小于0,所以这里把-4在内存中的补码看成无符号数字取出来打印,也就是FF FF FF FC

%d:以有符号形式、10进制形式打印数字,也就是-4

在这里插入图片描述

题11

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;
}

&aa是整个数组的地址,&aa+1跨过整个数组,
aa是首元素地址,aa+1是第二个一维数组的地址,*(aa+1)得到第二个一维数组,也就是数组名–首元素地址,将该地址强制类型转换为int*类型,如下图所示

在这里插入图片描述

*(ptr1 - 1) = 10
*(ptr2 - 1) = 5

题12

#include <stdio.h>
int main()
{
	char *a[] = {"work","at","alibaba"};
	char**pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
} 

数组名a是首元素的地址,所以a是char**类型的指针,pa的类型也是char**,指向的数据类型为char*,将a的值赋值给pa,pa++(指针+1,跨过的字节数为它所指向元素的类型大小的字节),跨过4个字节(因为char*占4个字节),所以此时pa指向数组a的第二个元素,*pa 得到数组a的第二个元素,是一个指针,指向常量字符串"at"的首字符’a’,所以%s形式打印字符串,结果为at。

在这里插入图片描述

题13

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;
}

我们来逐一分析:
在这里插入图片描述

(1) **++cpp:
指针cpp+1,跨过的字节数为cpp指向的元素的大小,也就是4个字节(指针+1跨过的字节数由指针类型决定),此时cpp的指向如下图所示:

在这里插入图片描述

**cpp = *(c+2) 是字符’P’的地址,所以以%s形式打印得到的是"POINT"

(2) ++cpp+3:
前置++的优先级大于+
++cpp后cpp的指向如下图所示

在这里插入图片描述

*的优先级大于+,++cpp得到c+1,此时表达式为–(c+1)+3,前置–的优先级大于+,所以表达式变为*c+3,得到字符’E’的地址,以%s的形式打印结果为“ER”

在这里插入图片描述
(3) *cpp[-2]+3
此时cpp的指向仍为上图所示,*cpp[-2]+3 = *( *(cpp - 2)) + 3
cpp - 2 的指向如下图所示,*的优先级比+高,* ( *(cpp-2))得到的是*(c+3),也就是字符’S’的地址,结果为“ST”
在这里插入图片描述

(4)cpp[-1][-1]+1
此时cpp的指向仍为上图所示,cpp[-1][-1] +1 = *(*(cpp -1) - 1) + 1 = *((c + 2) - 1)+1 =\ *(c+1)+1,是字符’E’的地址,所以结果为“EW”
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值