【C语言】进阶指针(三)—>指针与数组笔试真题详解

目录

前言:

一、一维数组

二、字符数组

三、二维数组

四、指针笔试题

笔试题(1)

笔试题(2)

笔试题(3)

笔试题(4)

笔试题(5)

笔试题(6)

笔试题(7)

笔试题(8)


前言:

本篇会列出全部有关数组和指针含义的内容、实例及分析,并引入八道笔试真题进行实操练习,干货满满。

贯穿本篇的核心知识就是数组名的意义

1、sizeof(数组名),这里的数组名表示整个数组,sizeof计算的是整个数组的大小,注意括号中必须为数组名才满足这一意义,比如sizeof(arr+1)就不是整个数组的大小了,&数组名同理。

2、&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

3、除此之外所有的数组名都表示首元素的地址。

一、一维数组

int main()
{
	//一维数组
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));        //16 
    //a为数组名,sizeof(数组名)计算的是整个数组的大小,即4*4=16  
	printf("%d\n", sizeof(a + 0));	  //4/8
    //a为数组名,代表首元素地址,a+0还是首元素地址,只要是地址,大小即为4/8  
	printf("%d\n", sizeof(*a));		  //4
    //*a为数组中的元素1,为int类型,sizeof(int)为4
	printf("%d\n", sizeof(a + 1));	  //4/8
    //a+1指向数组中的元素2,为int*,只要是指针类型,大小即为4/8
	printf("%d\n", sizeof(a[1]));	  //4
	printf("%d\n", sizeof(&a));		  //4/8
    //&数组名取出的是整个数组的地址,但是仍为地址,所以大小为4/8
	printf("%d\n", sizeof(*&a));	  //16
    //*&a相当于a,sizeof(数组名)计算的是整个数组的大小,为16
	printf("%d\n", sizeof(&a + 1));	  //4/8
    //&a+1指向4后面一个元素,但是&a+1是指针,所以大小为4/8
	printf("%d\n", sizeof(&a[0]));	  //4/8
    //首元素地址,大小为4/8
	printf("%d\n", sizeof(&a[0] + 1));//4/8
    //&a[0]+1指向元素2,但他也是指针,所以大小为4/8
	return 0;
}

经过对一维数组的分析, 我们可以知道:

1、在判断时,只要是指针,那么他的大小即为4/8,32位为4,64位为8,可以加快我们做题判断的速度。

2、指针的步长取决于指针的类型,指针的类型为int,那么指针加+1就是跳过一个int类型,指针类型为数组,那么指针+1就是跳过整个数组。

二、字符数组

字符数组的分析除了sizeof我们再引入strlen库函数进行更深入的分析。

strlen函数的参数为const char*,功能为统计'\0'前的元素个数。

int main()
{
	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));        //6       
	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
	printf("%d\n", strlen(arr));        //随机
    //该字符数组不是字符串,'\0'的位置不确定,所以为随机值
	printf("%d\n", strlen(arr + 0));    //随机
	printf("%d\n", strlen(*arr));       //err
    //strlen需要的参数是指针,而*arr是字符'a'。ASCII值为97
    //字符'a'的ASCII值为97,strlen会从97这个地址开始计算字符串长度
    //会造成非法访问
	printf("%d\n", strlen(arr[1]));     //err
	printf("%d\n", strlen(&arr));       //随机
	printf("%d\n", strlen(&arr + 1));   //随机
	printf("%d\n", strlen(&arr[0] + 1));//随机

	char arr[] = "abcdef";
	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
	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));   //随机
    //该指针步长为该字符串,+1后指针指向'\0'的后一个位置,所以值为随机
	printf("%d\n", strlen(&arr[0] + 1));//5
    //该指针步长为一个字符,+1后指针指向字符'b',所以计算值为5

	char* p = "abcdef";
    //经过前面的学习我们知道p存放的是该字符串首个字符的地址
	printf("%d\n", sizeof(p));          //4/8
	printf("%d\n", sizeof(p + 1));      //4/8
	printf("%d\n", sizeof(*p));         //1
	printf("%d\n", sizeof(p[0]));       //1
	printf("%d\n", sizeof(&p));         //4/8
	printf("%d\n", sizeof(&p + 1));     //4/8
    //思考:这里指针指向了哪里??
	printf("%d\n", sizeof(&p[0] + 1));  //4/8
	printf("%d\n", strlen(p));          //6
	printf("%d\n", strlen(p + 1));      //5
	printf("%d\n", strlen(*p));         //err
	printf("%d\n", strlen(p[0]));       //err
	printf("%d\n", strlen(&p));         //随机
	printf("%d\n", strlen(&p + 1));     //随机
	printf("%d\n", strlen(&p[0] + 1));  //5
	return 0;
}

