深度讲解指针的笔试题目

简介: 主页:C语言的前男友知识讲解:C语言指针创作者:C语言的前男友开发环境:VS2022前言:前面学了好久的指针,今天来看一些组织的面试题,来帮助我们加深理解。今天主要看一些关于sizeof(),和strlen(),对数组,字符串的操作。通过练习深度理解指针。深刻理解数组与指针,数组名与指针,字符串与指针,二维数组指针之间的关系。欢迎大家前来指正,如果觉得作者写的还不错的话,请麻烦动动发财的小手,关注,点赞,收藏,评论

2085e93f84af423885c53ac9e106fc53.png


一.一维数组

    int a[] = { 1,2,3,4 };
  printf("%d\n", sizeof(a));  
  printf("%d\n", sizeof(a + 0)); 
  printf("%d\n", sizeof(*a));   
  printf("%d\n", sizeof(a + 1));   
  printf("%d\n", sizeof(a[1]));  
  printf("%d\n", sizeof(&a));   
  printf("%d\n", sizeof(*&a)); 
  printf("%d\n", sizeof(&a + 1));  
  printf("%d\n", sizeof(&a[0]));  
  printf("%d\n", sizeof(&a[0] + 1));  

(1) sizeof()的操作对象是数组名

数组名表示的是数组首元素的地址,但是有两个例外:


1.数组名是 sizeof 的操作对象

2. & + 数组名 ,


上述两种情况比较特殊,此时的数组名表示的是,整个数组。也就是说,sizeof(数组名)计算的是整个数组的大小,& + 数组名,代表是的取出整个数组的地址。

printf("%d\n", sizeof(a));
printf("%d\n", sizeof(*&a));

&a是数组的地址,是数组指针类型,*&a是都数组指针解引用,访问一个数组的大小。



(2)sizeof()操作对象是指针

针对 sizeof 这个操作符,是用来求类型的大小的(单位:字节),当然也可以求指针的大小,我们知道一个普通指针在内存中占 4 个字节或者 8 个字节,至于到底是几个字节,取决于计算机平台,在32位平台是 4 字节,在 64 位平台是 8 字节。

    printf("%d\n", sizeof(a + 0)); 
  printf("%d\n", sizeof(a + 1));   
  printf("%d\n", sizeof(&a));   
  printf("%d\n", sizeof(&a + 1));  
  printf("%d\n", sizeof(&a[0]));  
  printf("%d\n", sizeof(&a[0] + 1));  

这里(a+0)如果是 a 单独是sizeof的操作对象,那么此时 a 就是代表整个数组,但是(a+0)就不代表整个数组。(a+0)就是一个普通指针,指向数组的第一个元素。也就是数组的首元素的地址。(a+1)就是数组第二个元素的地址。(&a)也是一个指针,是一个数组指针类型的指针,但仍然是一个指针。(&a +1 ): &a 是取出了整个数组的地址,是一个数组指针类型的指针,(&a +1 )跳过整个数组的地址。(这里的针对指针加上一个整数的运算,后面会单拎出来好好讲讲)所以(&a+1)在实际上还是一个指针。是指针大小就是,4 或 8 字节。&arr[0]实际上也是一样,拿到是首元素的地址,(&a[0] + 1):就是在首元素的指针上往后跳过一个元素,也就是第二个数组元素的地址,无论 &a[0] , &a[0] + 1都是指针类型。所以大小自然都是 4 或者 8 字节。

32平台(X86)展示:



64平台(X64 )展示:



(3)sizeof()的操作对象是普通变量或者普通变量类型

    printf("%d\n", sizeof(*a));
  printf("%d\n", sizeof(a[1]));


a并没有单独放在sizeof()里面,所以 a 是首元素的地址,(*a)也就是首元素了,a[1],数组访问第二个元素,都是每一个数组元素又都是 int 类型。所以这两个结果都是 4 。


ae2f78708bbc42a7a0c8ff1cb091ac56.png


二.指针类型对指针的影响

刚才我们遇到了,&a + 1 是越过了整个数组的地址,那到底为什么呢?下面我们就来好好分析一下。

1.指针的类型决定了指针在加减一个整数,指针移动的空间长度。

举例:

int main()
{
  int a = 0;
  char ch = 0;
  int arr[3] = { 0 };
  int* pa = &a;
  char* pc = &ch;
  int(*parr)[3] = &arr;
  printf("pa=%p,pa+1=%p\n", pa, pa + 1);
  printf("pc=%p,pc+1=%p\n", pc, pc + 1);
  printf("parr=%p,parr+1=%p\n", parr, parr + 1);
  return 0;
}


15571b4d235948569fac55157cf95457.png


这个地方明显看出,pa 是整形指针,pa + 1只越过了 4 个字节。pc 是一个字符型指针,pc + 1越过了 1 个字节。parr 是一个数组指针,数组有三个元素,每个元素都是一个 int 型数据。所以数组的大小是 4*3=12,所以 parr + 1 越过了 12 个字节。

2.指针的类型决定了指针在解引用时,一次能访问多少字节的空间。

举例:

int main()
{
    int a = 0x11223344;
    int* pa = &a;
    char* pc = &a;
    short* ps = &a;
    printf("%x\n", *pa);
    printf("%x\n", *ps);
    printf("%x\n", *pc);
    return 0;
}

我们将十六进制数 11223344 存入a的四个字节空间,因为pa是整形指针,(*pa)一次就可以访问四个字节,所以,十六进制打印出来的(*pa)就是原数据11223344,ps 是短整型指针,(*ps)一次就可以访问两个字节,由于我的机器采用的是小端存储模式,所以在存储时是将数据的权重低位,放在了地址的低地址处,而读取内存还是从地址在高地址读取,所以十六进制打印出来的恢复数据的高低位就是 3344 ;pc是 char * 类型,(*pc)一次访问一个字节,也就是内存低位的一个字节,在我的机器上就是数据的权重低位,也就是44;


a2292304d39646ed9b43de1465e4da47.png


956d193226ab4699961e2e849810bf17.png


三.字符数组

    char arr[] = { 'a','b','c','d','e','f' };
  printf("%d\n", strlen(arr)); 
  printf("%d\n", strlen(arr + 0));
  printf("%d\n", strlen(*arr));
  printf("%d\n", strlen(arr[1]));
  printf("%d\n", strlen(&arr));
  printf("%d\n", strlen(&arr + 1));
  printf("%d\n", strlen(&arr[0] + 1));

(1)strlen()的底层原理

strlen()是一个计算字符串长度的库函数,计算的原理是:接受字符串的首字符指针,直到找到字符串的结束标志也就是‘  \0 ’ 结束。


(2)strlen()和字符数组

由于字符数组的没有 ‘ \0 ’作为结束标志,所以strlen()函数在什么位置寻找到 ‘ \0 ’也是不能知道的。所以在计算长度的时候,自然也就是一个随机值。

    char arr[] = { 'a','b','c','d','e','f' };
  printf("%d\n", strlen(arr));
  // 从 arr 出开始找‘ \0 ’
  printf("%d\n", strlen(arr + 0));
  //仍然是从 arr 处开始找‘\0’  
  printf("%d\n", strlen(&arr));
  // 由于 &arr 和 arr 虽然类型不同,但是都是指向数组的首元素地址,
  // 都是从数组的首元素地址处开始的寻找‘ \0 ’
  printf("%d\n", strlen(&arr + 1));
  // &arr + 1 从越过整个数组后的地址处开始寻找‘ \0 ’
  printf("%d\n", strlen(&arr[0] + 1));
  // &arr[0]仍然是指向首元素的地址,&arr[0] + 1 是从数组第一个元素开始寻找‘\0’


63f0984e45bb4f8b9140d06cbaec5816.png


因为&arr+1 跨过了一个数组,所以strlen(&arr + 1)的随机值会比strlen(arr),


strlen(arr + 0),strlen(&arr) 小 6.而&arr[0]+1只只是跨过了一个字符,strlen(&arr[0]+1)所以会比strlen(arr),strlen(arr + 0),strlen(&arr) 小 1。

代码运行结果:



由于strlen()参数需要的是指针,所以下面的代码就是错误的,由于内存访问冲突引起的。

    printf("%d\n", strlen(*arr));
  printf("%d\n", strlen(arr[1]));

代码运行结果:



四.字符串数组

(1)sizeof()和字符串数组

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));

我们要知道字符串在数组里面是怎么存储的:



我们知道注意:

printf("%d\n", sizeof(arr));

这里的arr是整个数组,而这里的数组有在最后多存了一个‘ \0 ’。所以加上‘ \0 ’一共七个字节。

其他的还是和数组一样的分析。

运行结果:

X86环境



X64环境:



(2)strlen()和字符串数组

char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

这里大家只要注意,strlen()求字符串的长度的原理就行了。


六.二维数组

int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));

(1)二维数组的数组名

二维数组的数组名,还是和一维数组差不过,除了sizeof( 数组名 )和 & + 数组名。其他的数组名都是,首元素的地址。注意:这个时候二维数组的首元素,不再是第一个元素,而是把二维数组看成一维数组,每一行看作一个一维数组,那么数组名是首元素的地址,也就是第一行一维数组的地址。

int main()
{
  int a[3][4] = { 0 };
  printf("%d\n", sizeof(a));
  //sizeof(a)数组名代表整个数组,
  //代表求整个数组的大小。
  printf("%d\n", sizeof(a[0][0]));
  //代表是求第一个数组元素的大小。
  printf("%d\n", sizeof(a[0]));
  //a是二维数组的数组名,是数组的第一行的地址,
  // a[0]相当于*(a+0),找到了数组第一行。
  //所以sizeof(a[0])算的是二维数组第一行的大小。
  printf("%d\n", sizeof(a[0] + 1));
  //a[0]其实就是二维数组第一行的数组名,
  // (a[0]+1)就是一个指向二维第一行第二个元素的指针。
  //算的是一个指针的大小。
  printf("%d\n", sizeof(*(a[0] + 1)));
  //在(a[0]+1)的基础上在解引用,
  //就是二维第一行第二个元素。
  printf("%d\n", sizeof(a + 1));
  //a是一个指向第一行数组的指针,
  // a+1就是一个指向第二行数组的指针。
  //算的是一个指针的大小
  printf("%d\n", sizeof(*(a + 1)));
  //(a+1)是指向二维数组第二行的地址,
  // 解引用以后就是找到了第二行
  //所以算的是第二行的大小
  printf("%d\n", sizeof(&a[0] + 1));
  //&a[0]取出第一行的地址,
  //等价于数组名 a ,所以等价于 a + 1
  printf("%d\n", sizeof(*(&a[0] + 1)));
  //等价于*(a+1)
  printf("%d\n", sizeof(*a));
  //等价于a[0]
  printf("%d\n", sizeof(a[3]));
  //等价于 *(a+3),虽然数组没有第四行,
  // 但是*(a+3)类型能够确定,
  // 就是 int [4],就是整形四元素数组。
  return 0;
}

运行效果(X86):



最后:

月是人间散客,卿是人间绝色,亦是人间难得。


 

相关文章
|
7月前
|
C语言
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
|
7月前
|
存储 C语言 C++
C语言------------指针笔试题目深度剖析
C语言------------指针笔试题目深度剖析
51 1
|
4月前
|
存储 编译器 C语言
【C语言】指针练习题目
【C语言】指针练习题目
|
6月前
|
算法
【经典LeetCode算法题目专栏分类】【第7期】快慢指针与链表
【经典LeetCode算法题目专栏分类】【第7期】快慢指针与链表
|
6月前
|
算法 容器
【经典LeetCode算法题目专栏分类】【第1期】左右双指针系列:盛最多水的容器、接雨水、回文子串、三数之和
【经典LeetCode算法题目专栏分类】【第1期】左右双指针系列:盛最多水的容器、接雨水、回文子串、三数之和
|
6月前
|
存储 SQL 算法
LeetCode 题目 117:填充每个节点的下一个右侧节点指针 II
LeetCode 题目 117:填充每个节点的下一个右侧节点指针 II
|
6月前
|
存储 SQL 算法
LeetCode 题目 116:填充每个节点的下一个右侧节点指针
LeetCode 题目 116:填充每个节点的下一个右侧节点指针
|
6月前
|
存储 算法 数据挖掘
LeetCode 题目 88:双指针\直接\递归\插入排序\归并排序 实现合并两个有序数组
LeetCode 题目 88:双指针\直接\递归\插入排序\归并排序 实现合并两个有序数组
|
7月前
指针和数组笔试题目解析
指针和数组笔试题目解析
33 0