指针进阶(3) -- 关于sizeof和strlen的详细总结(下)

简介: 指针进阶(3) -- 关于sizeof和strlen的详细总结(下)

指针的sizeof的使用

char*p的sizeof的使用

把*p当成数组来理解即可,跟数组名是一个道理

int main()
 {
   char *p = "abcdef";
  printf("%d\n", sizeof(p));
  printf("%d\n", sizeof(p+1));
  printf("%d\n", sizeof(*p));
  printf("%d\n", sizeof(p[0]));
  printf("%d\n", sizeof(&p));
  printf("%d\n", sizeof(&p+1));
  printf("%d\n", sizeof(&p[0]+1));
  return 0;
}


1.sizeof(p)

p是指向字符串首字符'a'的,是用来存放地址的,但是地址本身是不占用内存的,p这个字符指针变量把'a'的地址存放起来了,那么创建一个变量就要开辟一块内存空间,但是指针变量的大小就是4/8 byte

运行结果:

766668b200ed4591a511a5a5e1903e3d.png

2.sizeof(p + 1)

char*的指针+1跳过一个字符,也就是'b'的地址,还是一个指针变量,大小还是4/8 byte

运行结果:

3510b019fcc4465f97c55b693f556a3c.png

3.sizeof(*p)

指针p的类型是char*,对p解引用访问1个字符,字符'a'的大小是1byte

运行结果:

6a9d0cb7c90d41cb8bb5b6047ee29cea.png

4. sizeof(p[0])

p[0] --> *(p+0) --‘a’ ,大小1byte

运行结果:

ed0f0f18526946c1850aba0676516a5b.png

5.sizeof(&p)

但凡在内存中开辟空间就有地址,跟字符串是没有关系的,p的类型是char*,所以&p的类型是二级指针

运行结果:

de3ff38f4df545ff8465fda0ab5082f4.png

6.sizeof(&p + 1)

&p+1为什么跳过一个p:

图解:

0b415916199a49e7871342ea69d73ce0.png

运行结果:

c39d72865ebe49e08adaede568f1b646.png

7.sizeof(&p[0] + 1)

p[0]是'a',&p[0]那就是'a'的地址,&p[0]+1,那就是'b'的地址,指向'b'的指针就是4/8byte

运行结果:

c44640dbf9ad40bbb09ec5dc081c94ea.png

char*p的strlen的使用

int main()
{
  char* p = "abcdef";
  printf("%d\n", strlen(p));//6
  printf("%d\n", strlen(p + 1));//p+1是'b'的地址 5 
  printf("%d\n", strlen(*p));//err
  printf("%d\n", strlen(p[0]));//err
  printf("%d\n", strlen(&p));//随机值
  printf("%d\n", strlen(&p + 1));//随机值
  printf("%d\n", strlen(&p[0] + 1));
  return 0;
}


1.strlen(p)

从'a'开始数,到'\0',6个字符

运行结果:

330cd77f679646d2be7533002758bdf0.png

2.strlen(p + 1)

从'b'开始数,到'\0',5个字符

运行结果:

4fba77878641461cade6d69045238171.png

3.strlen(*p)

解引用的结果--‘a’,ascll码值是97,非法访问,err

运行结果:

fdb79d9e32a045968f8c55cd59a2072e.png

4.strlen(p[0])

和3是一样的结果,err

运行结果:

e0c85c22707d473392748709b854258f.png

5.strlen(&p)

共同点:

不管是p指向的字符串地址,还是&p的这个地址,传入strlen 都是计算长度的,计算长度的结束标志就是遇到\0

关于这里我要详细说明一下p和&p的区别:

传入p后, 从字符串首元素地址开始向后找  能找到\0  因为字符串就是以\0结尾的

例如:

传入p能确定的原因就是 "abcdef\0" 这个是顺序存储的, p的地址就是字符a的地址 ,从a开始向后找\0 是能找到的

而传入&p这个地址,每次执行程序都是不确定的,内存中数据的存储也是不确定的,所以\0的位置也是不确定的

可以看到三次的执行结果都不一样的:

a4b9f45240e24b409e942e8509d65216.png

三次&p的结果都是不同的,所以从&p这个地址向后寻找\0也是不确定的

虽然p的地址  执行三次也可能不同,但是它后面的\0位置是确定的

然而关于'\0'后面还有数据我的理解是:

288c2629facf4b8fa19b4b8b47b9d605.png

并不是说\0之后就不存数据了,内存中肯定是有数据的,但是strlen关心的是首次出现\0的位置在哪里,计算的是这个长度

运行结果:

9576afd832d34767a40c381417565fcb.png

6.strlen(&p + 1)

跳过了一个p变量,也是随机值

运行结果:

2ff7ffa8b65b4002a1c82f42254dcc20.png

注意:strlen(&p + 1)和strlen(&p),不存在大小问题,还是随机的

7.strlen(&p[0] + 1)

字符串第二个元素,向后数,5个字符

运行结果:

09179cd2b8d24592b6c67e3939e84ba5.png


二维数组的sizeof


int a[3][4] 的sizeof的使用

#include<stdio.h>
int main()
{
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]));
return 0;
}


数组在内存中是连续存放的,这种二维数组题其实脑子要清晰一点,什么都是相对的,无论是sizeof还是strlen,其实只要记住:它们就计算两样东西:元素和地址(针对上面的题),第一时间先弄明白它到底是元素还是地址,二者选一,题就会做出来了。

图解:

ff3cb300c85c4e9e8498e3a64ec28d84.png

 1.sizeof(a)

a这个二维数组的数组名单独放在sizeof内部,计算整个二维数组的大小

运行结果:6fca4bbeb7504884b0310f956d485cc0.png

