经典指针与数组笔试题——C语言

简介: 经典指针与数组笔试题——C语言

   学习这片文章中的知识点,可以加深大家对指针应用的理解,让大家更能轻松知道指针在各种情况下指向那个内存地址。

   文章开始之前 ,我们先来介绍一下一些必要的知识点 📢 :

···以下代码都是在64位编译器下测试的


数组名表示的意思

提个问题📢 :

int arr[5] = {1,2,3,4,5};

我们知道数组名arr是指针,指向一块地址,只有在一下两种情况下,arr表示整个数组:

  • sizeof(arr); 当出现sizeof的时候arr代表整个数组,计算的是整个数组的大小,所以sizeof(arr);运行的结果是20.
  • &arr; 这里的arr表示的也是整个数组,所以取出来的是整个数组的地址,当出现&arr+1的时候,表示跳过整个数组。如下图:

1.一维数组

#include <string.h>
#include <stdio.h>
int main()
{
  int arr[] = { 1,2,3,4,5 };
  printf("%d\n", sizeof(arr));//前面已经提过,此时表示整个数组的大小,int类型占4个字节,5个元素,所以结果为20
  printf("%d\n", sizeof(arr+0));//数组名指向第一个元素的地址,加0还是表示第一个元素的地址,既然是地址,那么大小只能是4/8,所以结果为8
  printf("%d\n", sizeof(*arr));//*arr解引用表示第一个元素‘1’,元素类型为int,所以大小为4
  printf("%d\n", sizeof(arr+1));//arr的类型为(int *),加1后就跳过了1个(int *)的大小,此时指针指向元素为2的地址处。因为依旧是指针,所以结果为8
  printf("%d\n", sizeof(arr[1]));//这个就表示第一个元素,因为类型是int,所以结果显然为4
  printf("%d\n", sizeof(&arr));//虽然&arr表示整个数组的大小,但是这里万不能过度理解,&arr依旧是一个地址,所以结果为8
  printf("%d\n", sizeof(*&arr));//*&arr也就等于arr,所以这个跟sizeof(arr),所以结果为20
  printf("%d\n", sizeof(&arr+1));//&arr表示整个数组的地址,加1以后也就跳过了一个数组,但依旧为指针,所以结果为8
  printf("%d\n", sizeof(&arr[0]));//这个表示第一个元素的地址,结果为8
  printf("%d\n", sizeof(&arr[0]+1));//&arr[0],表示第一个元素的地址,加1以后指针指向元素2的地址,依旧为指针,所以结果为8
  return 0;
}

2.字符数组

  学习之前,我们需要知道strlen函数返回的是字符串的长度,也就是字符串开头和终止空字符(‘\0’)之间的字符数。

#include <stdio.h>
#include <string.h>
int main()
{
  char arr[] = { 'a','b','c','d','e','f' };
  
  printf("%d\n", strlen(arr));//strlen计算的是截止到'\0'的字符数,由于arr数组里面没有字符'\0',
  //只能依靠数组空间后面遇到'\0',这种情况是随机的,所以结果也是一个随机值。
  
  printf("%d\n", strlen(arr + 0));//arr+0也就是第一个元素的地址,和前一个一样,结果也是随机值。
  
  //printf("%d\n", strlen(*arr));//strlen需要的参数是一个(char *)的地址,*arr也就是第一个元素'a',它的ASCII码值为97,
  //所以会将97作为地址传参进去,就会从97这个地址开始往后计算长度,就会造成非法访问空间,结果报错。
  
  //printf("%d\n", strlen(arr[1]));//原因同上,结果报错。
  printf("%d\n", strlen(&arr));//&arr是整个数组的地址,指向首元素,向后统计字符数,跟第一个情况一样,所以结果为随机值。
  printf("%d\n", strlen(&arr + 1));//&arr取出的是整个数组的地址,所以&arr+1也就是会跳过一个数组的大小,也就是6个字符,
  //结果为strlen(&arr)-6.
  printf("%d\n", strlen(&arr[0] + 1));//&arr[0]表示第一个元素的地址,&arr[0]+1也就是第二个元素的地址,结果为strlen(&arr)-1.
  printf("%d\n", sizeof(arr));//sizeof(数组名)表示整个数组的大小,也就是(数组类型大小 * 数组元素个数),结果为6.
  printf("%d\n", sizeof(arr + 0));//arr+0是数组首元素的地址,既然是地址,那么结果就位4/8,运行结果为8
  printf("%d\n", sizeof(*arr));//*arr是数组首元素'a',类型是char,所以结果为1
  printf("%d\n", sizeof(arr[1]));//arr[0] == *arr,同上 ,结果为1.
  printf("%d\n", sizeof(&arr));//&arr表示整个数组地址,指向首元素,因为是地址,所以结果为4/8,运行结果是8
  printf("%d\n", sizeof(&arr + 1));//&arr+1上面提过,指向数组最后一个元素的下一个地址,因为是地址,所以结果为4/8,运行结果是8
  printf("%d\n", sizeof(&arr[0] + 1));//&arr[0]+1表示数组的第二个元素,因为是地址,所以结果为4/8,运行结果是8
  return 0;
}

