指针(四)

目录

1.sizeof与strlen的区别

1.1sizeof

 2.2strlen

2.关于数组与指针的题目细致分析

2.1一维数组

 2.2字符数组

题目一组:

 题目二组:

​编辑

  题目三组

2.3二维数组

 总结:

3.指针运算题目的分析

题目一:

题目二:

题目三: 


前言:

 C 和 C++ 语言中,“% p” 通常用于以十六进制形式输出指针的值

“% x” 通常作为格式化输出的占位符,用于以十六进制形式输出整数

1.sizeof与strlen的区别

1.1sizeof

在学习操作符时我们学习了sizeof操作符,sizeof计算变量所占内存空间的大小单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存的大小

sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。

#include <stdio.h>
int main()
{
	int a = 10;
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof a);
	printf("%d\n", sizeof(int));
	return 0;
}

 

 2.2strlen

strlen是库函数,用来计算一个字符长度,其原理是:

从函数中的参数,地址一直向后开始数字符个数,一直数到\0。

strlen函数会一直向后找\0,直到找到为止,所以可能存在越界查找。

例如:

int main()
{
	char a[] = "i love u";
	char b[] = { 'i','l','o','v','e','u' };
	printf("%d\n", strlen(a));
	printf("%d\n", strlen(b));
	return 0;
}

 我们知道字符串的后面是包括"\0"的

但是b数组并没有\0那么strlen会一直找下去,所以我们知道第一个结果应该说8,第二个结果是随机值

2.关于数组与指针的题目细致分析

下面的题目只要搞懂一个重要概念:

数组名的含义:

1.arr(数组名)单独在sizeof中时,这里的数组名代表着整个数组;

2.&arr(数组名)时,这里的数组名也代表整个数组;

3.其余情况,数组名都代表数组首元素的地址。

2.1一维数组

题目:

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a + 0));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(a[1]));
	printf("%d\n", sizeof(&a));
	printf("%d\n", sizeof(*&a));
	printf("%d\n", sizeof(&a + 1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0] + 1));
}

 下面是这些题目的解析

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	//数组名a单独放在sizeof中,计算的是整个数组的大小
	//int类型大小是4,一共有4个元素,大小是4*4 = 16

	printf("%d\n", sizeof(a + 0));
	//这里a并没有单独放在sizeof中,所以a代表的是首元素地址
	//a+0代表第一个元素的地址,地址就是4/8个字节

	printf("%d\n", sizeof(*a));
	//a被解引用,a先和*结合的这里的a代表着首元素地址,解引用
	//就是首元素,int类型大小,所以应该是4

	printf("%d\n", sizeof(a + 1));
	//a+1,a代表首元素地址,a+1则代表跳到下一个元素
	//也就是a[1]的地址,是地址则大小为4/8个字节

	printf("%d\n", sizeof(a[1]));
	//a[1]就是第二个元素
	//大小是4个字节

	printf("%d\n", sizeof(&a));
	//取地址a,这里的a是代表整个数组的大小
	//是地址,所以大小是4/8个字节

	printf("%d\n", sizeof(*&a));
	//先取a的地址,这里a代表整个数组的地址
	//再解引用整个数组的地址,得到整个数组大小为 4*4 = 16字节

	printf("%d\n", sizeof(&a + 1));
	//这里a取地址,是整个数组的大小
	//再加一是跳过整个数组。我们在前面说过,sizeof只计算类型的大小,不会直接访问这个数组,所以不会越界
	//是地址,所以大小是4/8个字节

	printf("%d\n", sizeof(&a[0]));
	//a[0]是首元素,取地址则得到首元素的地址
	//是地址大小为4/8个字节

	printf("%d\n", sizeof(&a[0] + 1));
	//取首元素的地址加一,这里应该是第二个元素的地址
	//是地址大小为4/8个字节
}

 运算结果

 

 2.2字符数组

题目一组:

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


	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	return 0;
}

