今天我们来看一组有关 指针与数组的笔试题 ,来巩固一下上一篇博客学习的知识。
上一篇博客的链接附上:進撃の指针
一. 一维数组与指针
数组名 通常 代表数组首元素的地址
但有两个意外:
———————————————————————————————————一. sizeof(数组名)—数组名表示整个数组,计算的是整个数组的大小,单位是字节
二. &arr 数组名表示整个数组,取出的是整个数组的地址
1.
#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)); //*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));//第二个原素的地址
}
2.
#include<stdio.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));//第二个元素地址的大小
}
#include<stdio.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//arr为首元素地址,arr[]无‘\0’,随机值
printf("%d\n", strlen(arr + 0));//同上
printf("%d\n", strlen(*arr));//*arr 是字符a,将97当作地址,报错
printf("%d\n", strlen(arr[1]));//arr[1] 是字符b,将98当作地址,报错
printf("%d\n", strlen(&arr));
//&arr是数组的地址,但传入strlen(char* str)时,
//依旧被读为首元素地址,再往后读取过程中,arr[]无‘\0’,最终为随机值
printf("%d\n", strlen(&arr + 1));//&arr+1跳过一个数组,后面内容位置,依旧是随机值
printf("%d\n", strlen(&arr[0] + 1));//b的地址,arr[]无‘\0’,随机值
}
3.
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//整个数组的大小,包括'\0'
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));//第二个元素地址的大小
}
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";
//a b c d e f \0
printf("%d\n", strlen(arr)); //首元素地址,6
printf("%d\n", strlen(arr + 0)); //首元素地址,6
printf("%d\n", strlen(*arr));//err
printf("%d\n", strlen(arr[1]));//err
printf("%d\n", strlen(&arr)); //6
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//
}
4.
#include<stdio.h>
#include<string.h>
int main()
{
char* p = "abcdef";
//指针p指向字符串abcdef的首地址,即a的地址
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)); //p的地址大小
printf("%d\n", sizeof(&p + 1)); //跳过p,指向p之后的空间的地址
printf("%d\n", sizeof(&p[0] + 1));//第二个元素地址大小
}
#include<stdio.h>
#include<string.h>
int main()
{
const char* p = "abcdef";
printf("%d\n", strlen(p));// 7,p中存有a的地址
printf("%d\n", strlen(p + 1));//6,p+1代表b的地址
printf("%d\n", strlen(*p));//err
printf("%d\n", strlen(p[0]));//err
printf("%d\n", strlen(&p)); //随机值,&p取出p的地址,
printf("%d\n", strlen(&p + 1));//随机值
printf("%d\n", strlen(&p[0] + 1));//5
}
对于下面两式,有必要详细解释一下:
printf("%d\n", strlen(&p)); //随机值,&p取出p的地址,
printf("%d\n", strlen(&p + 1));//随机值
---- .... ---- ---- ---- ---- ---- ---- (内存)
p a b c d e f
^ ^
| |
&p &p+1
&p取出指针p的地址,观察上图,strlen函数以p地址为起点 一直往后找'\0',
但我们无法得知p指针与字符串在内存中的位置,也不知道p和a之间是否已经存在
'\0',或者p指针的四个字节中有'\0'的存在,所以最终结果为随机值。
同理,&p+1,跳过了指针p,strlen函数从p的后面为起点向后找,依然为随机值。
二. 二维数组与指针
对于二维数组a[i][j]来说:
sizeof(a[0]),当数组名a[0]单独出现在sizeof内部的时候,a[0]表示的是整个第一行,sizeof(a[0])计算的是第一行的大小。推广来说,a[i]表示的是第i行的数组名。
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
3.除此之外所有的数组名都表示首元素的地址
#include<stdio.h>
#include<string.h>
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));
//a单独放在sizeof中,表示整个数组大小
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));
//a[0]没单独放在sizeof内,也没取地址,所以表示a[0][0]的地址,a[0]+1表示a[0][1]的地址
printf("%d\n", sizeof(*(a[0] + 1)));
//表示a[0][1]
printf("%d\n", sizeof(a + 1));
//a表示第一行地址,即二维数组首地址,a+1表示第二行的地址
printf("%d\n", sizeof(*(a + 1)));
// a+1表示第二行的地址,解引用表示 第二行,计算的是第二行的大小
printf("%d\n", sizeof(*(&a[0] + 1)));
//a[0]是第一行的数组名,&a[0]是第一行的地址,&a[0]+1是第二行的地址
printf("%d\n", sizeof(*a));
//a作为数组名,没有&,没有单独放在sizeof()内部,*a代表第一行
}
拓展:下面这句代码结果如何?
printf("%d\n",sizeof(a[3]));
//16, a[3]其实是第四行的数组名,虽然实际上不存在,但也能通过类型计算大小
三.
#include<stdio.h>
#include<string.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 的类型是 int(*)a ,我们强制转化为int* ,即指向5之后的元素的地址
a[5] 1 2 3 4 5
^ ^ ^
| | |
&a | &a+1
a a+1
#include<stdio.h>
#include<string.h>
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为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的指针类型是结构体指针,加一跳过20 ,化为16进制,为0x100014
当p的类型被强转为无符号长整形,加一跳过1,即0x100001
当p的类型被强转为无符号整形指针型,加一跳过4,即0x100004
#include<stdio.h>
#include<string.h>
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;
}
小端存储:
a[5] 01 00 00 00 |02 00 00 00 |03 00 00 00 |04 00 00 00
^ ^ ^
| | |
&a ptr1[-1] &a+1
(ptr1)
对于ptr1,&a表示整个数组的大小,&a+1跳过整个数组,指向数组之后的地址,
类型为int(*)[4],所以严格来说,&a+1表示的是原数组之后的4个整形空间的
大小,但&a+1本身的值还是一个地址,此时我们将其强转为int*类型,这时候
(int*)&a+1只代表a[5]之后的一个整形空间。我们将该地址赋给ptr1,当
ptr1[-1]时,等价于*(ptr1+(-1)),向左移动一个整形空间(4字节),即
04 00 00 00,转化一下为0x00000004。
a[5] 01 00 00 00 |02 00 00 00 |03 00 00 00 |04 00 00 00
^ ^
| |
a |
(int)a+1
—— —— —— —— (*ptr2=0x02000000)
对于ptr2,a代表数组首元素首地址,即1(在内存中为 01 00 00 00)的地址,在小端存储中
指向01的位置,现在假设这个首地址为0x20, (int)a就变成了32,此时加一,
(int)a=33,然后我们在强转回int*,变为0x21。我们发现,0x21在内存中位于0x20右边一位。
即为 00 00 00 02 ,将其赋给ptr2,再解引用即为0x02000000,打印出来为2000000.
#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,0,0,0},答案为1
#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;
}
对于第五题,涉及知识点较多
我们将a[5][5]画出来:
— — — — — |— — — — — |— — — — —| — — — — — |— — — — —
^
对于a[4][2],我们很容易找到,那么p[4][2]怎么找?
p=a,将a的首地址给数组指针p,p加一跳过4,也就是说:
— — — —| — — — —| — — — —| — — — —| — — — —| — — — —| —
^
我们可以将p当作一个8行4列的二位数组的名。
于是我们可以找到p[4][2]。
我们知道,指针与指针相减得到的是之间的元素个数,
&p[4][2] - &a[4][2]=-4,-4以%d形式打印,输出就是-4.
但当我们以%p打印时,是这样计算的:
-4原码: 10000000 00000000 00000000 00000100
-4反码: 11111111 11111111 11111111 11111011
-4补码: 11111111 11111111 11111111 11111100
内存中村的时补码,当我们以%p打印时,直接认为内存中所存的就是地址,
也就是我们直接把补码取出作为地址,化为16进制:
1111 1111 1111 1111 1111 1111 1111 1100
F F F F F F F C
对于整形数据存储不熟悉的同学,可以看这篇博客:【c语言】内存的世界,数据何以为家(一)—整形篇
#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与&aa的区别:
&aa表示整个数组的地址,加一跳过整个数组
aa此时表示数组首地址,即第一行的地址,加一跳过一行
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
对于第七题:
我们将三个字符串的首地址放在a[]数组中
pa---> char* -> work
pa+1--->char* -> at
char* -> alibaba
#include<stdo.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;
}
对于第八题,需要清晰的思路:
c[] cpp[] cp[]
char* ENTER | char***->char** FIRST
char* NEW | char** POINT
char* POINT | char** NEW
char* FIRST | char** ENTER
大家有看不明白的地方,可以在评论区留言给我喔