【C/C++】深入理解指针(六)

深入理解指针(六)

1.sizeof和strlen的对比

1.1 sizeof

在学习操作符的时候,我们学习了 sizeof , sizeof 计算变量所占内存空间⼤⼩的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。

sizeof是单目操作符,不是函数!

sizeof 关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。

比如:

#include <stdio.h>
int main()
{
 int a = 10;
 printf("%zd\n", sizeof(a));
 printf("%zd\n", sizeof a);//两者输出结果一致,证明了sizeof不是函数,函数不能去掉()
 printf("%zd\n", sizeof(int));
 return 0;
}

1.2 strlen

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

size_t strlen ( const char * str );//返回%zd
char str[]="abcdef";
const char* str="abcdef";//两者都可以
printf("%zd\n",strlen(str));

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

size_t len=strlen("abc\0def");
//返回3
#include<string.h>
#include <stdio.h>
int main()
{
 char arr1[3] = {'a', 'b', 'c'};//不知道0在哪
 char arr2[] = "abc";//abc\0
 printf("%d\n", strlen(arr1));//返回随机值
 printf("%d\n", strlen(arr2));//返回3
 
 printf("%d\n", sizeof(arr1));//返回3
 printf("%d\n", sizeof(arr2));//返回4(a b c \0)共四个
 return 0;
}

1.3 sizeof和strlen的对⽐

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

对于sizeof

sizeof括号中有表达式时,()里是不计算的

例如:

int a=8;
short s=4;
printf("%d\n",sizeof(s=a+2));//2
printf("%d\n",s);//4

在这里插入图片描述

a跟2都是int类型,把他们赋给short类型,所以sizeof认为它是short类型,所以返回2,但表达式是不进行真实计算的.

原因如下:

c语言是编译型语言

需要经过编译链接生成可执行程序,sizeof在编译过程完成,s=a+2在运行过程完成,所以还未到运行过程,程序就已经结束,不会运行到s=a+2这一步.

在这里插入图片描述

2.数组和指针笔试题解析

数组名的意义:

1.sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。

2.&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。

3.除此之外所有的数组名都表⽰⾸元素的地址。

2.1 ⼀维数组

int a[] = {1,2,3,4};
printf("%zd\n",sizeof(a));//zizeof(数组名)传递的是整个数组,所以结果为16(4*4)--四个整型
printf("%zd\n",sizeof(a+0));//a是首元素地址,没有单独存放进sizeof,类型是int *,a+0还是首元素地址(不会真的计算),是地址就是4(32位)或者8(64位)个字节
printf("%zd\n",sizeof(*a));//a是首元素地址,解引用就是第一个元素,所以是4个字节
printf("%zd\n",sizeof(a+1));//a是首元素地址,类型是int*,+1跳过一个整型-->第二个元素地址,是地址就是4或8个字节
printf("%zd\n",sizeof(a[1]));//a[1]是第二个元素,为4字节
printf("%zd\n",sizeof(&a));//存放的是数组地址,还是4或8
printf("%zd\n",sizeof(*&a));//两种理解方式(结果为16)
                            //1.&a是取出整个数组的地址,对数组指针解引用访问数组
                            //2.*和&相互抵消 d等价于(sizeof)
printf("%zd\n",sizeof(&a+1));//跳过整个数组后的位置的地址  依然是地址 4或者8
printf("%zd\n",sizeof(&a[0]));//地址 4或8
printf("%zd\n",sizeof(&a[0]+1));//第二个元素的地址 4或8

对于数组a

a——数组名

a——首元素地址——int*

&a——数组的地址——int(*)[4]

2.2 字符数组

代码1:
#include <stdio.h>
int main()
{
 char arr[] = {'a','b','c','d','e','f'};
 printf("%d\n", sizeof(arr));//数组名单独放sizeof,计算的是数组大小 6字节(一个char类型一个字节)
 printf("%d\n", sizeof(arr+0));//地址 4或8
 printf("%d\n", sizeof(*arr));//arr是首元素地址,*arr为第一个元素,1个字节
 printf("%d\n", sizeof(arr[1]));//第二个元素 1字节
 printf("%d\n", sizeof(&arr));//&arr是数组地址,4或8字节  arr——char(*)[6]
 printf("%d\n", sizeof(&arr+1));//跳过整个数组,指向数组后边的空间  4或者8
 printf("%d\n", sizeof(&arr[0]+1));//第二个元素地址  4或8
 return 0;
}

