系统性学习C语言-第十六讲-深入理解指针(6)

1. sizeofstrlen 的对比

1.1 sizeof

在学习操作符的时候,我们学习了 sizeofsizeof 计算变量所占内存空间大小的,单位是字节,

如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。

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

在这里插入图片描述

1.2 strlen

strlen 是 C语言 库函数,功能是求字符串长度。函数原型如下:

size_t strlen ( const char * str );

统计的是从 strlen 函数的参数 str 中这个地址开始向后,\0 之前字符串中字符的个数。

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

#include <stdio.h>
int main()
{
 char arr1[3] = {'a', 'b', 'c'};
 char arr2[] = "abc";
 printf("%d\n", strlen(arr1));
 printf("%d\n", strlen(arr2));
 printf("%d\n", sizeof(arr1));
 printf("%d\n", sizeof(arr2));
 return 0;
}

在这里插入图片描述

1.3 sizeofstrlen 的对比

在这里插入图片描述

2. 数组和指针笔试题解析

2.1 一维数组

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

在对代码进行分析之前我们先回忆几个重要知识点。

  • 数组名通常代表数组首元素的地址,但是有例外。

  • sizeof(数组名) ,此时数组名代表整个数组。

  • &数组名 ,此时取出的地址为整个数组的地址。

在对上面的知识点进行回忆后,我们便可以对代码进行分析。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a)); //为数组名的特殊用法,代表整个数组,结果为 16
printf("%d\n",sizeof(a+0)); //数组名并未单独放在 sizeof 内部,所以代表数组首元素,结果为 4/8
printf("%d\n",sizeof(*a)); //数组名为首元素地址,对其解引用后代表首元素,首元素类型为整形,结果为 4
printf("%d\n",sizeof(a+1));//数组首元素地址 + 1,仍为地址,结果为 4/8
printf("%d\n",sizeof(a[1]));//数组中第二个元素的字节数大小,结果为 4
printf("%d\n",sizeof(&a));//取出整个数组的地址,但仍为地址,结果为 4/8
printf("%d\n",sizeof(*&a));//先取出整个数组的地址,然后再解引用,仍然相当于直接求整个数组的字节数大小,结果为 16
printf("%d\n",sizeof(&a+1));//取出整个数组的地址然后 + 1,仍为地址,结果为 4/8
printf("%d\n",sizeof(&a[0]));//求数组第一个元素的地址的字节数大小,仍为地址,结果为 4/8 
printf("%d\n",sizeof(&a[0]+1));//相当于求第二个元素的地址字节数大小,仍为地址,结果为 4/8

在这里插入图片描述

这里作者的运行环境为 32位,故地址的字节数为 4。

2.2 字符数组

练习 1:

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

与上面一样的步骤,接下来我们对代码进行解析。

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));//取出整个数组的地址,然后 + 1,相当于跳过了整个数组后的地址,仍为地址,字节数为 4/8
printf("%d\n", sizeof(&arr[0]+1));//相当于取出的是整个数组第二个元素的地址,仍为地址,字节数为 4/8

在这里插入图片描述

练习 2:

char arr[] = {'a','b','c','d','e','f'};
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[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));//从首元素地址开始计算,由于 strlen 的特性,数组中并没有存储 \0,最终结果为随机值
printf("%d\n", strlen(arr+0));//从首元素开始计算,与上面的解析同理,最终结果仍为随机值
printf("%d\n", strlen(*arr));//*arr 解析出的结果为 a,a 的 ASCII值为 97,strlen 会将这个值作为地址进行访问,最终结果为非法访问
printf("%d\n", strlen(arr[1]));//arr[1] 解析出的结果为 b,与上面的解析同理,最终结果仍为非法访问
printf("%d\n", strlen(&arr));//从数组的地址开始计算,结果为随机值,与上面的解析同理
printf("%d\n", strlen(&arr+1));//从跳过整个数组的第一个地址开始计算,结果为随机值,与上面解析同理
printf("%d\n", strlen(&arr[0]+1));//从数组的第二个元素地址开始计算,结果为随机值,与上面解析同理

因有非法访问,所以无法插入实机演示结果

练习 3:

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

解析:

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

在这里插入图片描述

练习 4:

char arr[] = "abcdef";
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", strlen(arr));//从首元素地址开始计算,结果为 6
printf("%d\n", strlen(arr+0));//从首元素地址开始计算,结果为 6
printf("%d\n", strlen(*arr));//*arr 地结果为 a ,对应地 ASCII值 为 97,strlen 会将 97 作为地址进行访问,结果为非法访问
printf("%d\n", strlen(arr[1]));//结果为非法访问,arr[1] 结果为 b,其余解析与上面一样
printf("%d\n", strlen(&arr));//从整个数组地地址开始计算,结果为 6
printf("%d\n", strlen(&arr+1));//从跳过整个数组地第一个地址开始计算,结果为随机值
printf("%d\n", strlen(&arr[0]+1));//从数组地第二个元素地址开始计算,结果为 5

