【C语言】指针相关题目解析(更加深入的理解指针)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【C语言】指针相关题目解析(更加深入的理解指针)

指针和数组笔试题解析

第一题(一维数组)

关于数组名:

数组名是首元素的地址

但是有两个例外:

1.sizeof(数组名)-数组名表示的是整个数组,计算的是整个数组的大小,单位是字节

2.&数组名数组名也表示整个数组,取出的是整个数组的地址

除了以上两个例外之外所有的数组名都是首元素的地址

#include<stdio.h>
int main() {
    int a[] = {1,2,3,4};
    printf("%d\n",sizeof(a));//16
    printf("%d\n",sizeof(a + 0));//4/8
    printf("%d\n",sizeof(*a));//4
    printf("%d\n",sizeof(a + 1));//4/8
    printf("%d\n",sizeof(a[1]));//4
    printf("%d\n",sizeof(&a));//4/8
    printf("%d\n",sizeof(*&a));//16
    printf("%d\n",sizeof(&a + 1));//4/8
    printf("%d\n",&a[0]);//4/8
    printf("%d\n",&a[0] + 1);//4/8
    return 0;
}

一个一个理解:

sizeof(a),a作为一个数组名单独放在sizeof内部,计算的是数组的大小,单位是字节,16

sizeof(a+0),a并非单独放在sizeof内部,也没有&,所以说数组名a就是首元素的地址,a+0还是首元素的地址,是地址大小就是4/8

sizeof(*a) a是首元素的地址,*a就是首元素,sizeof(*a)就算的是首元素的大小,4

sizeof(a+1),a是首元素的大小,(a+1)是第二个元素的地址,sizeof(a+1)计算的是指针的大小4/8

sizeof(a[1]),a[1]就是数组的第二个元素,sizeof计算的大小是4.

sizeof(&a),&a取出的是数组的地址,数组的地址,也是地址,sizeof(&a)就是4/8

sizeof(*&a),&a是数组的地址,是数组指针类型,*&a是对数组解引用,访问的是一个数组的大小,16

sizeof(&a+1),&a是数组的地址,&a+1 是跳过整个数组,&a+1还是地址,是4/8

sizeof(&a[0]),a[0],是数组第一个元素的地址,&a[0]是第一个元素的地址,是4/8

sizeof(&a[0]+1),&a[0]是第一个元素的地址,&a[0]+1就是第二个元素的地址,是4/8

第二题(字符数组)

#include <stdio.h>
#include <string.h>
//字符数组
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));//随机值
    printf("%d\n",strlen(arr + 0));//随机值
    printf("%d\n",strlen(*arr));//err
    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));//随机值
    return 0;
}

还是一个一个理解:

sizeof是计算对象或者类型创建的对象所占内存空间的大小,单位是字节

sizeof 是一个操作符,不是函数

sizeof(arr), arr 是数组名,并且是单独放在sizeof内部,计算的是数组的总大小,单位是字节,6

sizeof(arr+0),arr是数组名,并非单独放在sizeof内部,arr表示首元素的地址,arr+0还是首元素的地址, 是地址大小就是4/8

sizeof(*arr),arr是首元素的地址,*arr就是首元素,sizeof计算的是首元素的大小,1

sizeof(arr[1]),arr[1]是数组的第二个元素,sizeof(arr[1]),计算的是第二个元素的大小, 1

sizeof(&arr),&arr-取出的是数组的地址,sizeof(&arr)计算的是数组的地址的大小,是地址就是4/8

sizeof(&arr+1), &arr是数组的地址,&arr+1跳过整个数组,指向’f’的后边,本质还是一个地址,是地址就是4/8

sizeof(&arr[0]+1),&arr[0]是’a’的地址,&arr[0]+1是’b’的地址,是地址就是 4/8

接下来是strlen的理解:

strlen求字符串长度的,计算的是字符串中\0之前出现的字符的个数,统计到\0为止,如果没有\0,会继续往后找

strlen 是一个库函数

strlen(arr),arr是数组名,但是没有放在sizeof内部,也没有取地址;arr就是首元素的地址,strlen得到arr后,从arr数组首元素的地方开始计算字符串的长度,直到\0,但是arr数组中没有\0,arr内存的后边是否有\0,在什么位置是不确定的,所以\0之前出现了多少个字符是随机的. 随机的

