当前位置: 首页 > news >正文

指针——练习

sizeof和strlen

sizeof

sizeof是用来计算变量所占内存空间大小的,单位是字节,如果操作数是类型,计算的是使用类型创建的变量所占内存空间的大小。

sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据。

我们来看一下这个代码,它的运行结果是多少?

#include<stdio.h>
int main()
{int a = 10;printf("%zu\n", sizeof(a + 3.14));return 0;
}

a是int类型的数据,3.14是double类型的数据,两者进行运算时,会将int类型的数据提升为double类型,所以最后结果的类型是double类型,所以最后结果应该是8.

strlen()

strlen 是 C 语言库函数,功能是求字符串长度。函数原型如下:

 size_t strlen (const char * str);

统计的是从 strlen 函数的参数 str 中这个地址所指向的元素开始向后找,直到\0 之前字符串中字符的个数。
strlen 函数会一直向后找 \0 字符,直到找到为止,所以可能存在越界查找。

看以下代码,运行结果啥?

#include <stdio.h>
#include <string.h>int main()
{char arr1[3] = {'a', 'b', 'c'};char arr2[] = "abc";printf("%d\n", strlen(arr1));printf("%d\n", strlen(arr2));printf("%d\n", sizeof(arr1));printf("%d\n", sizeof(arr2));return 0;
}

arr1是一个字符数组,末尾没有'\0',而strlen会一直向后找'\0',直到找到,所以使用strlen(arr1)的结果应该是一个随机值。

arr2是一个字符串,字符串的末尾含有'\0',所以strlen(arr2)的结果为3。

我们之前说过,sizeof(数组名)中,数组名表示整个数组,计算结果使整个数组的大小。

所以,sizeof(arr1)=3,sizeof(arr2)=4.

sizeof和strlen对比

sizeofstrlen
1. sizeof 是操作符
2. sizeof 计算操作数所占内存的大小,单位是字节
3. 不关注内存中存放什么数据
1. strlen 是库函数,使用需要包含头文件 string.h
2. strlen 是求字符串长度的,统计的是 \0 之前字符的个数
3. 关注内存中是否有 \0,如果没有 \0,就会持续往后找,可能会越界

数组和指针试题

在此之前,还是提醒一下大家:

数组名的意义

  1. sizeof (数组名),数组名单独放在sizeof()中,这里的数组名表示整个数组,计算的是整个数组的大小。
  2. & 数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。
  • 题目1:下面代码的运行结果
int main()
{int a[] = { 1,2,3,4 };printf("%zu\n", sizeof(a));printf("%zu\n", sizeof(a + 0));printf("%zu\n", sizeof(*a));printf("%zu\n", sizeof(a + 1));printf("%zu\n", sizeof(a[1]));printf("%zu\n", sizeof(&a));printf("%zu\n", sizeof(*&a));  printf("%zu\n", sizeof(&a + 1));printf("%zu\n", sizeof(&a[0]));printf("%zu\n", sizeof(&a[0] + 1));   return 0;
}

解释:

16     //数组名单独放在sizeof()中,所以计算的是整个数组的大小,结果是4*4=16
8/4    //这里的数组名并没有单独放在sizeof()中,表示的是首元素的地址,a+0表示地址偏移量为0,所以表示的是首元素的地址,结果是指针的大小,即4/8
4       //这里的数组名并没有单独放在sizeof()中,表示首元素的地址,*a得到首元素a[0],这是一个整型变量,大小为4个字节
8/4    //a没有单独放在sizeof()中,表示首元素的地址,a+1表示地址偏移量为1,所以表示的是第二个元素的地址,结果是指针的大小,即4/8
4     //a[1]表示的是数组里面的整型元素,故结果为4
8/4  //&a取出的是整个数组的地址,既然是地址,那就是指针类型的数据,指针大小都是4/8
16   //&a取出的是整个数组的地址,那么它的类型就是数组指针:int(*)[4],那么对&a进行解引用,得到的就是整个数组,整个数组的大小自然就是16
8/4  //&a取出的是整个数组的地址,那么它的类型就是数组指针,&a+1表示要跳过整个数组的大小,但它的类型还是指针,所以结果为4/8
8/4  //a[0]是整型变量,它的地址类型就是整型指针,指针的大小就是4/8
8/4 //&a[0]是第一个元素的地址,&arr[0]+1表示的是第二个元素的地址,指针的大小就是4/8

  • 题目2:下面代码的运行结果
