当前位置:   article > 正文

【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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

一个一个理解:

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

还是一个一个理解:
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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

还是一个一个的理解:
对于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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

还是一个一个理解,先理解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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里插入图片描述

第二题

#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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

由于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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

假设下图是整个数组在内存中的存储方式:
在这里插入图片描述

我们可以看到最后的打印,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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这个比较简单,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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

假设这个数组在内存中是这样存储的:
在这里插入图片描述
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这个题比较简单,&aa是整个数组的地址,加1之后是最后一个元素的后一个地址
ptr2 里面实际存的是第二行的第一个地址

第七题

#include <stdio.h>

int main() {
    char* a[] = {"work", "at", "alibaba"};
    char** pa = a;
    pa ++;
    printf("%s\n", *pa);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

根据前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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/164697
推荐阅读
相关标签
  

闽ICP备14008679号