(C语言)指针和数组笔试题解析

目录

  • 一维数组
  • 字符数组
  • 二维数组(重点)
  • 指针笔试题
    • 第一题
    • 第二题
    • 第三题
    • 第四题(难)(详解)
    • 第五题
    • 第六题(难)
    • 第七题

一维数组

//一维数组
int a[] = {1,2,3,4};

printf("%d\n",sizeof(a));//16
//sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节

printf("%d\n",sizeof(a+0));//4/8
//a不是单独放在sizeof内部,也没有取地址,所以a就是首元素的地址,a+0还是首元素的地址
//是地址,大小就是4/8个字节

printf("%d\n",sizeof(*a));//4
//*a中的a是数组首元素的地址,*a就是对首元素的地址解引用,找到的就是首元素
//首元素的大小就是4个字节

printf("%d\n",sizeof(a+1));//4/8
//这里的a是数组首元素的地址
//a+1是第二个元素的地址
//sizeof(a+1)就是地址的大小

printf("%d\n", sizeof(a[1]));//4
//计算的是第二个元素的大小

printf("%d\n",sizeof(&a));//4/8
//&a取出的数组的地址,数组的地址,也就是个地址

printf("%d\n",sizeof(*&a));//16(整个数组的大小)
//&a----> int(*)[4]
//&a拿到的是数组名的地址,类型是 int(*)[4],是一种数组指针
//数组指针解引用找到的是数组
//*&a ---> a

printf("%d\n", sizeof(&a + 1));//4/8
//&a取出的是数组的地址
//&a的类型为int(*)[4]
//&a+1 是从数组a的地址向后跳过了一个(4个整型元素的)数组的大小
//本质上&a+1还是地址,是地址就是4/8字节

printf("%d\n", sizeof(&a[0]));//4/8
//&a[0]就是第一个元素的地址
//计算的是地址的大小

printf("%d\n",sizeof(&a[0]+1));//4/8
//&a[0]+1是第二个元素的地址
//大小是4/8个字节
//&a[0]+1 ---> &a[1]

字符数组

我们计算之前先了解两个概念
(C语言)指针和数组笔试题解析_第1张图片
strlen是库函数,只针对字符串,strlen是求一个字符串长度的函数从你给定的合理地址开始,一直找到’\0’为之,计算这之间共有多少个字符。或者说strlen是求字符串长度的,关注的是字符串中的\0,计算的是\0之前出现的字符的个数。
sizeof是操作符,sizeof只关注占用内存空间的大小,不在乎内存中放的是什么

//字符数组
char arr[] = {'a','b','c','d','e','f'};

printf("%d\n", sizeof(arr));//6
//sizeof(数组名)

printf("%d\n", sizeof(arr+0));//4/8
//arr + 0 是数组首元素的地址

printf("%d\n", sizeof(*arr));//1
//*arr就是数组的首元素,大小是1字节
//*arr --> arr[0]
//*(arr+0) --> arr[0]

printf("%d\n", sizeof(arr[1]));//1
//arr[1]第二个元素'b',char大小是一个字节

printf("%d\n", sizeof(&arr));//4/8
//&arr是数组的地址,是地址就是4/8个字节

printf("%d\n", sizeof(&arr+1));//4/8
//&arr + 1是跳过一整个数组后的地址

printf("%d\n", sizeof(&arr[0]+1));//4/8
//&arr[0] + 1是第二个元素的地址

printf("%d\n", strlen(arr));//随机值(大于等于6)
//字符串中没有'\0',不知道strlen什么时候停下来

printf("%d\n", strlen(arr+0));//随机值
//字符串中没有'\0',不知道strlen什么时候停下来

printf("%d\n", strlen(*arr));//--> strlen('a');-->strlen(97);//野指针
//内存单元编号为97的那片存储空间还未定义,不能使用

printf("%d\n", strlen(arr[1]));//-->strlen('b')-->strlen(98);//野指针
//内存单元编号为98的那片存储空间还未定义,不能使用

printf("%d\n", strlen(&arr));//随机值
//&arr是整个数组的地址,还是从头开始找,但arr数组里没有'\0',是随机值

printf("%d\n", strlen(&arr + 1));//随机值-6
//&arr+1是跳过整个数组的地址,从整个数组之后直到找到'\0'为之,是随机值

printf("%d\n", strlen(&arr[0] + 1));//随机值-1
//&arr[0] + 1是第二个元素的地址,从第二个元素之后直到找到'\0'为之,是随机值
char arr[] = "abcdef";

//[a b c d e f \0]
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

//[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));//--> strlen('a');-->strlen(97);//野指针  err
printf("%d\n", strlen(arr[1]));//--> strlen('b');-->strlen(98);//野指针  err
printf("%d\n", strlen(&arr));//从一个元素开始6
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//从第二个元素开始5