在这里插入图片描述

&arr+1为野指针,但是不使用

代码2:
#include <stdio.h>
#include <string.h>
int main()
{
 char arr[] = {'a','b','c','d','e','f'};
 printf("%d\n", strlen(arr));//arr是首元素地址,数组中没有\0,会导致越界访问,所以结果是随机值
 printf("%d\n", strlen(arr+0));//同上
 printf("%d\n", strlen(*arr));//arr是首元素地址,*arr是首元素,就是'a',对应的ASII码值为97,就相当于把97作为地址传递给了strlen,strlen得到的就是野指针,所以该程序错误
 printf("%d\n", strlen(arr[1]));//arr[1]--'b'--98,同上错误
 printf("%d\n", strlen(&arr));//&arr是数组地址,起始位置是第一个元素位置-------随机值
 printf("%d\n", strlen(&arr+1));//随机值
 printf("%d\n", strlen(&arr[0]+1));//从第二个元素开始向后统计,也是随机值
 return 0;//假设第10行随机值为x,则第11行为x+6,第12行为x-1
}
代码3:
#include <stdio.h>
int main()
{
 char arr[] = "abcdef";//a b c d e f \0
 printf("%d\n", sizeof(arr));//数组总大小7
 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
 return 0;
}
代码4:
#include <stdio.h>
#include <string.h>
int main()
{
 char arr[] = "abcdef";//a b c d e f \0
 printf("%d\n", strlen(arr));//6
 printf("%d\n", strlen(arr+0));//首地址开始向后 \0之前  6
 printf("%d\n", strlen(*arr));//'a'--97出错
 printf("%d\n", strlen(arr[1]));'b'--98出错
 printf("%d\n", strlen(&arr));//整个数组地址,也是从第一个元素开始   6
 printf("%d\n", strlen(&arr+1));//随机值
 printf("%d\n", strlen(&arr[0]+1));//第二个元素开始 5
 return 0;
}
代码5:
#include <stdio.h>
int main()
{
 const char *p = "abcdef";//字符常量不能修改
 printf("%d\n", sizeof(p));//p是指针变量 4或8
 printf("%d\n", sizeof(p+1));//p是char*类型 +1跳一个字节 是b的地址 4或8
 printf("%d\n", sizeof(*p));//p的类型是char*  *p的类型就是char 1字节
 printf("%d\n", sizeof(p[0]));//p[0]-->*(p+0)-->*p-->'a'  1字节 把常量字符串想象成数组,p可以理解为数组名,p[0]就是首元素
 printf("%d\n", sizeof(&p));//4或8 取出的是p的地址
 printf("%d\n", sizeof(&p+1));//跳过p后的地址 4或8
 printf("%d\n", sizeof(&p[0]+1));//地址 4或8
 return 0;
}
代码6:
#include <stdio.h>
#include <string.h>
int main()
{
 char *p = "abcdef";// a b c d e f \0
 printf("%d\n", strlen(p));//6 往后数
 printf("%d\n", strlen(p+1));//5
 printf("%d\n", strlen(*p));//*p就是'a'--97  错误
 printf("%d\n", strlen(p[0]));//p[0]---*(p+0)---*p  错误
 printf("%d\n", strlen(&p));//&p取出的是指针变量p的地址,和字符串"abcdef"关系不大,从p这个指针变量起始位置开始向后数------随机值
 printf("%d\n", strlen(&p+1));//随机值 但不一定为上一行的随机值+1 因为&p和&p+1之间可能有\0
 printf("%d\n", strlen(&p[0]+1));//5
 return 0;
}

2.3 ⼆维数组