strlen(arr+0),arr是首元素的地址,arr+0还是首元素的地址, 随机值

strlen(*arr),arr 是首元素的地址,*arr是首元素’a’,97,strlen会把’a’的ASCII值97当成了地址,err会非法访问内存 err

strlen(arr[1]),arr[1]- ‘b’ - 98 err和上一个情况一样 err

strlen(&arr),&arr是数组的地址,数组的地址也是指向数组起始位置,和第一个一样 随机值

strlen(&arr+1),&arr+1是跳过整个数组,只是起始位置变了,也是随机值 随机值

strlen(&arr[0]+1), &arr[0]是取出第一个地址,然后加1跳过一个元素,也是随机值 随机值

第三题(字符串)

#include <stdio.h>
#include <cstring>
//字符串
int main() {
    char p[] = "abcdef";
    printf("%d\n",sizeof(p));//7
    printf("%d\n",sizeof(p+1));//4/8
    printf("%d\n",sizeof(*p));//1
    printf("%d\n",sizeof(p[1]));//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+0));//6
    printf("%d\n",strlen(*p));//err
    printf("%d\n",strlen(p[1]));//err
    printf("%d\n",strlen(&p));//6
    printf("%d\n",strlen(&p+1));//随机值
    printf("%d\n",strlen(&p[0]+1));//5
    return 0;
}

还是一个一个的理解:

对于sizeof的理解

sizeof(p),p是数组名,直接放在sizeof中,这里计算的是整个数组的大小,单位是字节 7

sizeof(p+1),这里的p表示首元素的地址,p+1表示第二个元素的地址,所以是4/8

sizeof(*p),*p是第一个元素,所以此处的大小是 1

sizeof(p[1]),p[1]访问的是第二个元素,此处表示具体的第二个元素是’b’这个字符 1

sizeof(&p),&p还是整个数组的地址,其实也就是第一个元素的地址,是地址就是4/8

sizeof(&p+1),这里和也是一个地址,是地址就是 4/8

sizeof(&[0]+1),这里还是一个地址,是地址就是 4/8

对于strlen的理解

strlen(p), p表示首元素的地址,所以从第一个元素可是找,找到\0为止,所以结果是 6

strlen(p+0),这个和第一个一样的情况6

strlen(*p),*p表示的是具体的元素, strlen接收的是地址,所以这个地方 err

strlen(p[1]),这个地方穿进去的也是具体的元素,所以还是 err

strlen(&p),取出的是整个数组的地址,也就是第一个元素的地址所以是 6

strlen(&p+1),这个地方是直接跳过整个数组,所以还是随机值

strlen(&p[0]+1),这个地方是将第二个元素的地址放进去,所以是 5

第四题

#include <stdio.h>
#include <cstring>
int main() {
    char *p = "abcdef";
    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+0));//6
    printf("%d\n",strlen(*p));//1
    printf("%d\n",strlen(p[0]));//1
    printf("%d\n",strlen(&p));//随机值
    printf("%d\n",strlen(&p+1));//随机值
    printf("%d\n",strlen(&p[0]+1));//5
    return 0;
}

还是一个一个理解,先理解sizeof

sizeof(p),这里的p是存的是第一个元素的地址,是地址就是4/8

sizeof(p+1),这里的p+1指向的是第二个元素的地址,是地址就是4/8

sizeof(*p),*p表示的是第一个元素,是char类型的,所以是 1

sizeof(p[0]),p[0]还是表示第一个元素,所以是 1

sizeof(&p),这里的&p是取出p的地址p中本来存的就是一个地址,所以这是一个二级指针,也是一个地址,是地址就是4/8

sizeof(&p+1),这里存的还是一个二级指针,是一个地址,地址就是4/8

sizeof(&p[0]+1),p[0]表示第一个元素,&p[0]表示的还是第一个元素的地址,加1之后表示的是第二个元素的地址

对于strlen的理解:

strlen(p), 这里是将首元素的地址存进去,所以是 6

strlen(p+0),这个和第一个一样,也是6

strlen(*p),p本来表示首元素的地址,这个地方*p表示的是第一个元素,所以 err

strlen(p[0]),和上一个的情况一样