总结:

strlen计算的时候注意起始位置得找对,终止字符’\0’也要确定在那里。

sizeof只要遇到指针,只需要考虑是在哪一个环境下的编译器以此来判断是4还是8


下面的代码跟上面的大同小异,就不过多赘述了。

#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));//5
  printf("%d\n", sizeof(arr));//会将末尾没有显示出来的'\0'也统计进去,结果为7
  printf("%d\n", sizeof(arr + 0));//arr + 0是首元素的地址,是地址就是4/8个字节
  printf("%d\n", sizeof(*arr));//*arr其实就是首元素,1个字节
  printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,1个字节
  printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8个字节
  printf("%d\n", sizeof(&arr + 1));//&arr + 1是跳过一个数组的地址,是地址就是4/8个字节
  printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,是地址就是4/8个字节
  return 0;
}

3.二维数组

#include <stdio.h>
#include <string.h>
int main()
{
  int a[3][4] = { 0 };
  printf("%d\n", sizeof(a));//表示整个数组的大小,3*4*4=48,所以结果为48
  printf("%d\n", sizeof(a[0][0]));// 表示第一个元素,int类型,结果为4
  printf("%d\n", sizeof(a[0]));//数组a三行四列,a[0]表示第一行的地址,所以是算第一行的大小,4*4=16,结果为16
  printf("%d\n", sizeof(a[0] + 1));//a[0]+1表示第一行的第二个元素的地址,所以是4/8,运行结果为8
  printf("%d\n", sizeof(*(a[0] + 1)));//解引用后表示第一行的第二个元素,类型是int,所以结果是4
  printf("%d\n", sizeof(a + 1));//a是数组首元素的地址,是第一行的地址,类型为int(*)[4],
  //a+1就是第二行的地址,所以是4/8,运行结果为8
  printf("%d\n", sizeof(*(a + 1)));//解引用以后表示计算第二行的大小,也就是4*4=16,所以结果为16
  printf("%d\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址,类型是int(*)[4],加1就会跳过相同类型大小的空间,
  //也就是第二行,因为是地址,所以结果为8
  printf("%d\n", sizeof(*(&a[0] + 1)));//根据上一行可以知道,&a[0] + 1表示第二行,
  //解引用以后就是计算第二行的大小,4*4=16,所以结果为16
  printf("%d\n", sizeof(*a));//a表示第一行的地址,解引用就表示第一行,也就是16
  printf("%d\n", sizeof(a[3]));//a[3]虽然已经越界了,但是仍然是指向一块空间的指针,a的类型是int(*) [4],所以结果为16.
  return 0;
}

我们需要知道在int a[3][4] = { 0 };里面:

  • a的类型是int(*)[4],所以大小时16个字节。a表示这个二维数组的首元素地址和第一行的首元素地址。

笔试题

第一道

int main() 
{
  int a = 7;
  short s = 4; 
  printf("%d\n", sizeof(s = a + 2));
  return 0;
}

s=a+2的返回结果是s的值,也就是9,因为s的类型是short,所以sizeof(s = a + 2)也就相当于sizeof(short),所以运行结果为2.

第二道

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)表示跳过整个数组,此时指针指向元素5的后面的地址