include <stdio.h>
int main()
{
 int a[3][4] = {0};
 printf("%d\n",sizeof(a));//a是数组名,单独放在sizeof内部,计算的是数组大小 3*4*sizeof(int)=48
 printf("%d\n",sizeof(a[0][0]));//第一个元素 4字节
 printf("%d\n",sizeof(a[0]));//是第一行数组,数组名单独放在sizeof中,是第一行数组总大小16
 printf("%d\n",sizeof(a[0]+1));//arr[0]是第一行数组,但没单独放在sizeof,所以是arr[0][0]的地址,+1后是arr[0][1]的地址  是地址就为4或8个字节
 printf("%d\n",sizeof(*(a[0]+1)));//a[0][1]元素 4个字节
 printf("%d\n",sizeof(a+1));//a表示首元素地址,a为二维数组,它的首元素为第一行的的地址,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));//*a为第一行元素 16
 printf("%d\n",sizeof(a[3]));//无需真实存在,仅通过类型推断就能算出长度,是第四行元素 16
 return 0;
}

3.指针运算笔试题解析

3.1 题⽬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;
}

在这里插入图片描述

输出结果为2,5

3.2 题⽬2
//在X86环境下 //假设结构体的⼤⼩是20个字节 
//程序输出的结果是啥? 
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p = (struct Test*)0x100000;
//指针+-整数
int main()
{                          //0x1就是1  0x1是十六进制
 printf("%p\n", p + 0x1);//结构体指针+1,跳过一个结构体 0x100000+20-->0x100014-->00100014  32位补0
 printf("%p\n", (unsigned long)p + 0x1);//转换成整型 整型+1就是+1 0x100000+1-->0x100001
 printf("%p\n", (unsigned int*)p + 0x1);//0x100000+4-->0x100004-->00100004
 return 0;
}
3.3 题⽬3
#include <stdio.h>
int main()
{
 int a[3][2] = { (0, 1), (2, 3), (4, 5) };//注意题目中()不是{}
 int *p;
 p = a[0];// 首元素地址,即第一行地址 其实就是a[0][0]的地址
 printf( "%d", p[0]);//p[0]-->*(p+0)-->*p 所以答案为1
 return 0;
}

在这里插入图片描述

3.4 题目4
//假设环境是x86环境,程序输出的结果是啥? 
#include <stdio.h>
int main()
{
 int a[5][5];
 int(*p)[4];//p是一个数组指针,p指向的是4个整型元素
 p = a;
 printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//指针-指针的绝对值是指针之间的元素个数
 return 0;//p[4][2]等价于*(*(p+4)+2)
}

在这里插入图片描述

a的类型是int * [5],p的类型是int * [4]

在这里插入图片描述

所以结果为%d打印的值为**-4**,%p打印的值为(补码)十六进制 把-4转换成十六进制的补码 结果为FFFFFFFC

3.5 题目5
#include <stdio.h>
int main()
{
 int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 int *ptr1 = (int *)(&aa + 1);//&aa是整个数组的地址 +1跳过整个数组 然后强制类型转换为int*
 int *ptr2 = (int *)(*(aa + 1));//得到一维数组名aa[1]=&aa[1][0] 第二行数组名表示第二行首元素地址
 printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
 return 0;
}

在这里插入图片描述

所以答案为10 5

3.6 题⽬6
#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};//a是指针数组
 char**pa = a;//pa是二级指针,指向的是a的首元素地址
 pa++;
 printf("%s\n", *pa);//%s打印的是字符串,给一个地址,从这个地址向后打印字符串,直到遇到\0
 return 0;
}

在这里插入图片描述

所以本题答案为at

3.7 题⽬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;
}

根据题图可画出下图

在这里插入图片描述

对于 printf("%s\n", **++cpp);来说,++cpp拿到c+2的值----也就是c+2的地址 再解引用——————PONIT

在这里插入图片描述

然后进行printf("%s\n", *--*++cpp+3);//再次++cpp指向c+1 然后解引用得到c+1 然后进行--操作变为c 再解引用 再加3 指向E 打印ER

在这里插入图片描述

  然后运行这一行 printf("%s\n", *cpp[-2]+3);//*cpp[-2]等价于**(cpp-2)  先进行cpp-2 重新指向c+3 解引用得到c+3 再解引用 再+3 指向S 最终输出ST

在这里插入图片描述

最后 printf("%s\n", cpp[-1][-1]+1);//由于上一行代码没有改变cpp的值 所有cpp初始还是指向c+1 cpp[-1][-1]等价于*(*(cpp-1)-1)    cpp-1指向c+2 解引用得到c+2 再—1 得到c+1 再解引用 再+1指向E 最终输出EW

在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值