strlen(&p),p本来就是一个指针变量,这里有&,所以这是一个二级指针,也是一个地址,但是这个地址具体从哪里开始的就不知道了,所以这是一个随机值

strlen(&p+1),这个地方由于我们不知道&p–>&p+1,之间有无\0,所以这也是一个随机值

`strlen(&p[0]+1),&p[0],表示的是第一个元素的地址,加1之后表示的是第二个元素的地址,所以这里的结果是 5

第五题(二维数组)

#include <stdio.h>
//二维数组
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
    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
    printf("%d\n",sizeof(*a));//16
    printf("%d\n",sizeof(a[3]));//16
    return 0;
}

sizeof(a),a是二维数组的数组名,数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节 48

sizeof(a[0][0]),a[0][0]是一个整型元素,大小是四个字节, 4

sizeof(a[0]), 把二维数组的每一行看做一维数组的时候,a[0]是第一行的数组名,第一行的数组名单独放在sizeof内部计算的是第一行的总大小,单位是字节 16

sizeof(a[0]+1),a[0]虽然是第一行的数组名,但是并非单独放在sizeof内部,a[0]作为第一行的数组名并非表示整个第一行这个数组,a[0]就是第一行首元素的地址,a[0]–>&a[0][0],a[0]+1跳过一个int,是a[0][1]的地址 4/8

sizeof(*(a[0]+1)),根据上个例子可以知道,那是一个地址,所以解引用后访问的是第一行第二个元素,是 4

sizeof(a+1),a是二维数组的数组名,没单独放在sizeof内部,也没有&,所以a就是数组首元素的地址,二维数组我们把它想象成一维数组,它的第一个元素就是二维数组的第一行,a就是第一行的地址,a+1就是第二行的地址,是地址就是4/8

sizeof(*(a+1)),a+1是第二行的地址,*(a+1)找到的就是第二行,所以这个表达式计算的就是第二行的大小 16

sizeof(&a[0]+1),&a[0]是第一行的地址,&a[0]+1就是第二行的地址,计算的是第二行的数组的大小 4/8

sizeof(*(&a[0]+1)),根据上一个可知这个访问的是第二行,所以是 16

sizeof(*a),a表示首元素的地址,就是第一行的地址,访问的就是第一行,所以大小是 16

sizeof(a[3]),代码没有问题,a[3]是二维数组的第四行,虽然没有第四行,但是类型能够确定,大小就是确定的大小就是一行的大小,单位是字节 16

任何一个表达式都有两个属性:值属性+类型属性

总结:

1. sizeof计算的时候只要遇见地址就是4/8

2. sizeof(数组名)表示的是整个数组的大小

3. &arr,也表示的是整个数组的大小

4.除了2和3之外的情况所有的情况数组名表示的都是首元素的大小

4.sizeof是一个操作符

5.strlen是一个库函数,形参是一个指针,所以传进去的应该是地址,这个库函数的作用就是看改地址到\0之间有几个字符.

指针笔试题

第一题

#include <stdio.h>
int main() {
    int a[5] = {1,2,3,4,5};
    int* ptr = (int*)(&a + 1);
    printf("%d %d\n", *(a+1), *(ptr - 1));
    return 0;
}

第二题

#include <stdio.h>
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);
    printf("%p\n",(unsigned long)p + 0x1);
    printf("%d\n",(unsigned int*)p + 0x1);
    return 0;
}

由于p里面存的是一个地址,第一个打印的时候是一个地址,如果让地址加1就直接跳过20个字节,第二个把p这个变量强转为数字,加1的话就是加1, 第三个将p强转为int*类型,加1就是跳过4个字节.

所以最后的结果是00100014, 00100001, 00100004;


第三题

#include <stdio.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;
}

假设下图是整个数组在内存中的存储方式:

我们可以看到最后的打印,ptr1先是&a这个是整个数组的地址,加1之后变为最后一个元素的后一个地址,然后将它强转为int类型,ptr2是现将这个地址转为为整数,然后加1最后转换为int类型造成的结果就是移动一个字节

ptr1[-1]--> *(ptr1-1)

ptr1[2]--> *(ptr1+2)

所以ptr1最后的指向就是

然后根据小端存储,结果是00 00 00 04,前面的0全部省略是4

根据理解可以知道ptr2最后的地址是如图所示:

访问4个字节就是02 00 00 00

第四题

#include <stdio.h>
int main() {
//数组的初始化内容有逗号表达式,实际上数组初始化的是1,3,5
    int a[3][2] = {(0,1),(2,3),(4,5)};
    int* p;
    p = a[0];
    printf("%d\n",p[0]);
    return 0;
}

这个比较简单,a[0]代表的第一行的地址,第一行的地址又和第一个元素的地址一样,所以p中存的就是第一个元素的地址,p[0]--->*(p+0),所以这个地方打印的就是第一个元素

注意:逗号表达式的结果,就是最后一个运算的结果

第五题

#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[4][2]的位置在如图所示

这里a是首元素的地址,也就是第一行,将第一行的地址给p,虽然类型不匹配,但是这只是一个警告,我们无视即可,所以刚开始p指向的是第一个元素的地址,这里注意p的类型,所以p每次加1只能访问4个元素.

指针相减,求得是之间有几个元素,这里还要打印地址也就是-4,

地址在打印的时候打的是补码所以最后的结果是 FF FF FF FC,最后这里还有一个要注意的地方

第六题

#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是整个数组的地址,加1之后是最后一个元素的后一个地址

ptr2 里面实际存的是第二行的第一个地址

第七题

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

a是一个字符指针数组,因为a是首元素的地址,首元素就是一个地址,又要来保存首元素,所以要用一个二级指针,pa++,之后访问的就是第二个元素的地址(*pa)就是第二个元素的首地址,打印的自然是at

第八题

#include <stdio.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;
}

根据前3行我们可以画出如下的图:

接着就来一行一行的理解:


根据上图可以知道,cpp刚开始指向c+3位置的地址,加1之后指向c+2位置的地址,然后第一解引用找到c+2里面的元素是一个地址,然后在解引用还是一个地址,地址指向POINT

接着是*--*++cpp+3

由于刚才的++cpp已经影响到了cpp的指向,所以到这一行的时候cpp已经指向的不是首元素了

cpp先加加,指向c+1,然后解引用拿到c+1处的元素,然后再减减,指向c处的元素

,c处的元素指向的是存放ENTER的地址的地址,然后在解引用指向的是首元素,最后加3指向的是第二个E处的地址.所以最后打印ER

接着是*cpp[-2]+3,


像上图那样,*cpp[-2]+3 -- > **(cpp-2)+3,cpp刚开始的指向如上图所示,然后减2 指向c+3处,然后经过两次解引用指向FIRST,接着加3 所以最后打印ST

最后就是cpp[-1][-1]+1

cpp刚开始的指向还是如上图所示,cpp[-1][-1]--> *(*(cpp-1)-1),cpp先减1指向的是c+2处的地址解引用,拿到的是一块地址,然后减1 变成c+1, 之后在解引用,之后在加1所以最后打印的是EW

相关文章
|
1月前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
145 14
|
1月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
59 9
|
1月前
|
存储 编译器 C语言
【C语言】数据类型全解析:编程效率提升的秘诀
在C语言中,合理选择和使用数据类型是编程的关键。通过深入理解基本数据类型和派生数据类型,掌握类型限定符和扩展技巧,可以编写出高效、稳定、可维护的代码。无论是在普通应用还是嵌入式系统中,数据类型的合理使用都能显著提升程序的性能和可靠性。
50 8
|
1月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
46 7
|
1月前
|
存储 算法 C语言
【C语言】深入浅出:C语言链表的全面解析
链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。
508 6
|
1月前
|
存储 网络协议 算法
【C语言】进制转换无难事:二进制、十进制、八进制与十六进制的全解析与实例
进制转换是计算机编程中常见的操作。在C语言中,了解如何在不同进制之间转换数据对于处理和显示数据非常重要。本文将详细介绍如何在二进制、十进制、八进制和十六进制之间进行转换。
42 5
|
1月前
|
C语言 开发者
【C语言】断言函数 -《深入解析C语言调试利器 !》
断言(assert)是一种调试工具,用于在程序运行时检查某些条件是否成立。如果条件不成立,断言会触发错误,并通常会终止程序的执行。断言有助于在开发和测试阶段捕捉逻辑错误。
44 5
|
21天前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
57 0
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
92 2
|
14天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

推荐镜像

更多