题目解析

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	//arr单独放在sizeof里面代表整个数组
	//大小是 1*6 = 6

	printf("%d\n", sizeof(arr + 0));
	//arr没有单独放在sizeof里面也不是取地址,arr是数组首元素的地址,
	//arr+0,就是第一个元素的地址,是地址就是4/8个字节

	printf("%d\n", sizeof(*arr));
	//arr直接被解引用,arr是首元素的地址,解引用是首元素
	//char类型的数组,每一个元素都是1字节,大小是1字节

	printf("%d\n", sizeof(arr[1]));
	//arr[1]是数组第二个元素,char类型数组每一个元素都是1字节
	//大小就是1字节

	printf("%d\n", sizeof(&arr));
	//取arr的地址,这arr就是整个数组的地址
	//是地址大小就是4/8个字节

	printf("%d\n", sizeof(&arr + 1));
	//这里取整个数组的地址加一,则跳过整个数组
	//我们前面说过sizeof不会直接访问数组,只会根据类型来计算大小,所以不会越界
	//是地址,大小就是4/8个字节

	printf("%d\n", sizeof(&arr[0] + 1));
	//取arr[0]的地址就是取首元素的地址,加一就是arr[1](第二个元素)的地址
	//是地址大小就是4/8个字节

	printf("%d\n", strlen(arr));
	//arr没有单独放在sizeof里面,也没有取地址,所以arr是数组首元素的地址
	//从首元素开始往后查找\0,一直找到为止,数组里面并没有\0所以,这个数是一个随机值

	printf("%d\n", strlen(arr + 0));
	//arr没有单独放在sizeof里面,也没有取地址,所以arr是数组首元素的地址
	//从首元素开始往后查找\0,一直找到为止,数组里面并没有\0所以,这个数是一个随机值

	printf("%d\n", strlen(*arr));
	//这里直接传过去的是一个字符,这个字符的ASCII码值被当成一个地址
	//但是这个地址并不是*arr的地址,所以编译器会报错

	printf("%d\n", strlen(arr[1]));
	//这里直接传过去的是一个字符,这个字符的ASCII码值被当成一个地址
	//但是这个地址并不是arr[1]的地址,所以编译器会报错

	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	//arr没有单独放在sizeof里面,也没有取地址,所以arr是数组首元素的地址
	//从首元素开始往后查找\0,一直找到为止,数组里面并没有\0所以,这个数是一个随机值
	return 0;
}

 我们把会报错的代码注释掉得到下面的结果

 题目二组:

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

解析:

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
//arr单独放在sizeof里面,代表整个数组,计算的是整个数组的大小
//这是char类型的数组,每个元素大小是1字节,所以大小是 1*(6+1) = 7字符串里面还有一个‘\0’不要忘记

printf("%d\n", sizeof(arr + 0));
//arr代表的是数组首元素的地址
//地址就是4/8个字节
printf("%d\n", sizeof(*arr));
//这里的arr数组名是首元素的地址,接引用就是首元素
//首元素大小就是1字节
printf("%d\n", sizeof(arr[1]));
//数组第二个元素的大小
//第二个元素的大小就是1字节
printf("%d\n", sizeof(&arr));
//这里的arr代表整个数组,再取地址我们就得到整个数组的地址
//是地址大小就是4/8个字节
printf("%d\n", sizeof(&arr + 1));
//这里的arr代表整个数组,再取地址我们就得到整个数组的地址
// 再加一就是跳过整个数组,但sizeof不会去真的访问数组,只根据类型来确定大小
//是地址大小就是4/8个字节
printf("%d\n", sizeof(&arr[0] + 1));
//这里取arr[0]的地址,加1就是访问下一个元素
//得到的就是第二个元素的地址,是地址大小就是4/8个字节


printf("%d\n", strlen(arr));
//arr这里没有放在sizeof里面,也没有&arr所以arr是首元素的地址
//从首元素的地址一直向后读取早知道读取到\0,这个数组存入一个字符串,字符串最后有一个隐藏的\0
//所以这个数就是6
printf("%d\n", strlen(arr + 0));
//这里的arr是数组首元素的地址,加0,向后访问0个,还是首元素的地址
//从首元素一直数到\0,结果还是6
printf("%d\n", strlen(*arr));
//这里arr是首元素的地址,解引用得到第一个字符,传过去的是字符的ASCII码值,strlen会把这个值当成一个地址
//但是这个地址不是*arr的地址,所以编译器会报错
printf("%d\n", strlen(arr[1]));
//这里arr[1]是第二个字符元素,传过去的是字符的ASCII码值,strlen会把这个值当成一个地址
//但是这个地址不是*arr的地址,所以编译器会报错
printf("%d\n", strlen(&arr));
//这里arr代表整个数组
//&arr是得到整个数组的地址,也就是最开头的地址开始数,数到\0为止,所以结果就是6
printf("%d\n", strlen(&arr + 1));
//这里arr代表整个数组,&arr得到整个数组的地址,加一跳过整个数组
//那么就超出了数组的范围,我们也不知道什么时候回遇到\0所以结果是一个随机值
printf("%d\n", strlen(&arr[0] + 1));
//这里是取出第一个元素的地址,加一访问下一个元素,
//也就是得到下一个元素的地址,从第二个元素开始数,所以结果应该是5

