C语言-指针进阶-常见笔试面试题详解(9.4)

简介: C语言-指针进阶-常见笔试面试题详解(9.4)

思维导图:



指针和数组笔试题

只有多刷题,才能巩固提高所学的知识。


例1:


#include 
int main()
{
  //一维数组
  int a[] = { 1,2,3,4 };
    //求出下列打印结果及原因
  printf("%d\n", sizeof(a));
  //sizeof(数组名)数组名代表整个数组,数组每个元素是整形,整形大小4个字节,所以打印16
  printf("%d\n", sizeof(a + 0));
  //数组名表示首元素地址,+0还是首元素地址,指针大小4/8,x86环境打印4
                                                      //我使用的是x86环境 
  printf("%d\n", sizeof(*a));
  //a是首元素地址,*a是首元素,整形大小4个字节,打印4
  printf("%d\n", sizeof(a + 1));
  //a是首元素地址,+1跳过一个整形,代表第二个元素地址,指针大小4/8,打印4
  printf("%d\n", sizeof(a[1]));
  //a[1]是数组第二个元素,就是*(a+1),整形大小4个字节,打印4
  printf("%d\n", sizeof(&a));
  //&a取出的是整个数组的地址,指针大小4/8,打印4
  printf("%d\n", sizeof(*&a));
  //&a取出的是整个数组的地址,*&a解引用出的是整个数组,数组四个整形元素,打印16
  printf("%d\n", sizeof(&a + 1));
  //&a取出整个数组的地址,+1跳过一整个数组,但他还是指针,指针大小4/8,打印4
  printf("%d\n", sizeof(&a[0]));
  //&a[0]取出数组首元素地址,指针大小4/8,打印4
  printf("%d\n", sizeof(&a[0] + 1));
  //&a[0]取出数组首元素地址,+1跳过一个整形,是数组第二个元素的地址,指针大小4/8,打印4
  return 0;
}


输出:


输出:
16
4
4
4
4
4
16
4
4
4

学完上述题目,我们发现了一些特点:


1.sizeof(数组名),数组名表示整个数组,


2.&数组名,取出的是整个数组,


3.除上述两种情况外,数组名都表示数组首元素地址。


例2:


#include 
int main()
{
  //字符数组
  char arr[] = { 'a','b','c','d','e','f' };
  //求出下列打印结果及原因
  printf("%d\n", sizeof(arr));
  //sizeof(arr),arr代表整个数组,数组每个元素是字符,打印6            
  printf("%d\n", sizeof(arr + 0));
  //arr是首元素地址,+0后还是地址,指针大小4/8,打印4
  printf("%d\n", sizeof(*arr));
  //arr是首元素地址,*arr解引用后得到首元素,数组元素是字符,打印1
  printf("%d\n", sizeof(arr[1]));
  //arr[1]是数组的第二个元素,数组每个元素是字符,打印1
  printf("%d\n", sizeof(&arr));
  //&arr取出的是整个元素的地址,指针大小4/8,打印4
  printf("%d\n", sizeof(&arr + 1));
  //&arr取出的是整个元素的地址,+1跳过一整个数组,但还是地址,指针大小4/8,打印4
  printf("%d\n", sizeof(&arr[0] + 1));
  //&arr[0]取出的数组首元素地址,+1跳过一个字节指向数组第二个元素地址,指针大小4/8,打印4
  return 0;
}

输出:


输出:
6
4
1
1
4
4
4

例三:

#include 
#include 
int main()
{
  //字符数组
  char arr[] = "abcdef";//该字符串在内存中存储的是:"abcdef\0"
  //求出下列打印结果及原因
  //sizeof
  printf("%d\n", sizeof(arr));
  //sizeof(arr)代表整个数组,数组有七个元素,类型是字符,打印7
  printf("%d\n", sizeof(arr + 0));
  //arr是首元素地址,+0后还是首元素地址,指针大小4/8,打印4
  printf("%d\n", sizeof(*arr));
  //arr是首元素地址,*解引用后得到首元素,类型是字符,打印1
  printf("%d\n", sizeof(arr[1]));
  //arr[1]是首元素,类型是字符,打印1
  printf("%d\n", sizeof(&arr));
  //&arr取出的整个数组的地址,指针大小4/8,打印4
  printf("%d\n", sizeof(&arr + 1));
  //&arr取出的整个数组的地址,+1跳过一整个数组,但还是地址,指针大小4/8,打印4
  printf("%d\n", sizeof(&arr[0] + 1));
  //&arr[0]取出的数组首元素地址,+1跳过一个字节指向数组第二个元素地址,指针大小4/8,打印4
  printf("\n");
  //strlen
  printf("%d\n", strlen(arr));
  //arr是首元素地址,strlen通过首元素地址查找整个字符串,遇到\0停下,求出字符长度,打印6
  printf("%d\n", strlen(arr + 0));
  //arr是首元素地址,+0还是首元素地址,打印6
  //printf("%d\n", strlen(*arr));//错误代码
  //arr是首元素地址,*解引用后得到首元素‘a’,‘a’不是地址,程序非法访问内存空间
  //printf("%d\n", strlen(arr[1]));//错误代码
  //arr[1]是数组第二个元素'b','b'不是地址,程序非法访问内存空间
  printf("%d\n", strlen(&arr));
  //&arr取出的是整个数组的地址,但内存中保存的是首元素地址,所以打印6
  printf("%d\n", strlen(&arr + 1));
  //&arr取出整个数组的地址,+1跳过整个数组,指针指向数组后的空间,
  //strlen只有找到\0才会停下,所以程序打印结果是随机值
  printf("%d\n", strlen(&arr[0] + 1));
  //&arr[0]取出的数组首元素地址,+1跳过一个字节指向数组第二个元素地址,
  //strlen将数组第二个元素的地址作为数组的首元素地址,打印5
  return 0;
}


/


输出:


输出:
7
4
1
1
4
4
4
6
6
6
12
5

tips:


1.strlen 函数将传来的地址作为起始地址。


2.strlen 函数只有遇到'\0'时才会停下来。


例4:


#include 
#include 
int main()
{
  //字符指针
  char* p = "abcdef";
  //求出下列打印结果及原因
  //sizeof
  printf("%d\n", sizeof(p));
  //p是个指针变量,指针大小4/8,打印4
  printf("%d\n", sizeof(p + 1));
  //p是个字符类型的指针变量,+1跳过一个字节,指针大小4/8,打印4
  printf("%d\n", sizeof(*p));
  //p是个指向字符串首字符的指针,*解引用得到字符串首字符,打印1
  printf("%d\n", sizeof(p[0]));
  //p[0]其实也能写成*(p+0),得到字符串首字符,打印1
  printf("%d\n", sizeof(&p));
  //p是个指针变量,&p是取p的地址,指针大小4/8,打印4
  printf("%d\n", sizeof(&p + 1));
  //p是个指针变量,&p+1还是地址,指针大小4/8,打印4
  printf("%d\n", sizeof(&p[0] + 1));
  //&p[0]取出的是个地址,+1还是地址,指针大小4/8,打印4
  printf("\n");
  //strlen
  printf("%d\n", strlen(p));
  //p是个指向字符串首字符的指针,字符串有6个字符,打印6
  printf("%d\n", strlen(p + 1));
  //p是个指向字符串首字符的指针,+1跳过一个字节,指向字符串第二个字符的地址,打印5
  //printf("%d\n", strlen(*p));//错误代码
  //p是个指向字符串首字符的指针,*解引用得到字符‘a’,'a'不是地址,程序非法访问内存
  //printf("%d\n", strlen(p[0]));//错误代码
  //p[0]代表字符串首字符‘a’,‘a’不是地址,程序非法访问内存
  printf("%d\n", strlen(&p));
  //p是个指针变量,&p取出p的地址,strlen找到\0才停下,打印随机值
  printf("%d\n", strlen(&p + 1));
  //和上一个类似,只是地址+1,打印随机值
  printf("%d\n", strlen(&p[0] + 1));
  //&p[0]取出的字符串首字符地址,+1跳过一个字节,打印5
  return 0;
}


输出:


输出:
4
4
1
1
4
4
4
6
5
15
11
5

接下来是一道有关二维数组的题目:


例5:


#include 
#include 
int main()
{
  //二维数组
  int a[3][4] = { 0 };
  //求出下列打印结果及原因
  printf("%d\n", sizeof(a));
  //sizeof(a)表示的是整个数组,数组大小是3*4*每个元素4个字节=48,打印48
  printf("%d\n", sizeof(a[0][0]));
  //arr[0][0]代表的是数组首元素,元素类型是整形,打印4
  printf("%d\n", sizeof(a[0]));
  //arr[0]是a[3][4]数组第一行的元素,也就是第一行的数组名,代表整个第一行数组,打印16
  printf("%d\n", sizeof(a[0] + 1));
  //arr[0]是数组第一行的元素,+1跳过一行,所以arr[0]+1是数组第二行的数组名,
  //数组名表示首元素地址,指针大小4/8,打印4
  printf("%d\n", sizeof(*(a[0] + 1)));
  //arr[0]+1是数组第二行的首元素地址,*解引用的到首元素,类型是整形,打印4
  printf("%d\n", sizeof(a + 1));
  //a代表数组首行地址,+1跳过一行,代表数组第二行地址,指针大小4/8,打印4
  printf("%d\n", sizeof(*(a + 1)));
  //a + 1代表数组第二行的地址,*解引用得到第二行,打印16
  printf("%d\n", sizeof(&a[0] + 1));
  //&a[0]取出数组第一行地址,+1跳过一行,代表数组第二行地址,指针大小4/8,打印4
  printf("%d\n", sizeof(*(&a[0] + 1)));
  //&a[0] + 1代表数组第二行地址,*解引用得到得到第二行,打印16
  printf("%d\n", sizeof(*a));
  //a是首行的地址,*解引用后得到第一行,打印16
  printf("%d\n", sizeof(a[3]));
  //arr[3]不在数组内,但是sizeof判断大小不会真的去访问内存,他会通过类型判断
  //arr[3]看起来像数组的一行,所以sizeof默认他为数组一行的大小,打印16
  return 0;
}

输出:


输出:
48
4
16
4
4
4
16
4
16
16


总结:


二维数组的数组名代表首行地址。


做完上面的题目,相信你一定对数组有了更深层次的理解,


也对sizeof 和strlen 的理解更透彻了,


那么我们接下来就要尝试做一做指针的题目了。


指针笔试题

例1:


#include 
int main()
{
  int a[5] = { 1, 2, 3, 4, 5 };
  int* ptr = (int*)(&a + 1);
  //&a + 1跳过一整个数组,然后强制类型转换成(int*)
  //所以指针变量ptr存放的是数组往后一个元素
  printf("%d,%d", *(a + 1), *(ptr - 1));
  //a时首元素地址,+1得到第二个元素的地址,*解引用得到2,打印2
  //(ptr-1)想前跳一个整形的大小,*解引用得到5,打印5
  return 0;
}


输出:


输出:2,5

例2:


#include 
//已知,结构体Test类型的变量大小是20个字节
struct Test
{
  int Num;
  char* pcName;
  short sDate;
  char cha[2];
  short sBa[4];
}*p;
int main()
{
  p = (struct Test*)0x100000;
  printf("%p\n", p + 0x1);
  //结构体Test的指针+1,跳过20个字节,转换成十六进制是0x00100014
  printf("%p\n", (unsigned long)p + 0x1);
  //p被强制类型转换成(unsigned long),整形+1,结果是0x00100001
  printf("%p\n", (unsigned int*)p + 0x1);
  //p被强制类型转换成(unsigned int*),无符号整形指针+1,跳过4个字节,结果是0x00100004
  return 0;
}

输出:


输出:
00100014
00100001
00100004

注:


指针+1才是跳过类型所占的字节大小,


整形加整形科是直接相加的,可别被绕进去了哦。


例3:


#include 
int main()
{
  int a[4] = { 1, 2, 3, 4 };
  int* ptr1 = (int*)(&a + 1);
  //(&a + 1)跳过整个数组,ptr1指向的是数组后一个元素
  int* ptr2 = (int*)((int)a + 1);
  //a是首元素地址,强制类型转换成整形后+1,代表整形1的地址从起始位置往后跳了一个字节
  //所以ptr2指针指向的是整形1的地址从起始位置往后跳了一个字节的位置
  printf("%x,%x", ptr1[-1], *ptr2);
  //prt1[-1]就是*(ptr1-1),向前跳一个整形,指向数组最后一个元素,*解引用后,打印4
  //我是小端环境,数据在内存中是倒着放的,指针指向整形1往后一个字节,访问的时候
  //指针访问四个字节,就将数组下一个元素整形2的第一个字节也包含了,所以打印20000000
  return 0;
}



输出:


输出:4,2000000

例4:


#include 
int main()
{
  int a[3][2] = { (0, 1), (2, 3), (4, 5) };
  //(0,1),看清楚,这是个逗号表达式,所以
  //数组a其实存放的是{ 1, 3, 5 }
  int* p;
  p = a[0];
  //指针p中存储的是数组第一行地址
  printf("%d", p[0]);
  //p[0]是第一行的首元素,所以打印1
  return 0;
}

输出:


输出:1

例5:


#include 
int main()
{
  int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  //数组第一行:1 2 3 4 5
  //数组第二行:6 7 8 9 10
  int* ptr1 = (int*)(&aa + 1);
  //&aa+1跳过整个数组
  int* ptr2 = (int*)(*(aa + 1));
  //aa是数组首行地址,+1得到第二行地址
  printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
  //ptr-1向前跳一个整形,得到数组最后一个元素地址,*解引用得到10
  //ptr2-1向前跳一个整形,得到数组第一行最后一个元素地址,*解引用得到5
  return 0;
}


输出:


输出:10,5

例6:


#include 
int main()
{
  char* a[] = { "work","at","alibaba" };
  //这个指针数组里存放的是三个字符串的首元素地址
  char** pa = a;
  //二级指针pa接收了数组a首元素地址
  pa++;
  //指针++,跳过4个字节,
  printf("%s\n", *pa);
  //*pa指向第二个字符串的首元素地址,打印at
  return 0;
}


输出:


输出:at

例7:


#include 
int main()
{
  char* c[] = { "ENTER","NEW","POINT","FIRST" };
  //数组中存放四个字符串的首元素地址
  char** cp[] = { c + 3,c + 2,c + 1,c };
  //这个二级指针数组存放的字符串数组分别是:
  //{ "FIRST", "POINT", "NEW", "ENTER" }
  char*** cpp = cp;
  //三级指针cpp指向cp数组的首元素地址
  printf("%s\n", **++cpp);
  //cpp++,cpp指向cp数组第二个元素地址,*第一次解引用得到cp数组第二个元素(c+2)
  //*第二次解引用cp数组第二个元素,得到c数组第三个元素,打印 POINT
  printf("%s\n", *-- * ++cpp + 3);
  //cpp++,cpp指向cp数组第三个元素地址,*第一次解引用得到cp数组第三个元素(c+1)
  //(c+1)--后等于c,再解引用得到c数组第一个元素,然后+3跳过三个字节,打印 ER
  printf("%s\n", *cpp[-2] + 3);
  //*cpp[-2]就是**(cpp-2),cpp-2得到cp数组首元素地址,*第一次解引用得到cp数组第二个元素(c+3)
  //*第二次解引用cp数组第一个元素,得到c数组第四个元素,+3跳过三个字节,打印 ST
  printf("%s\n", cpp[-1][-1] + 1);
  //cpp[-1][-1]就是*(*(cpp-1)-1),cpp-1指向cp数组第二个元素地址,*得到cp数组第二个元素(c+2)
  //然后-1等于(c+1),*得到c数组第二个元素,+1跳过一个字节,打印 EW
  return 0;
}

输出:


输出:
POINT
ER
ST
EW

学到这里,相信你对指针的理解一定更上一层楼了!


写在最后:

以上就是本篇文章的内容了,感谢你的阅读。


如果喜欢本文的话,欢迎点赞和评论,写下你的见解。


如果想和我一起学习编程,不妨点个关注,我们一起学习,一同成长。



相关文章
|
1月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
104 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
1月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
88 9
|
1月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
53 7
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
221 13
|
2月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
180 3
|
2月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
2月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
57 1
|
2月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
2月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。