当前位置: 首页 > 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.dtcms.com/a/319742.html

相关文章:

  • 算法 - 贪心算法
  • 计网学习笔记第3章 数据链路层层(灰灰题库)
  • 冷库设备远程监控物联网+省电节能解决方案
  • linux下实现System V消息队列实现任意结构体传输
  • 具身智能,正在翻越三座大山
  • 计算机毕业设计java疫情开放下的新冠信息共享平台 基于Java的社区疫情防控人员流动管理系统 疫情防控期间社区人员动态管理系统
  • 范数的定义、分类与 MATLAB 应用实践
  • 解决React白板应用中的画布内容丢失问题
  • 3363. 最多可收集的水果数目
  • 关键字 - 第二讲
  • Spring AI + Redis:构建高效AI应用缓存方案
  • 【物联网】基于树莓派的物联网开发【25】——树莓派安装Grafana与Influxdb无缝集成
  • 在 Linux 系统上安装 Docker 的步骤如下(以 Ubuntu/Debian为例)
  • 前缀和
  • 简洁明了的讲明什么是哈希(hash)函数
  • [激光原理与应用-170]:测量仪器 - 能量型 - 光功率计的工作原理与内部功能模块组成
  • 【第7话:相机模型3】自动驾驶IPM图像投影拼接技术详解及代码示例
  • 直连微软,下载速度达18M/S
  • Mysql 单行函数 聚合函数
  • MySQL聚簇索引与非聚簇索引详解
  • 北京企业数据防泄漏指南:5款适合北方市场的安全加密工具评测
  • 【华为机试】332. 重新安排行程
  • MySQL——黑马
  • STM32U5 周期性异常复位问题分析
  • 【MyQSL】库 表—基操
  • 性能优化——GPU的影响
  • [C++20]协程:语义、调度与异步 | Reactor 模式
  • Kafka原理--主题、分区、消费者的关系
  • windows内核研究(内存管理-线性地址的管理)
  • 【PHP 中的 `use` 关键字完全指南】