我们注释掉会报错的代码 

  题目三组

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

 解析:

int main()
{
	char* p = "abcdef";//字符串是把首元素的地址存入了指针之中
	printf("%d\n", sizeof(p));
	//这里p是一个地址
	//地址的大小就是4/8个字节

	printf("%d\n", sizeof(p + 1));
	//p+1也是一个地址,p相当于数组首元素的地址,p+1就是第二个元素的地址
	//是地址,大小就是4/8个字节
	printf("%d\n", sizeof(*p));
	//这里的p是首元素的地址
	//解引用自然是首元素,就是字符'a'也就是1字节

	printf("%d\n", sizeof(p[0]));
	//p[0]就是第一个元素的大小
	//大小就是1字节

	printf("%d\n", sizeof(&p));
	//这里取地址p,p就相当于数组名,p就代表整个数组,取出的是整个数组的地址
	//是地址大小就是4/8字节

	printf("%d\n", sizeof(&p + 1));
	//这里p代表整个数组,加一就跳过整个数组
	//是地址就是4/8个字节

	printf("%d\n", sizeof(&p[0] + 1));
	//这里就是取第一个元素的地址,加一就是调到第二个元素的地址
	//是地址大小就是4/8个字节


	printf("%d\n", strlen(p));
	//字符串最后都会隐藏一个\0
	//这里的p就是首元素的地址,就从开始数到\0
	//大小就是6

	printf("%d\n", strlen(p + 1));
	//p是数组首元素的地址,首元素地址加一就是第二个元素的地址
	//从第二个元素开始数,则是5

	//printf("%d\n", strlen(*p));
	//这里解引用得到的是首元素的值,也就是一个字符,传给strlen是他的ASCII码值
	//strlen把这个值当成地址,但是他不是*p的地址,所以编译器会报错

	//printf("%d\n", strlen(p[0]));
	//这里和上面题一样,给strlen是一个字符的ASCII码值
	//编译器会报错

	printf("%d\n", strlen(&p));
	//这里的p就是整个数组,这里取地址p取整个数组的地址,从起始开始数一直数到\0
	//所以大小就是6

	printf("%d\n", strlen(&p + 1));
	//这里的p就是整个数组,取地址就是取整个数组的地址,加一就是跳过整个数组,我们也不知道在这个数组外什么时候能遇到\0
	//所以这里是一个随机值

	printf("%d\n", strlen(&p[0] + 1));
	//这里是取第一个元素的地址+1就是第二个元素的地址
	//所以就是从第二个元素开始数,所以就是5
	return 0;
}

我们把不能运行的代码屏蔽掉,运行如下:

 

 这组题的要点就是,字符串存入一个指针时是把首元素的地址存入这个指针变量里

这个指针p就相当于这个字符串的数组名,可以和数组名的用法来对比

2.3二维数组

题目:

int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));

题目分析