char* p="hello world";

字符指针p存放的是该字符串首个字符的地址。

我想单独讲解一下这段代码:

int main
{
    char* p = "abcdef";
    printf("%d\n", sizeof(&p));         //4/8
    printf("%d\n", sizeof(&p + 1));     //4/8
    //思考:这里指针指向了哪里??
    return 0;
}

首先我们知道p指向的是字符'a',即p的类型为char*,在&p后,&p的类型就是char**,此时给&p+1,&p的步长应为char*,也就是指针&p+1相对于&p跳过了一个char* ,比如p的地址是0x0012ff40,如图:

【C语言】进阶指针(三)—>指针与数组笔试真题详解_第1张图片

 也就是&p是指针的指针,所以他的步长应为一个指针的长度

其中相似原理的我并未重复注释,大家有问题的可以评论或者私信问我

三、二维数组

首先我们需要知道: 

假定一个二维数组int a[3][4],对于二维数组a来说,a[0]就是该二维数组第一行的一维数组的数组名,同理a[1]就是该二维数组第二行的一维数组的数组名,又因为数组名就是数组首元素的地址,所以a[0]就是该二维数组第一行的一维数组的首元素地址,同理a[1]就是该二维数组第二行的一维数组的首元素地址。

在了解了上面的概念后,接下来的代码就迎刃而解了。

int main()
{
	//二维数组
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));           //48
	printf("%d\n", sizeof(a[0][0]));     //4
	printf("%d\n", sizeof(a[0]));        //16
	printf("%d\n", sizeof(a[0] + 1));    //4/8
    //即a[0]其实就是a[0][0]的地址,+1后变为a[0][1]的地址,只要是地址,大小就是4/8
	printf("%d\n", sizeof(*(a[0] + 1))); //4
	printf("%d\n", sizeof(a + 1));       //4/8
	printf("%d\n", sizeof(*(a + 1)));    //16
	printf("%d\n", sizeof(&a[0] + 1));   //4/8
	printf("%d\n", sizeof(*(&a[0] + 1)));//16
    //a[0]是第一行数组的数组名,所以&a[0]取出的是该一维数组的地址
    //+1后指向了第二行的一维数组,解引用后是第二行一维数组,大小为4*4=16
	printf("%d\n", sizeof(*a));          //16
	printf("%d\n", sizeof(a[3]));        //16
    //越界会影响sizeof的计算么??
	return 0;
}

 sizeof是如何计算的?

我们发现最后一个题目中数组越界了,但并没有影响程序的运行,也就是说,sizeof的计算只考虑类型,不会真的访问计算,a[3]的类型是int [4],所以大小为4*4=16。

四、指针笔试题

笔试题(1)

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代表首元素地址,指针类型为int*,步长为int,+1后指向元素'2',解引用后为元素'2',所以打印值为2。

&a拿到的是整个数组的地址,指针类型为int(*)[5],步长为int [5],+1后ptr指向元素'5'后面的位置,并将指针强制转换为int*类型,ptr指针类型为int*,步长为int,-1后ptr指向元素'5',解引用得到元素'5',所以打印值为5。


笔试题(2)

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

答案:0x100014 0x100001 0x100004 

分析:

p的类型为结构体指针struct Test*,步长为struct Test,0x1是十六进制数字,转为十进制为1,+1后相当于+20(整型),转化为十六进制加到0x100000后即为0x100014。

p被转化为数字,+1就是0x100000+1,为0x100001。

