我今天想分享一下我对指针部分的一些细节和指针部分一些题目的理解。
一.strlen和sizeof的对比
二.数组和指针在定义等方面的基础题目
三.指针运算方面的疑难重点
只要可以熟练掌握基本知识,就可以拿下这些题目。
1.sizeof和strlen的对比
一.sizeof方面
sizeof是用来计算相关数据在内存中占的大小,它才不管你在内存中存放了什么数据,当然,这个还是要看你的数据类型的。(单位是字节)
我们可以看下面一组代码:
#inculde
int main()
{
int a = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a);
printf("%d\n", sizeof(int));
return 0;
}
结果如下:
a作为整型,大小无论是32位还是64位都是占4个字节,在我看来,对于一个具体的变量,sizeof后面有没有小括号都无所谓,而如果后面的是数据类型,那么就一定要打小括号了,不然编译器会报错的。
二.strlen方面
我们熟悉的strlen其实是C语言里面的库函数,既然是函数,那么它一定有相应的参数,我们可以看一看,以便于我们使用它:
size_t strlen ( const char * str );
这说明strlen的返回类型是size_t类型,它的参数类型是不能改变字符但能改变字符地址的char类型的指针。
计算的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。假如strlen函数找不到\0,它会一直往后找\0,直到找到\0才停下,故我们要提防越界查找。
我们看下一段代码:
#include
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;
}
结果如下:
对于那个“35”的答案,是因为这个数组里面只有‘a’‘b’‘c’三个字符,没有‘\0’,strlen会颠掉的,往后面一直找啊找啊,你知道它会停止在哪里吗?
其他的你们应该知道的。
我们对比一下这两个:
sizeof只是用来计算数据在内存中的大小,不在乎里面数据长什么样;
strlen是用来计算“标准数据”的字符个数,包含<string.h>看到‘\0’才停下。
2.数组和指针在定义等方面的基础题目
一.一维整型数组
我们往下看:
#include<stdio.h>
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));
}
结果是什么呢?
第一个结果应该是16,sizeof里面是一个赤裸裸的数组名,这个时候表示的是整个整型数组的大小。
第二个结果应该是4或8,因为这个时候数组名不是单独放在sizeof当中,此时a为数组名,表示数组首元素的地址(即‘1’的地址),地址+0还是地址,占4或8个字节(看环境)。
第三个结果应该是4,由于a不是单独放在sizeof内部,故a是数组首元素地址,*a表示1,1为整型,不管环境如何,大小都是4个字节。
第四个结果应该是4或8,“a+1”中"a"因为不单独,表示数组首元素的地址,+1跳过一个元素即得到元素“2”的地址,大小为4或8。
第五个结果应该是4,这个可以从数组的角度去理解,也可以从之前我们结合指针及“+1”跳过几个字节的知识来解决。
第六个结果应该是4或8,取出的是整个数组的地址,大小就是4或8。
第七个结果应该是16,“*”和“&”抵消,等效。
第八个结果应该是4或8,这个“+1”跳过了整个数组,指向元素“4”的后面,还是地址啊,所以大小是4或8。
第九个结果应该是4或8,结合数组的知识等,我们不难发现这个是元素“1”的地址,大小就是4或8。
第十个结果应该是4或8,同上,是元素“2”的地址,大小是4或8。
我们先用X86验证一下:
再用X64的环境验证一下:
是没有问题的。
二.一维字符数组
我们来试试六个题目:
其一:
#include"20230817定义.h"
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));
return 0;
}
第一个的答案应该是6,求的是整个字符数组中数据所占内存的大小。
第二个的答案应该是4或8,因为“arr”此时不单独,作为数组名,等价为数组首元素的地址,大小是4或8个字节。
第三个的答案应该是1,“*arr”得到元素‘a’,为单个字符,大小为1个字节。
第四个的答案应该是1,“arr[1]”是元素‘b’,同上,大小为一个字节。
第五个的答案应该是4或8,取出来的是整个数组的地址,看环境,大小是4或8个字节。
第六个的答案应该是4或8,“+1”跳过整个数组,指针指向‘f’的后面,大小是4或8个字节。
第七个的答案应该是4或8,取出元素‘a’的地址加1,得到元素‘b’的地址,大小是4或8个字节。
我们检验一下:
X86环境下:
X64环境下:
其二:
#include"20230817定义.h"
int main()
{
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));
return 0;
}
第一个答案应该是随机值,因为不知道内存中有没有“\0”。
第二个答案应该还是随机值,和sizeof是不一样的,这里“arr”和“arr+0”是一个东西,同上。
第三个的答案应该是会发生错误,我用strlen必须要传待测量长度数据的地址,故会有问题。
第四个的答案应该是同第三个。
第五个的答案和第六个的答案应该都是随机值,strlen从“a”的地址开始计算,结果就真的不知道了。
第七个的答案应该也是随机值,同理。
我们验证一下:
X86或X64:
程序崩溃了。
其三:
#include"20230817定义.h"
int main()
{
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));
return 0;
}
第一个的答案应该是6,整个字符数组的大小应该是六个字符,6个字节。
第二个的答案应该是4或8,"arr"不单独,故表示首元素的地址,就是元素“a”的地址,视情况是4字节或8字节。
第三个的答案应该是1,求的是“a”的大小。
第四个的答案应该是1,求的是“b”的大小。
第五个的答案应该是4或8,取出的是整个数组的地址,是地址就是4或8个字节。
第六个的答案应该是4或8,“&arr+1”跳过整个数组,指向“\0”,但它还是地址,大小就是4或8个字节。
第七个的答案应该是4或8,最终指向“b”,但还是地址,和环境有关。
我们来验证一下:
X86:
第一个的答案最终是7,因为我忽略的字符串是自带“\0”的,求sizeof的时候要注意把它加上去。
X64:
其他的就没有问题了。
其四:
#include"20230817定义.h"
int main()
{
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));
return 0;
}
第一个的答案应该是6,字符串本身在末尾带了一个“\0”,要停下来的。
第二个的答案应该也是6,同上。
第三个的答案应该是会发生访问冲突,地址为“a”的数据是什么我们也不知道。
第四个的答案同上,地址是“b”的数据我们也不知道是什么玩意。
第五个的答案应该是6,首先指向arr的前面,即“a”的地址处,然后往后走,正好是6个字符。
第六个的答案应该是随机值,指向整个数组的最后端,也不知道在哪里停下来。
第七个的答案应该是5,通过“+1”指向元素“b”,那是不是5呢。
我们来验证一下:
X86或X64:
被注释掉的部分无法运行。
其五:
#include"20230817定义.h"
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));
return 0;
}
第一个的答案应该是4或8个字节,p是char*类型的指针,看环境。
第二个的答案应该是4或8个字节,“p+1”是“b”的地址,指针加一即到下一个元素的地址,应该和数组内元素具体类型有关而跳过几个字节,但他还是指针,具体看环境。
第三个的答案应该是1,是元素“a”,“a”为字符,大小为一个字节。
第四个的答案应该是1,求的是元素“a”的地址。
第五个的答案应该是4或8,取出来的是“a”的地址的地址,具体看环境。
第六个的答案应该是4或8,“a”的地址的地址“+1”我也不知道是什么,反正还是地址,看环境。
第七个的答案应该是4或8,求的是“b”的地址的大小,具体看环境。
我们来验证一下:
X86:
X64:
没有问题。
其六:
#include"20230817定义.h"
int main()
{
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));
return 0;
}
第一个的答案应该是6,p是元素"a"的地址,往后到"\0"停止,刚好为6个长度。
第二个的答案应该是5,在数组中,指针“+1”跳到下一个元素“b”的地址,刚好减一个长度。
第三个的答案应该是发生访问冲突,你应该找不到地址为"a"的元素在哪里。
第四个的答案应该同第三个。
第五个的答案应该是随机值,你也不知道“&p”指向哪里。
第六个的答案应该是随机值,同第五个。
第七个的答案应该是5,从元素"b"开始读取,刚好5个。
我们来验证一下:
X86或X64:
朋友们可以自己去试一下。
三.二维数组题
#include"20230817定义.h"
int main()
{
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]));
return 0;
}
第一个答案应该是48,它把二维数组的数组名单独放在了sizeof里面,12个整型48个字节大小。
第二个答案应该是4,求的是首元素“0”的大小。
第三个答案应该是16,a[0]是第一行的一维数组的数组名,单独放在sizeof内部就是整个一维数组的大小,为16个字节。
第四个答案应该是12,"a[0]+1"是第二行一维数组的数组名,单独放在sizeof内部计算整个一维数组的大小。
第五个的答案应该是4,得到的是第二行第一个元素“0”的大小,是4个字节。
第六个的答案应该是4或8,指针指向二维数组的末尾。
第七个的答案应该是4,末尾的元素不知道但是可以肯定是一个整型。
第八个的答案应该是4或8,取出了第二行的地址+1指向第三行,但还是地址,看环境。
第九个的答案应该是4,是第三行第一个元素"0"的大小,这个是整型数组,大小为4个字节。
第十个的答案应该是12,“*a”是取出二维数组的首元素,是第一行的一维数组,大小是·12.
第十一个的答案应该是12,“a[3]”虽然是第四行的数组名,但一般默认是全用“0”代替,大小不变。
我们用双环境验证一下:
X86:
这里我犯了一个问题:数组名确实是首元素地址,但是放在sizeof内部就要小心一下,+一下也要考虑清楚,特别是二维数组。
我们结合a[0],a[1],a[2]分别是第一行,第二行,第三行的数组名,再回到第四个,
第七个,第九个,第十个,第十一个:
第四个:a[0]是第一行的数组名,“+1”代表a[0]视为首元素地址即指向第一行第一个元素,+1指向第一行第二个元素,求的是“0”的大小。
第九个:取出了第一行的地址+1跳到第二行的地址,就相当于把“&第二行”放在了sizeof内部,一个*号就把第二行的数组名放在了sizeof内部,求得第二行的大小为16个字节。
第十,第十一个是由于粗心,看错了,应该是16。
X64:
就没有问题了。(二维肯定比一维更难的)
注意:数组名有两个特别的含义,注意理解:
一::sizeof(数组名):求的是整个数组的大小。(此处数组名表示整个数组)
二::&数组名:取出的是整个数组的地址。(此处数组名表示整个数组)
除此以外数组名都表示首元素的地址。
3.指针运算方面的疑难重点
我们看看下面8道题:
其一:
#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;
}
答案应该是2 5。“a+1”是“2”的地址,解引用是“2”,“&a+1”是5后面的地址,强制类型转换决定了后面+1-1操作是什么样子的,ptr-1回到了元素“5”,解引用是元素“5”,打印下来是2 5。
我们验证一下:
没有毛病。
(注意:指针+1到底是多少,看单位元素和两个特例‘sizeof数组名和&数组名’)
其二:
#include"20230817定义.h"
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;
}
第一个的答案应该是0x100000+整个结构体的大小(转换成二进制等),同理,我们如果把一个二维数组的指针加一,跳过整个二维数组。
第二个的答案应该是0x100000+1就可以。
第三个的答案应该是0x100000+4或8就好。
下面我们来验证一下:
X86:
X64:
其实指针+1跳过的是指针类型对应的类型的大小,如int*类型的指针加一跳过一个int的大小,而不是一个int的大小。(我大意了)
其三:
#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,逗号表达式的结果取最右边的。
我们验证一下:
X64或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可以看作二维数组的指针。
答案应该是 FFFFFFFC(由于结果是负数,打印出地址,取反+1,从二进制转换为十六进制),-4(指针-指针=元素个数)。
我们看一下结果:
X86:
X64:
64位环境下就长一点,这个我疏忽了。
上面的我们其实画一下图就可以很快的写出来了。
其五:
#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;
}
第一个答案应该是10,“&aa+1”指向整个元素的最右端,并将其强制类型转换为int*,这样把它理解为“-1”是指针减4或8或者是整型向前移动一个,解引用得到的结果是10。(注意:指针+-整数到底是多少,和指针的类型密切相关)
第二个的答案应该是5,“aa+1”中“aa”属于“大众”的数组名(数组首元素地址),“+1”跳过第一行的一维数组,后面“-1”后再解引用得到5。
(再次注意:除了sizeof(数组名)中,数组名代表整个数组;“&数组名”取出的是整个元素的地址,直接决定加减n表示跳或回退几个数组)
我们验证一下:
两种环境下结果是一样的。
其六:
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
这个的答案应该是"at",a是数组首元素的地址也就是w的地址的地址,放到二级指针pa中,那么pa++根据指针类型可以推断出最后pa是“at”中“a”的地址的地址,*pa得到“a”的地址,把“a”的地址放到“%s”中,类似于strlen的实现,最后碰到“\0”打印出“at”。
我们验证一下:
所以我们要一步一步分析,把握两个特殊情况(再次注意:除了sizeof(数组名)中,数组名代表整个数组;“&数组名”取出的是整个元素的地址,直接决定加减n表示跳或回退几个数组)和其他通用情况(数组名大多数情况下表示数组首元素的地址),再认真分析就好了。
其七:
#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;
}
这个复杂的题目我们只能靠画图来解决。
第一个的答案是POINT。**++cpp=**(cp+1)=**(&(c+2))=**(&(&&"P"))=&"P",最终打印出POINT。
第二个的答案是ER。*--*++cpp+3=*--*&(c+1)+3=*--(c+1)+3=*--(&&"N")+3=&"E"(字符指针)+3=&“E”,打印出来的是ER。
第三个的答案应该是ST,*cpp[-2]+3=**(cp-2)+3=**(&&&"c+3"-2)+3=**(&&&"F")+3,得到FIRST中“s”的地址,打印出ST。
第四个的答案应该是EW,cpp[-1][-1]+1=*(*(cpp-1)-1)+1=*((c+2)-1)+1=*(c+1)+1=&"N"+1=&"E",打印出EW。
我们验证一下:
好好画图,一步一步分析,搞清楚指针+-1的位置变化,和各操作符优先级,++--指针的布局改变等,会较为轻松的解决该题。
看一下我画的图吧,快跟着我一起画一下吧。
我们下次再见。