int main()
{
	int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	printf("%d\n", sizeof(a));
	//a作为数组名单独在sizeof里面,所以a代表的是整个数组,sizeof计算的是整个数组的大小,
	//int 类型一共有3*4 = 12个元素,12*4 = 48,一共是48个字节
	printf("%d\n", sizeof(a[0][0]));
	//这里a[0][0]是第一个元素
	//大小就是4个字节
	printf("%d\n", sizeof(a[0]));
	//二维数组就是一维数组的数组,数组名就是首元素的地址,数组名加[0]就是第一个元素,也就是第一行的数组名
	//这个数组名单独放在sizeof里面所以是整个第一行数组的大小也就是4*4 =16个字节
	printf("%d\n", sizeof(a[0] + 1));
	//a[0]是a数组第一个元素的名字,也就是a[0][i]这个数组的数组名,数组名加一,就是数组第一个元素的地址调到
	// 第二个元素的地址,也就是从a[0][0]地址跳到a[0][1]地址
	//是地址大小就是4/8个字节
	printf("%d\n", sizeof(*(a[0] + 1)));
	//a[0]是a数组第一个元素的名字,也就是a[0][i]这个数组的数组名,数组名加一,就是数组第一个元素的地址调到
	// 第二个元素的地址,也就是从a[0][0]地址跳到a[0][1]地址这时我们把这个地址解引用得到的就是
	//a[0][1]这个元素,这个元素的大小是4个字节
	printf("%d\n", sizeof(a + 1));
	//这里a就是首元素的地址,首元素的地址加一跳到第二个元素的地址也就是a[1]
	//是地址大小就是4/8个字节
	printf("%d\n", sizeof(*(a + 1)));
	//a是a[i]数组首元素的地址所以是a[0]的地址,这个地址加一就跳到了a[1]的地址
	//解引用这个地址得到第二行的数组,大小就是4*4 = 16字节
	printf("%d\n", sizeof(&a[0] + 1));
	//a[0]是a[i]数组的首元素,也是a[0][i]数组的名字,这里直接取地址数组名,数组名就代表整个数组,整个数组的地址
	// 被取出加一就是跳过这个数组,也就是a[1]数组的地址
	//是地址大小就是4/8个字节
	printf("%d\n", sizeof(*(&a[0] + 1)));
	//a[0]是a数组第一个元素,也就是第一行的数组,这个数组名被取地址,代表整个第一个数组的地址,加一就是第二个数组的地址
	//解引用第二个数组的地址就是,第二个数组大小16
	printf("%d\n", sizeof(*a));
	//a没有单独放在sizeof里面也没有被取地址,所以a是a数组的首元素的地址,也就是第一个数组的地址
	//解引用第一个数组的地址结果就是第一个数组,大小16
	printf("%d\n", sizeof(a[3]));
	//a[3]的含义是a数组第四个元素,也就是第四个数组,虽然没有第四个数组,但sizeof不会直接访问这个数组,只根据类型来判断大小
	//所以a[3]和a[0]是一样的,a[0]是第一个数组的名字,单独放在sizeof里面代表整个第一个数组,计算整个大小,大小就是16字节
	return 0;
}

 运算图:

 

 总结:

数组名的意义:
1. sizeof( 数组名 ) ,这里的数组名表示整个数组,计算的是整个数组的大小。
2. & 数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。

3.指针运算题目的分析

题目一:

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0;
}
//程序的结果是什么?

答:

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	//&a这里的a是整个地址。取整个地址加一就是跳过这个数组的地址,这时(&a+1)的类型就是int (*)[5],强制类型转换成int*存入ptr
	printf("%d,%d", *(a + 1), *(ptr - 1));
	//a这里代表首元素的地址,类型是int*,加一跳过一个元素,是第二个元素的地址就是2
	//ptr的类型也是int*减一就是减去一个元素类型的地址大小,就是5
	return 0;
}

题目二:

//由于还没学习结构体,这里告知结构体的大小是20个字节
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);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0;
}

这里我们先有了设了一个结构体,然后我们给他定义了一个指针变量p

我们知道p的值0x100000(0x是代表了16进制),且我们知道这个结构体的类型大小是20字节

p+0x1,p指向的是结构体,大小是20个字节,指针在进行算术运算时,移动的字节数,取决于类型的大小 ,所以+1实际上是移动20个字节,有因为0x表示的是16进制,20的16进制就是14,所以p的值是0x100014

这里p指向的是结构体,把他强制类型转换成(unsigned long)在32位环境下是4个字节,我们把p里面的值转化成了一个整形,整形加1就是加一

p的值就是0x100001

我们把p的类型转换成了(unsigned int*)这个类型的大小在32位环境下是4个字节,指针点运算取决于指针指向的类型大小,+1就代表着跳过4个字节

p的值就是0x100004

题目三: 

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

 解析如下:

int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	//我们取地址a,a就代表着整个数组,就是取整个数组的大小,类型就是int(*)[4],
	//加一就是跳过整个数组,再给他强制转换为int*,让他变成了指向int类型的指针
	int* ptr2 = (int*)((int)a + 1);
	//a此时没有单独在sizeof里面,也没有取地址a,所以a代表的是数组首元素地址,把首元素地址
	// 强制转换成int类型,再加一就是得到一个地址一样值,再加一,再强制类型
	//转换成为int*类型的地址,让他指向一个整形,
	printf("%x,%x", ptr1[-1], *ptr2);
	//1:ptr1[-1] == *(ptr1 - 1),就是地址向数组内前面移动一个一个数所以是4,%x是以16进制打印整形,所以是4
	//2:这里的a加一只是在地址整形含义上加一,并不是地址向后加一,所以他指向的数一个是奇怪的数
	return 0;
}

 

还有一些特别的题目会在以后分享,喜欢的朋友可以点个关注,我会积极更新的

欢迎指正

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值