p被转化为unsigned int*类型,步长为4个字节,+1就是跳过4个字节,即0x100000+4=0x100004。


笔试题(3)

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;
}
//程序的结果是什么?

 答案:4,2000000

对于ptr1:

&a+1指针指向元素'4'后面的位置,将该指针强制转换为int*类型,ptr1[-1]相当于*(ptr1-1),又因为ptr1的步长为int,所以解引用得到元素为'4',用%x(十六进制打印)得到值为4。

对于ptr2:

涉及到了内存的存储,为了更好地讲解,请看下图:

【C语言】进阶指针(三)—>指针与数组笔试真题详解_第2张图片


笔试题(4)

int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int* p;
    p = a[0];
    printf("%d", p[0]);
    return 0;
}
//程序输出的结果是什么?

答案:1

这里有一个陷阱,我们知道二维数组的定义方法应为int a[3][2]={{0,1},{2,3},{4,5}}; 

但是本题为int a[3][2] = { (0, 1), (2, 3), (4, 5) };

他实际上为int a[3][2] = { 1, 3, 5 };

即:

【C语言】进阶指针(三)—>指针与数组笔试真题详解_第3张图片

 所以值为1。


笔试题(5)

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;
}
//程序的结果是什么?

答案:FFFFFFFC,-4 

 根据前面学过的知识,我们知道p是数组指针,指向的元素类型为int [4]。

我们将p[4][2]也理解为二维数组,那么&p[4][2]就是指向p数组第5行第三列的元素,即为图中所示。

【C语言】进阶指针(三)—>指针与数组笔试真题详解_第4张图片

指针相减得到两个指针之间的元素个数,又因为地址是由低到高变化的,所以小地址减去大地址得到负数,为-4。

 %p是打印地址的操作符,又因为-4在内存中存储的是补码:

1111 1111 1111 1111 1111 1111 1111 1100  //-4的补码

   F      F      F      F      F      F      F      C   

转化为16进制数字0xFFFFFFFC


笔试题(6)

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,5 

ptr1:

&aa取出的是整个数组的地址,+1指向元素'10'后面的元素,并强制转化为int*类型,打印时-1指向元素'10',解引用得到元素'10',所以值为10。

ptr2:

aa+1指向二维数组的第二行的一维数组,解引用得到该一维数组首元素的地址,也可以将*(aa+1)理解为aa[1],即为6的地址,打印时-1指向元素'5',解引用得到元素'5',所以值为5。

解引用一个数组(非数组名)的地址,得到的是该数组首元素的地址。


笔试题(7)

int main()
{
     char *a[] = {"work","at","alibaba"};
     char**pa = a;
     pa++;
     printf("%s\n", *pa);
     return 0;
}
//程序的结果是什么?

答案:at 

 a本质上是指针数组,该数组存放着三个字符串首个字符的地址。

pa指向的是数组a的首元素的地址,所以pa是一个二级指针,pa指向指向字符'w'的指针。

即:

【C语言】进阶指针(三)—>指针与数组笔试真题详解_第5张图片

 所以得到的值为at。


笔试题(8)

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  ER  ST  EW

与第七题同理,难度略高。

【C语言】进阶指针(三)—>指针与数组笔试真题详解_第6张图片

 如图,当我们知道指针之间的关系后,那么还需要攻克的就是符号优先级的问题。

1、**++cpp,++的优先级高于*,所以cpp先+1在连续两次解引用,得到元素'P'的地址,打印得到POINT。

2、*--*++cpp+3,cpp先++,再解引用,再--,再解引用,最后+3,得到元素'E'的地址,打印得到ER。

3、*cpp[-2]+3,可以理解为**(cpp-2)+3,得到元素'S'的地址,打印得到ST。

4、cpp[-1][-1]+1,可以理解为*(*(cpp-1)-1) +1,得到元素'E'的地址,打印得到EW。


小结:进阶指针的学习到这里就全部结束了,想要学习关于进阶指针的全部知识,欢迎到我的主页查询进阶指针(一)与进阶指针(二),接下来博主会持续更新C语言的更多知识, 关注博主不迷路

你可能感兴趣的:(C语言,c语言,算法,数据结构,学习,程序人生)