在写笔试题之前,我们要先了解两个特殊情况:
int a1 = sizeof(arr);
int* a2 = &arr;
一个是sizeof(数组名),另一个是取地址数组名
sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
除此之外所有的数组名都表示首元素的地址
还需要注意的是,指针的大小由于系统的地址总线不同而有所不同,当我们使用的是x86环境时,指针的大小为4个字节,若使用的时x64环境,则指针的大小为8;
(下述所有指针面试题都在x64环境下进行)
接下来我们看我们的笔试题:
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));
1. sizeof(a)中的a指代的是整个数组的大小,而数组中有四个元素,且int的大小为四个字节,故输出答案应该为16;
2. 第二个和第一个又有所不同,看似和sizeof(数组名)很类似,实际上并不符合该条件,因为括号内并不是纯粹的数组名,反而多出一个+0,这也是大多数人会困惑的地方,故a+0指代的是数组首元素地址,所以输出应该为8;
3. *a的意思是a指针所指向的元素,而a代表的是数组首元素地址,故*a指的是a数组的第一个元素,即a[0],故输出答案为4;
4. a指的是数组首元素地址,首元素地址+1为第二个元素的地址,地址的大小为8个字节,故输出结果为8;
5. a[1]指的是第一个元素,数组类型为整形,元素大小为4个字节,故输出结果为4;
6. &a指的是整个数组,但依然是一个地址,故输出结果为8;
7.&a指的是整个数组,是一个指针,指针解引用后就指向了整个数组,整个数组的大小为16;故输出结果为16;
8.&a指向的是整个数组的地址,&a+1则指向的是a数组之后的地址,既然是指针,那么输出的结果就为8;
9.a[0]指的是a数组的第1个元素,而&a[0]则是指向a[0]的指针,指针的大小为8,故输出结果为8;
10.&a[0]+1指的就是a[1]的地址,既然是指针,那么他的大小就为8,故输出结果为8;
由于字符数组的题目较多,我们分六个部分讲解:
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));
1. sizeof(arr)指的是整个数组的大小,而char类型的大小为1个字节,arr数组一共有6个元素,故输出结果为6;
2. sizeof(arr)指的是整个数组,但是sizeof(arr+0)指的仅仅只是数组首元素的地址的大小,并不满足sizeof(数组名)的格式,故输出结果为8;
3. arr是首元素的地址,那么*arr就是arr数组的第1个元素的值,故输出结果为1;
4. arr[1]是数组的第二个元素,故输出结果为1;
5. &arr是arr整个数组的地址,是一个指针,故输出结果为8;
6. 7. 同理,都是指针,输出结果都为8,但区别在于6和7指向的空间不同;6指向的是数组后6个字节的内存空间,而7指向的是数组第二个元素的内存空间;
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[0]+1));
//printf("%d\n", strlen(*arr));
//printf("%d\n", strlen(arr[1]));
根据cplusplus官网上对strlen的解释
可知,strlen函数的参数应该为一个字符指针; 故题中最后两行代码会运行异常!!! 并且,strlen函数是一个求字符串的长度的函数,'\0'是字符串结束的标志,strlen函数只有遇到了'\0'字符才会停止,综合以上,我们继续来完成笔试题;
1. 由于arr数组的元素个数为6,且没有'\0',故strlen函数读取完整个数组之后,会继续向后读取,直到后面某个地址中存放着'\0'为止,故输出的结果是一个随机值,在我的编译器上运行的结果就是42,而为什么会使42呢?我们接下来继续探讨:
图中0x0000001C6BFF914是数组的首元素地址,内存中存放的是十六进制的数据,十六进制的61就是十进制的97,也就是字符'a',由该内存监视图我们不难看出从61往后数(包括61)42个字节,到第43个字节时,内存中存放的值就是00;也就是我们说的'\0',故当strlen函数读取到00时,就停止运行了,所以我们输出的结果就是42;
2. 和第一个没有任何区别,输出的结果同样也是42;
3. &arr指的是整个数组的地址,但是该指针内存放的依旧是数组首元素的地址;故输出结果为42;
4. &arr + 1指向的是arr数组之后的一块内存空间,也就是说该指针跳过了arr数组,指向数组最后一个元素的后一个地址,所以输出结果为42-6 = 36;
5. &arr[0] + 1指向的是arr数组的第二个元素,故输出结果为42 - 1 = 41;
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));
1. arr数组中存放的是字符串,但字符串是以'\0'结尾的,故arr数组中存放的内容为abcdef和\0,一共七个元素,故输出结果为7;
2. sizeof(arr+0)不符合sizeof(数组名)的格式,故arr指的是数组首元素的地址,故输出的结果为8;
3. *arr指的是arr数组的第一个元素,如输出结果为1;
4. arr[1]指的是arr数组的第二个元素,故输出结果为1;
5 6 7. sizeof中的参数都是地址,故输出结果都为8;
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[0]+1));
//printf("%d\n", strlen(*arr));
//printf("%d\n", strlen(arr[1]));
strlen函数的参数是字符指针,故最后两行会报错;
1. strlen函数遇到'\0'则停止,而字符串以'\0'结尾,故输出的结果为6;
2. 和第一种没有区别;
3. &arr指的是arr整个数组的地址,但其内存放的依然是arr数组的首元素地址,故输出结果为6;
4. &arr+1指的是arr数组最后一个元素之后的一块7个字节大小的内存空间,这块空间内存放的是随机值,故strlen输出的结果也为随机值;
5. &arr[0] + 1指的是arr数组的第二个元素,也就是arr[1];故输出的结果为6;
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));
p是指针,其类型为char*,存放的是字符串的首字符的地址;
1. 指针的大小为8,故输出的结果为8;
2. p+1同样是一个指针,但指向的是第二个字符的地址,故输出结果为8;
3. *p指的是字符串首字符,故输出结果为1;
4. p[0]同样是指字符串首元素字符,故输出结果也为1;
5. p是一个指针,&p同样也是一个指针,不过是一个二级指针,故输出结果为8;
6. 同5,是一个指针,但指向的地址是p字符串之后的一块内存空间,故输出结果为8;
7. 同5,是一个指针,指向的是字符串第二个字符的地址,故输出结果为8;
char* p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));
//printf("%d\n", strlen(*p));
//printf("%d\n", strlen(p[0]));
由于strlen的参数要为字符指针,故最后两行代码会运行异常;
1. p指向的是字符串的首字符,而字符串默认以'\0'结尾,故输出结果为6;
2. p+1指向的是字符串第二个元素,故输出结果为5;
3. &p指向的是整个字符串,但存放的依然是字符串首元素的地址,故输出结果为6;
4. &p+1指向的是字符串之后的一块空间,'\0'的位置是随机的,故输出结果是一个随机值;
5. p[0]是字符串首元素,取地址后+1指向的是字符串的第二个元素,故输出结果为5;
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));
在写二维数组笔试题之前,我们需要了解二维数组的本质,实际上二维数组就是一个存放一维数组的数组,像题中的a[3][4]中的[4]实际上指的就是二维数组中存放的一维数组的元素个数,而[3]指的就是二维数组中存放了几个一维数组,所以在二维数组中,a是一个二维数组数组名,同时a[0]也是数组名,不过是一维数组的数组名,有了以上知识,我们继续解题;
1. sizeof (数组名)指的是整个数组的大小,数组一共有12个整形元素,故输出结果为48;
2. a[0][0]指的是二维数组的第一行第一个元素,故输出结果为4;
3. a[0]指的是二维数组的第一行,符合sizeof(数组名)格式,一共四个元素,故大小为16;
4. sizeof(a[0] + 1)不符合sizeof (数组名)格式,故a[0]指的是第一行的首元素地址,+1则是第一行的第二个元素的地址,故输出结果为8;
int a[3][4] = {0};
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));
在写这个题之前,我们需要像大家介绍一个新的知识点:
我们平常使用数组的时候,最常见的写法就是:
int arr = { 1, 2, 3, 4 };
arr[1] = 3;
但实际上,他还可以换成另外两种写法,和以上写法是相同的效果:
int arr = { 1, 2, 3, 4 };
int* p = arr;
p[1] = 3;
*(p + 1) = 3;
其实,我们平常所写的arr[1] = 1;在运行时都会将其转换成指针的写法,故以上三种写法都是可以运行的;
示例图:
综合以上知识点,我们继续开始答题:
1. a[0]指的是a数组第一行的第一个元素的地址,+1即第二个元素的地址,解引用就是a数组第一行的第二个元素,故输出结果为4;
2. a是二维数组首元素,也就是a[0],a[0]指的是第一行首元素的地址,a + 1也就是第二行的地址,故输出结果为8;
3. a + 1是第二行的地址,*(a + 1)其实也就等价于a[1],a[1]又是数组a第一个元素一维数组的数组名,这样就符合sizeof(数组名)的结构,故a[1]指代的就是整个第一行的大小,故输出结果为16;
4. &a[0]是数组a第一行的地址,+1后依然是一个地址,也就是指向第二行的一个指针,故输出结果为8;(实践上&a[0] +1可以写成 &*(a + 0) + 1,也就是a+1,和第二种无区别)
5. &a[0]+1是整个第二行的地址,解引用后代表整个第二行,也就是a[1],故输出结果为16;
6. *a也就是*(a+0),也就是 a[0],故输出结果为16;
void main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
//程序的结果是什么?
}
&a指的是整个数组的大小,而&a+1是整个数组之后的大小和数组a一样大的一块内存空间,而&a内存放的是数组首元素的地址,而ptr存放的是数组之后的第一块内存空间的地址,故*(a+1) = a[1],输出结果为2,*(ptr-1) = a[5],输出结果为5;
#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[0]所代表的值就是a[0][0]的值,故输出结果为1;
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 + 4) + 2,同理&a[4][2]也可以写成*(a + 4) + 2,而p是一个数组指针,指向的数组有四个元素,而p却指向了a的首元素地址,故p只能保存a数组每行的四个元素,而数组a是二维数组,一共五行五列,故p[4][2]之前实际上只有18个元素,而a[4][2]之前则有22个元素(实际上a[4][2] = p[5][2]),又因为指针 - 指针的结果是两个地址之间的元素个数,正负由地址的大小决定,故&p[4][2] - &a[4][2]的结果为-4,而-4转换成十六进制就是FFFFFFFFFFFFFFFC,故其输出结果为如下:
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指的是aa数组后和aa大小相同的一块内存空间,故*(ptr1 - 1) = aa[1][4],结果为10;
*(aa+1) = aa[1],指向第二行的首元素,故*(ptr2 - 1) = aa[0][4],结果为5;
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
数组a是一个字符指针数组,存放的是每个字符串首元素的地址,故a数组第一个元素存放的是w的地址,第二个元素存放的是a的地址,pa是数组名,存放的是首元素的地址,故pa++为第二个元素的地址,故输出结果为at;
(这个题目大概是整篇文章中最难的了,有讲解的不到位的地方希望大家多多包涵,也欢迎大家在评论区中和我讨论)
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;
}
数组c的类型为char*(一级指针),存放的是字符串的首元素地址,cp数组存放的是数组c中元素的地址,类型为char**(二级指针),而cpp存放的是cp首元素的地址,类型是char***(三级指针),做题之前我们还要分析操作符的优先级和结合性,[]的优先级最大,*和前置++-- 的优先级一样,结合性是从左到右,优先级最小的是+-双目操作符;
1. 现在来分析**++cpp,cpp先++,指向由cp的第一个元素变成了第二个元素,也就是c+2,第一次解引用后指向了c数组的第三个元素,再次解引用后指向了POINT字符串的首元素,故第一个打印输出结果为POINT;
2. 由于第一个打印中,cpp指向了cp数组的第二个元素,故++cpp指向了cp数组的第三个元素,解引用后指向了c数组的第二个元素,再--则指向了c数组的第一个元素,再次解引用后指向了ENTER的首元素的地址,首元素地址+3,则指向了ENTER字符串的第四个元素,也就是E,故打印输出结果为ER;
3. 由于第二次打印中,cpp指向了cp数组的第三个元素,故cpp[-2]即*(cpp - 2)指向了cp数组的第四个元素,再解引用,则指向了FIRST字符串的首元素,再+3则指向了字符串的第四个元素,即S,故输出结果为ST;
4. 此时cpp指向的还是cp数组的第三个元素,cpp[-1][-1]即*(*(cpp - 1) - 1),cpp - 1则cpp指向了cp数组的第二个元素,解引用后指向了c数组的第三个元素,再-1后指向了c数组的第二个元素,再解引用则指向了字符串NEW的首元素地址,再次+1,则指向了字符串的第二个元素,即E,故打印结果为EW;
以上就是本期全部内容,爆肝8000多字,希望大家能够支持,同时也希望大家能在我的分享中学习到更多的知识,如果我在文章中某些地方表达不清晰或者有所错误,希望大家能够指出来,有所疑惑的地方也欢迎大家在评论区讨论,我会及时回复的,