char *p = "abcdef";

printf("%d\n", sizeof(p));//4/8
//首元素的地址
printf("%d\n", sizeof(p+1));//4/8
//p+1跳过一个字节,但还是一个地址
printf("%d\n", sizeof(*p));//1
//首元素的地址解引用是a,a占一个字节
printf("%d\n", sizeof(p[0]));//1
//首元素a占一个字节p[0]--->*(p+0)
printf("%d\n", sizeof(&p));//4/8
//p已经是一个地址了,再进行取地址操作,二级指针
printf("%d\n", sizeof(&p+1));//4/8
//取出p的地址,再+1是p的后面的一个地址
printf("%d\n", sizeof(&p[0]+1));//4/8
//b的地址

printf("%d\n", strlen(p));//6
//从a开始向后数
printf("%d\n", strlen(p+1));//5
//从b开始向后数
printf("%d\n", strlen(*p));//err
//strlen('a');-->strlen(97);//野指针
printf("%d\n", strlen(p[0]));//err
//strlen('a');-->strlen(97);//野指针
printf("%d\n", strlen(&p));//随机值
//站在p的角度看,直到找到'\0'为止
printf("%d\n", strlen(&p+1));//随机值
//站在p后面的一个地址的角度看,直到找到'\0'为止
printf("%d\n", strlen(&p[0]+1))//5
//站在b的角度看,直到找到'\0'为止

(C语言)指针和数组笔试题解析_第2张图片

二维数组(重点)

//二维数组
int a[3][4] = {0};

printf("%d\n",sizeof(a));//48
//a[0]是第一行这个一维数组的数组名,单独放在sizeof内部,a[0]表示第一个整个这个一维数组

printf("%d\n",sizeof(a[0][0]));//4
//a[0][0]代表第一个元素

printf("%d\n",sizeof(a[0]));//16
//a[0]是第一行这个一维数组的数组名,单独放在sizeof内部,a[0]表示第一个整个这个一维数组
//sizeof(a[0])计算的就是第一行的大小

printf("%d\n",sizeof(a[0]+1));//4/8
printf("%d\n",sizeof(*(a[0]+1)));//4
//a[0]并没有单独放在sizeof内部,也没取地址,a[0]就表示首元素的地址
//就是第一行这个一维数组的第一个元素的地址,a[0] + 1就是第一行第二个元素的地址
//对(a[0]+1))解引用就是第一行第二个元素

printf("%d\n",sizeof(a+1));//4/8
printf("%d\n",sizeof(*(a+1)));//16
//a虽然是二维数组的地址,但是并没有单独放在sizeof内部,也没取地址
//a表示首元素的地址,二维数组的首元素是它的第一行,a就是第一行的地址
//a+1就是跳过第一行,表示第二行的地址
//对(a+1)解引用就是表示第二行

printf("%d\n",sizeof(&a[0]+1));//4
printf("%d\n",sizeof(*(&a[0]+1)));//16
//&a[0] - 对第一行的数组名取地址,拿出的是第一行的地址
//&a[0]+1 - 得到的是第二行的地址
//对第二行的地址解引用得到的是第二行

printf("%d\n",sizeof(*a));//16
//a没有单独放在sizeof内部,表示首元素的地址,就是第一行的地址
//*a就是对第一行地址的解引用,拿到的就是第一行

printf("%d\n",sizeof(a[3]));//16

这里不会真的去访问第四行
类比
int a = 10;
sizeof(int);
sizeof(a);
sizeof只要知道类型就会知道占几个字节。

指针笔试题

第一题

在x86的环境下

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}* p = (struct Test*)0x100000;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节


int main()
{
	printf("%p\n", p + 0x1);
	//0x100000+20转化为十六进制是0x100014
	printf("%p\n", (unsigned long)p + 0x1);
	//1,048,576+1 --> 1,048,577
	//长整型,终归是一个整形,就只是整形数据+1
	//0x100001
	printf("%p\n", (unsigned int*)p + 0x1);
	//unsigned int*类型占4个字节+1,跳过4个字节
	//0x100000+4-->0x100004
	return 0;
}

答案:
(C语言)指针和数组笔试题解析_第3张图片
换成x64的环境结构体大小会改变,最后算出来的可能就不是这个结果了。

第二题

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

答案:
(C语言)指针和数组笔试题解析_第4张图片
对于ptr2的解释:vs编译器下是小端模式存储的,int类型的数组,每个元素占四个字节,在内存中的存储如下图所示。a是首元素的地址,假设是0x0012ff40,然后被强制类型转换为int类型,当前a就是一个普通的整形数字,+1之后变为0x0012ff41,然后再把ptr2强制类型转化为int*类型的,指向下图所示位置,输出时小端存储模式,数据高位存在高地址,数据低位存在低地址,%x按照16进制输出,高位的0舍去,即输出2000000
(C语言)指针和数组笔试题解析_第5张图片