练习 5:

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

解析:

char *p = "abcdef";
printf("%d\n", sizeof(p));//计算指针 p 的大小,结果为 4/8
printf("%d\n", sizeof(p+1));//计算第二字符 b 的地址大小,结果为 4/8
printf("%d\n", sizeof(*p));//计算第一元素 a 的字节大小,结果为 1
printf("%d\n", sizeof(p[0]));//计算第一元素 a 的字节大小,结果为 1
printf("%d\n", sizeof(&p));//计算指针 p 的地址大小,结果为 4/8
printf("%d\n", sizeof(&p+1));//计算跳过指针 p 地址后的第一个地址大小,结果为 4/8
printf("%d\n", sizeof(&p[0]+1));//计算 b 的地址大小,结果为 4/8

在这里插入图片描述

练习 6:

char *p = "abcdef";
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));

解析:

char *p = "abcdef";
printf("%d\n", strlen(p));//从 p 开始计算,结果为 6
printf("%d\n", strlen(p+1));//从数组的第二个元素开始计算,结果为 5
printf("%d\n", strlen(*p));//*p 解析为 a ,ASCII值 为 97,strlen 会将 97 当作地址进行访问,结果为非法访问
printf("%d\n", strlen(p[0]));//p[0] 解析为 a,结果为非法访问
printf("%d\n", strlen(&p));//从 p 的地址开始计算,结果为随机值
printf("%d\n", strlen(&p+1));//从跳过 p 的地址后的第一个地址进行计算,结果为随机值
printf("%d\n", strlen(&p[0]+1));//从数组的第二个元素的地址开始计算,结尾为 5

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 a[3][4] = {0};
printf("%d\n",sizeof(a));//计算整个 a 数组的字节数大小,结果为 48
printf("%d\n",sizeof(a[0][0]));//计算第一行第一个元素的大小,元素为整形类型,结果为 4
printf("%d\n",sizeof(a[0]));//a[0] 解析为 a数组 第一行的数组名,计算整个第一行的字节数大小,结果为 16
printf("%d\n",sizeof(a[0]+1));//a[0]+1 解析为 &a[0][1] ,计算第一行第二个元素地址的大小,结果为 4/8
printf("%d\n",sizeof(*(a[0]+1)));//*(a[0]+1) 解析为 a[0][1],计算第一行第二个元素的大小,结果为 4
printf("%d\n",sizeof(a+1));//计算 a数组 第二行地址的大小,结果为 4/8
printf("%d\n",sizeof(*(a+1)));//计算 a数组 第二行的大小,结果为 16
printf("%d\n",sizeof(&a[0]+1));//取出跳过第一行后的第一个地址,也就是第二行的地址,结果为 4/8
printf("%d\n",sizeof(*(&a[0]+1)));//取出第二行的地址然后解引用,求第二行的大小,结果为 16
printf("%d\n",sizeof(*a));// a 为首元素 a[0] 的地址,对其进行解引用表示第一行,求第一行的大小,结果为16 
printf("%d\n",sizeof(a[3]));//a 数组并没有第四行,最终结果为报错

到此我们再对数组名的意义进行总结:

  1. sizeof(数组名) ,这里的数组名表示整个数组,计算的是整个数组的大小。

  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

  3. 除此之外所有的数组名都表示首元素的地址

3. 指针运算笔试题解析

3.1 题目1:

#include <stdio.h>
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 *ptr = (int *)(&a + 1);

对于指针 ptr 所指向的地址 &a + 1 代表这跳过整个 a 数组地址后的第一个地址。

所以 *(ptr - 1) 就是对指针 ptr 的前一个地址进行解引用,也就是数组中最后一个元素的地址进行解引用。

对于 *(a + 1)a + 1 表示数组中第二个元素的地址,所以 *(a + 1) 表示对第二个元素的地址进行解引用。

最终的结果为 2,5

在这里插入图片描述

3.2 题目2