#include <stdio.h>
int main()
{char arr[] = { 'a','b','c','d','e','f' };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));return 0;
}

这道题跟上面那道题做法几乎相同,我们再来看一下:

6      //数组名单独放在sizeof()中,所以计算的是整个数组的大小,结果是1*6=6
8/4   //这里的数组名并没有单独放在sizeof()中,表示的是首元素的地址,arr+0表示地址偏移量为0,所以表示的是首元素的地址,结果是指针的大小,即4/8
1     //这里的数组名并没有单独放在sizeof()中,表示首元素的地址,*a得到首元素a[0],这是一个整型变量,大小为1个字节
1    //arr[1]表示的是数组里面的整型元素,故结果为1
8/4  //&arr取出的是整个数组的地址,既然是地址,那就是指针类型的数据,指针大小都是              4/8
8/4  //&arr取出的是整个数组的地址,那么它的类型就是数组指针,&a+1表示要跳过整个数组的大小,但它的类型还是指针,所以结果为4/8
8/4   //&arr[0]是第一个元素的地址,&arr[0]+1表示的是第二个元素的地址,指针的大小就是             4/8

  • 题目3:下面代码的运行结果
#include <stdio.h>
#include <string.h>int main()
{char arr[] = { 'a','b','c','d','e','f' };
//1.printf("%zu\n", strlen(arr));
//2.printf("%zu\n", strlen(arr + 0));
//3.printf("%zu\n", strlen(*arr));
//4.printf("%zu\n", strlen(arr[1]));
5.printf("%zu\n", strlen(&arr));
6.printf("%zu\n", strlen(&arr + 1));
7.printf("%zu\n", strlen(&arr[0] + 1));return 0;
}

解释:

//这个字符串中没有\0,strlen就会持续往后找\0,就会越界,所以结果是随机值

//arr和arr+0表示的都是首元素的地址,所以在同一测试环境行下,这和第一个结果一样,都是随机值

//*arr表示的是首元素a,a的ASCII码值是97,是一个整型,但是strlen需要接收的是一个字符指针类型的地址,所以会把97当成一个地址,但这块地址可能不属于当前内存,这就造成了非法访问,导致程序崩溃,如果要继续测试下面的代码,就要注释掉这句代码

//和第三个结果一样,会导致程序崩溃,如果要继续测试下面的代码,就要注释掉这句代码

//&arr的类型是数组指针类型:char(*)[],但是它里面存放的值和arr是一样的,而strlen需要接受的是char*类型的指针,所以会把&arr的结果当做char*类型来处理,所以在同一测试环境行下,这和第一个结果一样,都是随机值

//&arr+1表示跳过了整个字符数组,也就是跳过了6个字符元素,但它的类型仍然和&arr类型相同,而strlen需要接受的是char*类型的指针,所以会把&arr+1的结果当做char*类型来处理,然后往后找\0,所以结果是随机值,而且在同一测试环境行下,这和第一个结果相差6(整个字符数组的大小)

//&arr[0]+1表示的是第二个元素的地址,这个字符串中没有\0,strlen就会持续往后找\0,就会越界,所以结果是随机值,而且在同一测试环境行下,这和第一个结果相差1

  • 题目4:下面代码的运行结果
#include <stdio.h>
int main()
{char arr[] = "abcdef";printf("%zu\n", sizeof(arr));//printf("%zu\n", sizeof(arr + 0));printf("%zu\n", sizeof(*arr));printf("%zu\n", sizeof(arr[1]));printf("%zu\n", sizeof(&arr));printf("%zu\n", sizeof(&arr + 1));printf("%zu\n", sizeof(&arr[0] + 1));return 0;
}

这个代码的结果分析与前面代码的分析一样,我们就不再赘述,如果有不懂的,欢迎在评论区留言哦。这里我们直接给出答案:

7
8/4
1
1
8/4
8/4
8/4

  • 题目5:下面代码的运行结果
