目录
前言
一、sizeof和strlen
二、数组和指针笔试题解析
2.1、一维数组
2.2 字符数组
2.3 二维数组
三、指针运算笔试题解析
总结
前言
今天我们继续来了解指针,讲一下sizeof和strlen的区别,数组和指针题解析
一、sizeof和strlen
我们之前在讲了sizeof操作符,sizeof 计算变量所占内存内存空间大小的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的大小。
#inculde <stdio.h>
int main()
{int a = 10;printf("%d\n", sizeof(a));//4printf("%d\n", sizeof a);//4printf("%d\n", sizeof(int));//4return 0;
}
输出结果都是4,而且不计算sizeof里面的内容:
#inculde <stdio.h>
int main()
{int a = 10;printf("%d\n", sizeof(a=a+3));//4return 0;
}
size_t strlen ( const char * str );
#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(arr1));return 0;
}
在arr1字符数组中,没有\0,所以strlen的结果为随机值,sizeof中数组名为数组大小,有三个字符,所以sizeof的结果为3。
在arr2字符串数组中,有'a','b','c','\0',所以strlen的结果为3,sizeof值同上。
sizeof | strlen |
1. sizeof是操作符 2. sizeof计算操作数所占内存的大小,单位是字节 3. 不关注内存中存放什么数据 | 1. strlen是库函数,使⽤需要包含头⽂件 string.h 2. srtlen是求字符串⻓度的,统计的是 \0 之前字符的隔个数 3. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界 |
二、数组和指针笔试题解析
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( ) | 解析 | 结果 |
---|---|---|
a | sizeof()里数组名就是数组的大小 | 16 |
a+0 | a为首元素的地址,加0还是首元素的地址,地址大小就是指针变量大小 | 4/8 |
*a | a为首元素的地址,解引用就是首元素,整型数组元素大小为4 | 4 |
a+1 | a为首元素的地址,+1跳过一个整型,就是第二个元素的地址,地址大小还是指针变量大小 | 4/8 |
a[1] | 就是第二个元素,元素大小为整型 | 4 |
&a | 在&后a为整个数组的地址,地址大小还是指针变量的大小 | 4/8 |
*&a | *和&先抵消,所以就是a,sizeof()里数组名就是数组的大小 | 16 |
&a+1 | &a为数组的地址,+1跳过整个数组,但还是一个地址,大小就是指针变量大小 | 4/8 |
&a[0] | a[0]为数组第一个元素,取地址,就是数组第一个元素的地址。 | 4/8 |
&a[0]+1 | &a[0]为首元素的地址,+1就是第二个元素的地址。 | 4/8 |
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));
sizeof( ) | 解析 | 结果 |
---|---|---|
arr | 数组名就是数组的大小,char类型为1个字节 | 6 |
arr+0 | arr为首元素的地址,+0还是首元素的地址 | 4/8 |
*arr | arr为首元素的地址,*解引用就是首元素,元素大小为char | 1 |
arr[1] | arr[1]为第二个元素的大小 | 1 |
&arr | &后arr为整个数组的地址,地址大小 | 4/8 |
&arr+1 | &后arr为整个数组的地址,+1跳过整个数组,还是一个地址大小 | 4/8 |
&arr[0]+1 | &arr[0]为首元素的地址,+1跳过一个char类型,就是第二个元素地址大小 | 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));
strlen( ) | 解析 | 结果 |
---|---|---|
arr | arr为首元素的地址,strlen计算数组的长度,但是数组中没有\0,,就会导致越界访问,结果就是随机的 | 随机值 |
arr+0 | arr+0还是首元素的地址,但是和第一个arr一样,结果为随机值 | 随机值 |
*arr | *解引用arr为首元素a,但是strlen需要一个指针,a的ASCII码值给strlen,strlen得到的就是野指针。 | error |
arr[1] | arr[1]就是数组元素b,与上面*arr一样, strlen得到的就是野指针。 | error |
&arr | &arr为数组的地址,但是数组没有\0,所以结果还是随机值 | 随机值 |
&arr+1 | &arr为数组的地址,+1跳过整个数组,然后strlen还向后访问,遇到\0停止,结果还是随机值 | 随机值 |
&arr[0]+1 | &arr[0]+1为第二个元素的地址,strlen向后遇到\0停止,结果是随机值 | 随机值 |
代码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));
sizeof() | 解析 | 结果 |
---|---|---|
arr | sizeof中数组名为整个数组的大小,计算数组的大小为7 | 7 |
arr+0 | arr为首元素的地址,+0还是首元素的地址,地址大小为指针变量大小 | 4/8 |
*arr | *arr为首元素,元素大小为char类型 | 1 |
arr[1] | arr[0]为数组首个元素,元素为char类型 | 1 |
&arr | &arr为整个数组的地址,地址大小 | 4/8 |
&arr+1 | &arr为数组的地址,+1跳过整个数组,还是一个地址 | 4/8 |
&arr[0]+1 | &arr[0]为首元素的地址,+1跳过一个char,就是第二个元素的地址 | 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));
strlen( ) | 解析 | 结果 |
---|---|---|
arr | arr为数组首元素地址,向后遇到\0停,数组字符串有\0,\0之前有6个字符 | 6 |
arr+0 | arr+0还是数组首元素的地址,结果与arr一样 | 6 |
*arr | *arr为数组首元素a,a的ASCII值给strlen得到的就是野指针。 | error |
arr[1] | arr[1]为数组第二个元素b,strlen得到的也是个野指针。 | error |
&arr | &arr为数组的地址,从第一个元素往后遇到\0停止,有6个字符 | 6 |
&arr+1 | &arr为数组的地址,+1跳过整个数组,然后strlen后遇到\0停止,所以结果为随机值 | 随机值 |
&arr[0]+1 | &arr[0]为第一个元素的地址,+1跳过一个元素,strlen第二个元素往后,有5个字符 | 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));
sizeof( ) | 解析 | 结果 |
---|---|---|
p | p为字符串的地址,就是地址的大小 | 4/8 |
p+1 | p为字符串的地址,+1就是b的地址,就是地址大小 | 4/8 |
*p | *p的类型为char *,解引用就是char | 1 |
p[0] | 1.p[0]--*(p+0)--*p,与上面*p一样,大小为char 2.字符串可以看成一个数组,p就是数组名,p[0]就是第一个元素,大小为char | 1 |
&p | &p为指针变量p的地址,也是一个指针变量 | 4/8 |
&p+1 | &p为指针变量p的地址,+1为跳过指针变量地址,还是一个地址 | 4/8 |
&p[0]+1 | &p[0]可以看成字符串数组第零个元素的地址,+1为第一个元素的地址。 | 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));
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]));
sizeof( ) | 解析 | 结果 |
---|---|---|
a | a在sizeof里表示,整个数组的大小,数组有12个整型元素 | 48 |
a[0][0] | a[0][0]为数组的第一个元素,元素为整型 | 4 |
a[0] | a[0]为首行元素,相当于a[0]为数组名,sizeof计算的数组大小 | 16 |
a[0]+1 | a[0]没有放在sizeof里,a[0]为数组首元素a[0][0]的地址,+1跳过一个char类型,为a[0][1]的地址,地址大小。 | 4/8 |
*(a[0]+1) | a[0]+1为a[0][1]的地址,解引用为元素的大小,为char类型 | 1 |
a+1 | a为二维数组首地址就是首行地址,第一行的地址,a+1,跳过一行,指向了第二行,a+1是第二行的地址,a+1是数组指针,地址大小 | 4/8 |
*( a+1) | *(a+1)--a[1],在sizeof内a[1]相当于第二行数组的首元素的地址,就是第二行数组大小 | 16 |
&a[0]+1 | a[0]为首行元素,&a[0]就是取出第一行数组的地址,+1就是跳过一个数组,就是第二行数组的地址 | 4/8 |
*(&a[0]+1) | &a[0]+1为第二行数组的地址,解引用就是第二行的大小 | 16 |
*a | a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址,就是第一行的地址,*a就是第一行,计算的就是第一行的大小 | 16 |
a[3] | a[3]无需真实存在,仅仅通过类型的推断就能算出长度 | 16 |
三、指针运算笔试题解析
题目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;
}
&a+1为数组跳过整个数组留下了的地址
所以*(a+1)为a[1]为数组第二个元素2,*(ptr-1)其中ptr-1为ptr为整型指针,-1就往前跳一个整型的地址,解引用就是5。
//在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;
}
p+1,p跳过一个结构体指针类型大小为20,为0x100014。
把p强制类型转换为unsigned long整型,整型直接加1为0x100001
把p强制类型转换为unsigned int*,大小为4个字节,加1为0x100004。
题目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;
}
我们分析题目中有一个陷阱,就是二维数组初始化时应该用大括号,而题目中却用的括号,其中为逗号表达式,实际上为 1,3,5
p为首行元素的地址,p[0]--*(p+0)就是第一个元素。
题目4 :
#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的类型为int * [4],而a的类型为int *[5],所以p[4][2]就是p在a数组里面以四个整型为一组,到第四个数组中的第二个元素,相当于a中的a[3][2],取出地址相减就是元素个数,但是a[4][2]的地址更大结果就是-4
但是通过%p打印,我们先求得-4的补码为11111111111111111111111111111100,地址是十六进制,转换为就是FFFFFFFC
题目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;
}
&aa为数组的地址,+1跳过整个数组到最后,ptr-1就表示先前跳一个地址再解引用就是10
aa为数组首元素的地址,为二维数组第一行的地址,+1跳过第一行数组,再接引用就是第二行首元素的地址,也可以换成aa[1]就是第二行首元素的地址,ptr2-1就是第一行最后一个元素地址,再接引用就是5
题目6:
#include <stdio.h>
int main()
{char *a[] = {"work","at","alibaba"};char**pa = a;pa++;printf("%s\n", *pa);return 0;
}
a为指针数组,存放的是三个字符串的地址,pa为二级指针存放的是数组首元素的地址,+1跳过一个char *类型就是存放数组第二个字符串的地址,*pa就是第二个元素的地址
题目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;
}
下面就是在内存中的大小:
**++cpp:cpp原先指向的是cp首元素的地址,++后变为cp+1的地址,*cpp就是cp中c+2的内容,再解引用*(c+2)就是POINT字符串的地址,最后打印的就是POINT。
*--*++cpp+3:cpp现在指向的是cp+1的地址,++cpp就是cp+2的地址,*cpp就是c+1,--(c+1)就变为c,然后就变为*c就是ENTER字符串的地址,再加3就变为E的地址,最后打印ER。
*cpp[-2]+3:cpp[-2]--*(cpp-2)现在cpp指向的是cp+2的地址,减2就指向cp的地址,*cp就是c+3,*(c+3)就是FIRST的地址,最后加三就是S的地址,最后打印ST。
cpp[-1][-1]+1:cpp[-1][-1]--*(*(cpp-1)-1),由于cpp指向的是cp+2的地址,-1指向的是cp+1的地址,解引用后为c+2,在减1变为c+1,在解引用变为NEW字符串的地址,最后加1为E的地址,最后打印EW。
总结
今天我们讲了指针最后的内容,讲了sizeof和strlen的区别和用法,和指针运算笔试题。希望对你有所帮助。