第三题

#include 
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
 return 0;
}

答案:
(C语言)指针和数组笔试题解析_第6张图片
此处不是正常的数组赋值,(0,1)是逗号表达式,(0,1)最后代表的值就是1,也就是最后的那个值就是逗号表达式的值,以此类推。最后赋进去的值其实是1,3,5,0,0,0。
a[0]是第一行的数组名,数组名没有单独放在sizeof’内部,也没有&数组名,那么数组名就是首元素的地址,也就是元素a [0] [0]的地址,p是个整形指针,p[0]相当于*(p+0),所以最后输出1.
(C语言)指针和数组笔试题解析_第7张图片

第四题(难)(详解)

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

(C语言)指针和数组笔试题解析_第8张图片
对int(*p)[4];解释:
p变量是数组指针类型的变量,p+1的含义是往后四个整形,因为p代表4个int类型数组的地址

p [ 4 ] [ 2 ]–>* ( *(p+4)+2)就是先往后走四个p的数组指针类型,也就是往后走16个字节。
*(p+4)就是访问p+4向后的四个字节(因为p的类型是包含4个整形的数组的指针),在这四个字节里再往后走两个整形,最后找到的就是蓝色区域。

至于a[4][2]比较容易理解就是红色的区域。

取出他们的地址然后相减,相同类型的地址减去地址就代表两者之间相差的元素个数。

低地址-高地址最后得到-4.
原码:10000000 00000000 00000000 00000100
反码:11111111 11111111 11111111 11111011
补码:11111111 11111111 11111111 11111100
负数在内存中以补码形式存放,以%p的形式打印就是默认补码为地址,并且为16进制的
即ff ff ff fc
以%d打印就是-4了
(C语言)指针和数组笔试题解析_第9张图片

第五题

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));//aa[1]
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}

(C语言)指针和数组笔试题解析_第10张图片
解释:
&aa要注意了,取的是是整个2行5列数组的地址,+1,跳过的是这整个数组的地址,然后强制类型转换为int*类型,限制了你的步长就为一个字节,-1就是往回走一个字节,就回到了10的位置,输出10.
aa是数组名,前面没有取地址,也没有单独放在sizeof内部,那么它就代表首元素即第一行的地址,+1跳过一行,走到第二行这一整行,此时还是一整个数组的地址,再解引用就拿到了第二行,此时是内部一个元素的地址,解引用之前是数组指针类型,解引用之后是整形指针类型。通过强制类型转换限制步长之后(这一步其实可有可无,因为在强制类型转换之前已经是整形指针类型了),-1就对应的是5的地址,所以输出5.

第六题(难)

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

答案:
(C语言)指针和数组笔试题解析_第11张图片
这里有三个字符串,分别把它们首元素的地址存到a数组里,a数组代表三个char*类型的数组,
其次知道int * a里的a代表它是一个int类型的指针+1跳过的就是int的大小,即四个字节
所以这里的char * * pa里的pa+1就代表跳过一个int * 类型的大小

所以这里就是跳过第一行来到第二行,此时pa还是一个二级指针,对pa解引用就是当前第二行的地址,%s从a开始往后打印遇到’\0’停止,就是at
(C语言)指针和数组笔试题解析_第12张图片

第七题

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语言)指针和数组笔试题解析_第13张图片
最原始的关系图
(C语言)指针和数组笔试题解析_第14张图片
**++cpp:首先++cpp指向c+2,然后解引用指向POINT,再解引用就找到了POINT这个字符串,然后打印。
每次解引用时类型才会发生改变。

++cpp+3:上一步自加操作之后再++,就指向c+1然后对其解引用指向NEW,再- -指向ENTER解引用找到ENTER的首地址+3跳过三个字节,此时指针位置在c[0]数组元素的第二个E的位置上,往后打印就是ER。

*cpp[-2]+3: 简写为 * ( * (cpp-2))+3,此时cpp经过上两步的变换之后到了c+1的位置,cpp-2指向c+3,解引用指向FIRST,再解引用拿到了FIRST,+3之后,此时指针位置在c[3]数组元素的S的位置上,往后打印就是ST。

cpp[-1][-1]+1:简写为*(*(cpp-1)-1)+1,cpp-1指向c+2,解引用指向POINT然后对其再-1就指向NEW,解引用就拿到了NEW,+1之后,指针位置在c[1]数组元素的E的位置上,往后打印就是EW。

你可能感兴趣的:(C语言,c语言,算法)