#include <stdio.h>
#include <string.h>
int main()
{char arr[] = "abcdef";printf("%zu\n", strlen(arr));printf("%zu\n", strlen(arr + 0));printf("%zu\n", strlen(*arr));printf("%zu\n", strlen(arr[1]));printf("%zu\n", strlen(&arr));printf("%zu\n", strlen(&arr + 1));printf("%zu\n", strlen(&arr[0] + 1));return 0;
}

//arr表示首元素的地址,由于这是字符串,末尾隐藏着一个\0,所以计算的是\0之前的字符个数,结果是6

//arr+0表示首元素的地址,由于这是字符串,末尾隐藏着一个\0,所以计算的是\0之前的字符个数,结果是6

//和题目三中结果一样,会导致程序崩溃

//会导致程序崩溃

//&arr的类型是数组指针类型,但是它里面存放的值和arr是一样的,而strlen需要接受的是char*类型的指针,所以会把&arr的结果当做char*类型来处理,所以在同一测试环境行下,这和第一个结果一样

//&arr+1表示跳过了整个字符数组,也就是跳过了7个字符元素,但它的类型仍然和&arr类型相同,而strlen需要接受的是char*类型的指针,所以会把&arr+1的结果当做char*类型来处理,然后往后找\0,所以结果是随机值

//&arr[0]+1表示第二个元素的地址,所以结果比第一个的少1,即结果是5

  • 题目6:下面代码的运行结果
#include <stdio.h>
int main()
{char* p = "abcdef";printf("%zu\n", sizeof(p));printf("%zu\n", sizeof(p + 1));printf("%zu\n", sizeof(*p));printf("%zu\n", sizeof(p[0]));printf("%zu\n", sizeof(&p));printf("%zu\n", sizeof(&p + 1));printf("%zu\n", sizeof(&p[0] + 1));return 0;
}

解释:

8/4     //p是指针变量,里面存放的是a的地址,所以结果是8/4
8/4     //p+1仍然是指针,里面存放的是b的地址,所以结果是8/4
1       //*p的结果是a,a是字符型变量,所以结果是1
1      //p[0]等价于*(p+0),得到的是a,所以结果是1
8/4     //&p是二级指针,也是地址,所以结果是4/8
8/4     //&p+1仍然是指针,所以结果是4/8
8/4     //&p[0]+1表示的是b的地址,所以结果是4/8

  • 题目7:下面代码的运行结果
#include <stdio.h>
#include <string.h>
int main()
{char* p = "abcdef";printf("%zu\n", strlen(p));printf("%zu\n", strlen(p + 1));printf("%zu\n", strlen(*p));printf("%zu\n", strlen(p[0]));printf("%zu\n", strlen(&p));printf("%zu\n", strlen(&p + 1));printf("%zu\n", strlen(&p[0] + 1));return 0;
}

//strlen会往p所指向的那块内存先后找,直到找到\0,所以结果是6

//p+1指向元素b,所以结果是5

//*p得到a,a的ASCII码值是97,strlen会把这个数值当成一个指针,但这个地址不一定在当前程序内,也不一定存在,就造成了非法访问,引起程序崩溃

//p[0]等价于*p,结果与上面一样

//&p是二级指针,strlen会往&p所指向的那块内存先后找,直到找到\0,但是不知道\0在那块空间的位置,所以结果是随机值

//和上面一样,结果是随机值,但是这里有一个问题,strlen(&p)和strlen(&p+1)的结果是否有关系呢?答案是没有关系,因为你不知道从p往后找是否能在走完p所对应的内存之前找到\0

//&p[0]+1表示b的地址,所以结果是5

  • 题目8:下面代码的运行结果
#include <stdio.h>
int main()
{int a[3][4] = { 0 };printf("%zu\n", sizeof(a));printf("%zu\n", sizeof(a[0][0]));printf("%zu\n", sizeof(a[0]));printf("%zu\n", sizeof(a[0] + 1));printf("%zu\n", sizeof(*(a[0] + 1)));printf("%zu\n", sizeof(a + 1));printf("%zu\n", sizeof(*(a + 1)));printf("%zu\n", sizeof(&a[0] + 1));printf("%zu\n", sizeof(*(&a[0] + 1)));printf("%zu\n", sizeof(*a));printf("%zu\n", sizeof(a[3]));return 0;
}