//在X86环境下 
//假设结构体的⼤⼩是20个字节 
//程序输出的结果是啥? 
struct Test
{
	int Num;
 	char *pcName;
 	short sDate;
 	char cha[2];
 	short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{
 	printf("%p\n", p + 0x1);
 	printf("%p\n", (unsigned long)p + 0x1);
 	printf("%p\n", (unsigned int*)p + 0x1);
 	return 0;
}

解析:

要解出正确答案,我们就要清楚 指针 +1 ,会产生怎样的操作,指针 + 1 会根据指针不同的步长,从而跳过不同的字节数,

所以对于 p + 0x1 跳过的就是结构体的字节数,结构体 Test 的字节数大小为 20 ,所以 p + 0x1 跳过的字节数大小为 20,

20 转换为 16进制 为 0x14,所以结果为 0x100014,同样的对于 (unsigned int*)p + 0x1 我们跳过则为 unsigned int 类型的字节数,

为 4 字节,所以最终结果为 0x100004,但是在(unsigned long)p + 0x1 p 被转换成了无符号长整形类型,不再为指针,

所以这时的 +1 就不能再遵循指针的规则,而是遵循整形的算术规则,正常 + 1,结果为 0x100001,

对于 X86 环境下的地址显示,32位系统在显示地址时最多能显示 8 位,所以我们要在结果前补上两个 0,

最终结果为:

在这里插入图片描述

3.3 题目3

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

解析:

对于这道题,我们一定要辨析好二维数组的初始化方式,题目中二维数组的初始化分组部分使用的是 () ,而并非 {}

而小括号内部使用了逗号表达式,逗号表达式返回的是表达式中最后一个值,

所以实际二维数组的初始化其实应该为这样 int a[3][2] = { 1, 3, 5 };

所以指针 pp = a[0]; 取到的是数组第一行地址,第一行包含的元素为 1,3,所以 p[0] 取到的第一个元素为 1,

最终结果为 1 。

在这里插入图片描述

3.4 题目4

//假设环境是x86环境,程序输出的结果是啥? 
#include <stdio.h>
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;
}

解析:

首先我们对于 p[4][2] 进行分析, p[4][2] 相当于 *(p + 4)[2],这里就和指针步长扯上关系了,指针 p 的类型为 int [4]

所以每次 + 1 时,跳过 4 个整形类型的字节,因为 p = a ,所以指针 p 与 数组 a 的首元素地址时一样的,

所以 p[4][2] 实际上跳过了 a 数组的 18 个元素,而 a[4][2] 跳过了数组的 22 个元素,指针 - 指针计算出的是指针之间的元素个数,

所以结果为 -4,但对于第一个 -4 我们要化成十六进制地址的形式,我们先写出 -4 的源码,然后求出补码。

源码:10000000 00000000 00000000 00000100
反码:11111111 11111111 11111111 11111011
补码:11111111 11111111 11111111 11111100
补码的十六进制:FFFFFFFC

所以最终的结果为:
在这里插入图片描述

3.5 题目5

#include <stdio.h>
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;
}

解析:

我们先对指针ptr1 进行解析,int *ptr1 = (int *)(&aa + 1); 其中 &aa + 1 是跳过整个 aa 数组后的第一个整形指针地址

*ptr2 = (int *)(*(aa + 1)); 其中 *(aa + 1)aa 表示数组首元素地址,为 &aa[0] ,+ 1 后指向 &aa[1]

所以指针 ptr2 实际代表数组 aa 的第二行。因为 ptr1,ptr2 指针的类型都为整形指针类型,所以 +1,-1 都只会跳过一个整形的地址

所以 *(ptr1 - 1) 是对 aa 数组的最后一个元素地址进行解引用,*(ptr2 - 1) 是对数组第一行最后一个元素地址进行解引用

所以最终的结果为:

在这里插入图片描述

3.6 题目6

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

解析:

我们先来分析字符指针数组 aa 的成员有三个,依次分别为 workatalibaba

因为 char**pa = a; ,所以指针 pa 指向数组 a 的首元素地址,pa++; 相当于 a[0] + 1 ,数组 a 的第一个元素为 work ,+1后

指向第二个元素 at ,所以最终的打印结果为 at

在这里插入图片描述

3.7 题目7

#include <stdio.h>
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;
}

解析:

这里我们先将所有结构以图标的形式呈现出来,以便我们更好地进行观察。
在这里插入图片描述

 	printf("%s\n", **++cpp);

现在我们再对代码进行分析,**++cpp ,其中 cpp 指针指向 cp 的首元素,在 ++ 后指向了 cp 的第二个元素 c + 2

所以结果为 POINT

 	printf("%s\n", *--*++cpp+3);

*--*++cpp+3++cpp 此时指向 cp 的第三个元素 c + 1 ,解引用符号的结合顺序更高,先结合

再与 -- 符号进行结合,此时就变成了 *--(c + 1) + 3 ,结合后变成 *c + 3 ,此时就是对 c 数组的首元素,

ENTER 的第四个元素开始输出,最终结果为 ER

 	printf("%s\n", *cpp[-2]+3);

此时 cpp 指针在与两个自增符号进行结合后,已经指向了 cp 数组的第三个元素,所以 cpp[-2] 指向了 cp 数组的第一个元素,

就表示为 *cp+3,也就是从 FIRST 的第四个字符开始输出,最终结果为 ST

 	printf("%s\n", cpp[-1][-1]+1);

此时的 cpp[-1] 代表 cp 的第二个元素,简化为 cp[1][-1]+1 再次简化为 *((c + 2) - 1) + 1 也就是 *(c + 1) + 1

NEW 的第二个字符开始输出,结果为 EW

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值