2.sizeof(a[0][0])

二维数组第一行第一个元素,4个字节

运行结果:d5bc3e38968d4f0baecfdae11ddfc613.png

3.sizeof(a[0])

二维第一行的数组名,参照上图arr1,这时数组名单独放在sizeof内部了,就是计算数组第一行的大小

运行结果:ee9186d047224bc3bcf728a26f0e09a2.png

4.sizeof(a[0] + 1)

a[0]不是单独放在sizeof内部

a[0]表示的首元素的地址,即第一行第一个元素的地址 - &a[0][0]

a[0] + 1 是第一行第2个元素的地址 &a[0][1]

个人理解:把a[0]理解成上图arr1,那么就算数组arr1的第一个元素的地址,也就是二维数组第一行第一个元素地址,如果arr1+1,那就是第一行第二个元素地址,是地址那大小为4/8 byte

运行结果:126a2969f6da4f5c8e1b0d23ad0384cf.png

5.sizeof(*(a[0] + 1))

二维数组第一行第二个元素地址解引用,那就是二维数组第一行第二个元素,大小4byte

运行结果:8a89e7ce0be74afe945647dd4b148105.png

6.sizeof(a + 1)

a作为二维数组的数组名并非单独放在sizeof内部,所以表示首元素的地址

二维数组的首元素是第一行,这里的a就是第一行的地址---  int (*)[4]

a+1是跳过第一行,指向了第二行

由上图得

86d9c2e8b0494e2792c1ced083c56d16.png

那就是二维数组数组名没有单独放在sizeof内部,此时表示二维数组第一个行的地址,也就是arr1(a[0]),那么arr1+1(a[0]+1),变成arr2(a[1]),也就是二维数组第二行的地址,是地址大小为4/8 byte

运行结果:d268d28bf909480284f11ddd9c61553a.png

7.sizeof(*(a + 1))

由6得此时是二维数组第二行的地址解引用,也就是第二行所有元素,也就是*(a[1])

*(a+1)-->a[1],计算第二行元素,那么结果为16byte

运行结果:93765ea8b5ae4cad81b0eed2850c8cb6.png

8.sizeof(&a[0] + 1)

&a[0]是第一行的地址,&a[0]+1是第二行的地址

个人理解:

由图:

86d9c2e8b0494e2792c1ced083c56d16.png

&a[0]是二维数组第一行的数组名,数组名表示首元素地址,也就算二维数组第一行的地址,

类比为&arr1,即为整个数组的地址,那么&arr1+1是跳过第一个数组,也就是arr2(二维数组第二行地址)

可以写成:&a[1]

运行结果:7e6e6374fb544fe4b9f6a1f39968a732.png

9.sizeof(*(&a[0] + 1))

由8得,*&a[1]-->a[1]-->二维数组第二行元素

运行结果:c46814ab019e46a1b1c6ab4ff232f21f.png

10.sizeof(*a)

*a - 就是第一行元素

运行结果:7cb23158d428442dbb4deab9ce283e33.png

11.sizeof(a[3])

这里是下标是存在越界问题,但是sizeof还是可以正常计算,也就是二维数组的第4行(下标:3)

a0895a9c8ba54bee9b14dd90ec2dd72f.png

数组名单独放在sizeof内部,计算的是第4行的大小,为16 byte

为解释此问题,举一个例子:

#include<stdio.h>
int main()
{
    int a = 5;
  short s = 11;
  printf("%d\n", sizeof(s = a + 2));//2
  printf("%d\n", s);//11
  return 0;
}

7f4b9db5e71f4cc3834d0fda35ad966f.png

s=a+2,a和2类型都是int,但是赋值的时候s是short类型,那么结果的类型就是short,表达式就不计算了,直接看由s的类型算出2byte,由于表达式没有运算所以值是不会改变的,答案依旧为11

7b8aa28593f949f1ba0bd00e261d3619.png

无论是值还是表达式都有值属性和类型属性,像int a=10 , a+3

a+3的值属性是10,a是int型,3是int型,类型属性是int,其实无论a+3怎么样都是要看它赋给什么类型,要是double型,那sizeof就是8

所以回到题目,sizeof(a[3])在编译期间就完成了运算,直接由类型判断算出值

运行结果:1e8d0a2f58a8406a87a51975ccc5c851.png


3.指针笔试题


笔试题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;
}


图解:

8d01e3d6ff6c4e47a90ac09afe2dbd0c.png

6057c53957ac4e54be5afdd111486060.png

笔试题2

//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0;
}


16进制,10进制,8进制,2进制只是数值的表示形式而已,

就像 f  15  17  1111表达的形式的值都为15


图解:

231edc9c60b64f0dae756b75b53cd837.png

代码运行:

b69da0ab261f46dbb8a214c76d7d739d.png

接下来还有不少笔试题,还待继续。欢迎大佬补充!

相关文章
|
6月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
2月前
|
Serverless 编译器 C语言
【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)
【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)
|
6月前
|
C语言
指针进阶(回调函数)(C语言)
指针进阶(回调函数)(C语言)
|
6月前
|
存储 C语言 C++
指针进阶(函数指针)(C语言)
指针进阶(函数指针)(C语言)
|
6月前
|
编译器 C语言
指针进阶(数组指针 )(C语言)
指针进阶(数组指针 )(C语言)
|
6月前
|
搜索推荐
指针进阶(2)
指针进阶(2)
52 4
|
6月前
指针进阶(3)
指针进阶(3)
45 1
|
6月前
|
C++
指针进阶(1)
指针进阶(1)
47 1
|
6月前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
52 0
|
6月前
|
存储 安全 编译器
C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)
C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)
47 0