解析:

     二维数组是元素为一维数组的数组

48 //a是数组名,数组名单独放在sizeof()中,计算的是整个数组的大小,所以结果是4*12=48
4  //a[0][0]表示的是数组中的整型元素,所以结果是4
16  //a[0]表示的是第一行一维数组的数组名,数组名单独放在sizeof中,计算的是整个数组的大小,所以结果是4*4=16
4/8   //a[0]表示第一行一维数组的数组名,数组名并没有单独放在sizeof中,所以表示的是第一行一维数组首元素的地址,所以a[0]+1表示的是第二个元素的地址,既然是地址,那大小就是4/8
4   //a[0]+1表示的是第一行的一维数组中第二个元素的地址,那么*(a[0]+1)就是访问数组中元素,所以结果为4
4/8  //a表示二维数组的数组名,数组名并没有单独放在sizeof中,表示的是第一行一维数组的地址,a+1表示要跳过第一行整个一维数组,指向第二行,即表示第二行一维数组的地址,既然是地址,那大小就是4/8
16  //a+1表示第二行一维数组的地址,*(a+1)表示访问整个一维数组,既然是访问整个一维数组,结果自然是4*4=16(也可以这么理解:*(a+1)等价于a[1],a[1]表示的是第二行一维数组的数组名,数组名单独放在sizeof中,结果就是计算这个一维数组的大小)
4/8   //a[0]表示第一行一维数组的数组名,前面有&,取出的是整个一维数组的地址,所以&arr[0]+1表示跳过第一行整个一维数组,指向第二行整个一维数组,既然还是地址,那么结果就是4/8
16   //&a[0]+1表示第二行整个一维数组的地址,对它进行解引用,访问的是整个一维数组,所以结果是16
16    //a表示二维数组的数组名,数组名并没有单独放在sizeof中,表示的是第一行一维数组的地址,对这个地址进行解引用,访问的是整个一维数组,所以结果是16(也可以这么理解:*a等价于*(a+0)等价于a[0],表示的是第一行一维数组的数组名,数组名单独放在sizeof中,访问的是整个数组,所以结果是16)
16   //看到这个代码,想必很多小伙伴都认为这个包会报错的,毕竟,这不是越界访问了吗?但是,我们说过,sizeof在计算变量大小的时候,是通过类型求推导的,所以他并不会真正去访问内存,既然没有越界访问内存,那不就不会报错了,而在根据类型推导sizeof(a[3])的大小时,会将a[0]和a[3]是同等类型的,所以结果自然是16了

指针运算试题

  • 题目1:下面代码的运行结果
#include <stdio.h>
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是数组名,一般情况下表示首元素的地址,a+1表示跳过一个整型的大小,所以a+1指向第二个元素,则*(a+1)得到的是2;&a取出的是整个数组的的地址,所以&a+1会跳过整个整形数组,如图,将这个地址转换成整型指针的地址以后赋给ptr,ptr指向如图所示,ptr-1会向前走过一个整形大小的步长,如图所示,所以*(ptr-1)得到的是5

  • 题目2:下面代码的运行结果
//在X86环境下
//假设结构体的大小是20个字节
//程序输出的结果是啥?
struct Test
{int Num;char* pcName;short sDate;char cha[2];short sBa[4];
}*p = (struct Test*)0x100000;int main()
{printf("%p\n", p + 0x1);printf("%p\n", (unsigned long)p + 0x1);printf("%p\n", (unsigned int*)p + 0x1);return 0;
}

解析:在定义结构体的同时创建了结构体指针变量p,并将0X100000强制类型转换成结构体指针类型以后赋值给p,p+0x1也就是p+1,p所指向的变量为结构体类型,所以p+1需要跳过20个字节,所以p+1=0x100020,等等,这对吗?注意咯,这里是十六进制的形式,逢十六进一,所以结果应该是0x100014,而在X86环境下,会将地址的8位都打印出来,且不会省略掉前面的0,所以结果是00100014.

将p强制类型转换成unsigned long类型以后,里面的值就是一个0x100000,加上一后就是普通的整型相加,所以结果是0X100001,在X86环境下的打印结果是0X100001