为什么要进行强制类型转换呢?

原因是&a的类型是int()[5], 而不是我们想象的int*,因此要进行强制类型转换
所以也容易得到指针ptr指向元素5的后面的地址,所以
(ptr-1)也就是向后移动了一个(int*)大小的位置,移动后指向元素5的地址,运行结果为:2 5

第三道

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

通过上面的多学,可以轻松知道ptr1指向数组元素4的后面一个位置,所以ptr[-1]也就是指向元素4的位置。

如图所示,ptr2移动以后指向的地址所存储的数据是00 00 00 02,由于我的机器是小端存储,所以值应该为02 00 00 00,由于打印的是十六进制的数,转换一下,得到:0x1E8480

第四道

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

先来看一下int a[3][2] = { (0, 1), (2, 3), (4, 5) };括号表达式的结果是最后一个式子的结果,也就是{1,3,5};

也就是给a初始化为a[3][2] = {{1,3,5} {0,0,0}{0,0,0}};

指针p指向的是第一行的地址,也就是元素1所在的地址,所以结果为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]);//-4
  //10000000000000000000000000000100
  //11111111111111111111111111111011
  //1111 1111 1111 1111 1111 1111 1111 1100
  //F    F    F    F    F    F    F    C
  //0xFFFFFFFC
  return 0;
}

如上图所示,因为指针-指针的意义是两个指针之间的字符数量,所以&p[4][2] 和 &a[4][2]之间相差4个字符。所以结果是-4

由于数据是由补码的形式存储的,所以要将原码-4转换为补码是:

4的二进制为:10000000000000000000000000000100

4的反码为: 11111111111111111111111111111011

4的补码为:1111 1111 1111 1111 1111 1111 1111 1100

转换成十六进制也就是:0xFFFFFFFC

所以运行结果为:0xFFFFFFFC -4

第六道

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

第七道题

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

pa++可以看做是pa[1],也就是第二行的元素,所以运行结果为: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;
}
  • printf("%s\n", **++cpp);先算++cpp,此时cpp指针指向 数组cp的c+2的地址,然后*++cpp表示数组cp元素c+2,此时指向数组c中的"POINT"的地址,**++cpp表示数组c中的元素"POINT",所以结果为POINT
  • printf("%s\n", *-- * ++cpp + 3);运算符的优先级:++ * – * + ++cpp表示cpp指针指向 数组cp的c+1的地址(因为前一行已经自增,指向了c+2),*++cpp表示数组cp元素c+1,此时指向数组c中的"NEW"的地址,–*++cpp表示指针指向数组cp的元素c,++cpp表示指向数组c的元素"ENTER"的地址,也就是指向第一个E,++cpp+3表示指向第二个E,因为打印格式为%s,所以运行结果为ER
  • printf("%s\n", *cpp[-2] + 3);此时cpp指针经过前面两次自增,此时指向数组cp中的c+1处,cpp[-2]表示指向数组cp的c+3,*cpp[-2]表示指向数组c中的"FIRST",*cpp[-2] + 3表示指向数组c中的"FIRST"的S,所以运行结果为ST
  • printf("%s\n", cpp[-1][-1] + 1);先来看cpp[-1],此时指向数组cp的c+2处,cpp[-1][-1]表示指向数组c中的"POINT"然后再向后移动一个char*大小,也就是指向"NEW", cpp[-1][-1] + 1表示指向E,所以运行结果为EW。其实 cpp[-1][-1] + 1还可以看作*(*(cpp-1)-1)+1,运算步骤同上。

  😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看。😄

相关文章
|
22天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
74 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
22天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
47 9
|
22天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
41 7
|
22天前
|
传感器 算法 安全
【C语言】两个数组比较详解
比较两个数组在C语言中有多种实现方法,选择合适的方法取决于具体的应用场景和性能要求。从逐元素比较到使用`memcmp`函数,再到指针优化,每种方法都有其优点和适用范围。在嵌入式系统中,考虑性能和资源限制尤为重要。通过合理选择和优化,可以有效提高程序的运行效率和可靠性。
69 6
|
25天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
49 5
|
25天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
25天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
86 3
|
26天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
25天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
36 1
|
29天前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。