将p强制类型转换成unsigned int*类型以后,对指针+1会跳过4个字节,所以结果就是0X100004,在X86环境下,结果就是00100004

  • 题目3:下面代码的运行结果
#include <stdio.h>
int main()
{int a[3][2] = { (0, 1), (2, 3), (4, 5) };int* p;p = a[0];printf("%d", p[0]);return 0;
}

解析:我们先要搞清楚数组里面放了啥,数组里面放的是0,1,2,3,4,5嘛?看着好像是的。注意啦兄弟们,数组里面用括号括起来的是逗号表达式哦,所以数组里面的元素应该是{1,3,5,0,0,0}。

弄清这个以后,我们再来看指针。p是一个整形指针,a[0]是第一行那个一维数组的数组名,一般情况下,他表示首元素的地址,也就是说p里面存放的是第一行一维数组首元素的地址,p[0]等价于*(p+0),p+0还是表示第一行一维数组首元素的地址,对它进行解引用,访问的就是第一行一维数组的首元素,也就是1,所以打印结果是1.

  • 题目4:下面代码的运行结果
#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表示的是二维数组的名称,一般情况下,数组名表示首元素的地址,则aa表示第一行一维数组的地址,aa+1表示跳过整个一维数组,则aa+1指向第二行整个一维数组;&aa取出的是整个二位数组的地址,&aa+1跳过的是整个二维数组,由此,可得图中各指针指向的由来。

ptr1和ptr2都是整型指针,+1时跳过一个整形元素的步长,-1时向前走一个整形元素的步长,所以得到相应指向,对指针解引用以后可以得到打印结果是:10,5

  • 题目5:下面代码的运行结果
//假设环境是x86环境,程序输出的结果是啥?
#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;
}

图示解析:

  • 题目6:下面代码的运行结果
#include <stdio.h>
int main()
{char* a[] = { "work","at","alibaba" };char** pa = a;pa++;printf("%s\n", *pa);return 0;
}

图示解析:

  • 题目7:下面代码的运行结果
#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;
}

图示解析:

运行结果:

http://www.lryc.cn/news/613243.html

相关文章:

  • OLMo 2 架构深度解析:开放语言模型的技术革命
  • A Logical Calculus of the Ideas Immanent in Nervous Activity(神经网络早期的M-P模型)
  • 【数字图像处理系列笔记】Ch05:傅里叶变换与频率域滤波
  • 【实时Linux实战系列】实时分布式计算架构的实现
  • Mongodb常用命令简介
  • MongoDB学习专题(六)复制集和分片集群
  • 02电气设计-安全继电器电路设计(让电路等级达到P4的安全等级)
  • 内存泄漏系列专题分析之三十二:高通相机CamX ION/dmabuf内存管理机制CmdBuffer
  • VC6800智能相机:赋能智能制造,开启AI视觉新纪元
  • vue2+elementui select框可以选择可以回车添加新的option
  • Godot ------ 中级人物血条制作01
  • ElementUI之表格
  • Oracle 19C In-Memory 列存储技术测试
  • Renesas Electronics RA8M1语音套件(VK-RA8M1)
  • 深入解析Go设计模式:责任链模式实战
  • Electron 中 license-keys 的完整集成方案
  • 网络虚拟化是啥啊?
  • 自然语言处理×第四卷:文本特征与数据——她开始准备:每一次输入,都是为了更像你地说话
  • 拥抱云原生:从传统架构到云原生架构的演进与实践
  • python题目练习 无重叠区间
  • 京东关键字搜索商品列表接口开发实战:从参数优化到分布式调用
  • yolo目标检测技术:基础概念(一)
  • 【洛谷题单】--分支结构(一)
  • 脱机部署k3s
  • Python 常用内置高阶函数
  • OO SALV的栏位功能
  • 大屏数据展示页面,数据可视化可以用到的框架和插件
  • 阿里云部署若依后,浏览器能正常访问,但是apifox和小程序访问后报错链接被重置
  • day27 同步互斥
  • IDEA-Research推出的一系列检测、分割模型:从DINO(改进版DETR)、Grounding Dino、DINO-X到Grounded SAM2