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

文章目录

深入理解指针(六)

1.sizeof和strlen的对比

1.1 sizeof

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

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

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

比如:

c 复制代码
#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语⾔库函数 ,功能是求字符串⻓度。函数原型如下:

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

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

c 复制代码
size_t len=strlen("abc\0def");
//返回3
c 复制代码
#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的对⽐

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

对于sizeof

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

例如:

c 复制代码
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 ⼀维数组

c 复制代码
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:
c 复制代码
#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:
c 复制代码
#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:
c 复制代码
#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:
c 复制代码
#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:
c 复制代码
#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:
c 复制代码
#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 ⼆维数组

c 复制代码
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
c 复制代码
#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
c 复制代码
//在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
c 复制代码
#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
c 复制代码
//假设环境是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
c 复制代码
#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
c 复制代码
#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

这题为本文章最难的一题,但一旦理解,会有醍醐灌顶之感

c 复制代码
#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
复制代码
相关推荐
兜小糖的小秃毛1 分钟前
文号验证-同时对两个输入框验证
开发语言·前端·javascript
YuforiaCode5 分钟前
第十二届蓝桥杯 2021 C/C++组 卡片
c语言·c++·蓝桥杯
anqi2727 分钟前
如何在 IntelliJ IDEA 中编写 Speak 程序
java·大数据·开发语言·spark·intellij-idea
XuX0331 分钟前
MATLAB小试牛刀系列(1)
开发语言·matlab
Suckerbin41 分钟前
第十四章-PHP与HTTP协议
开发语言·http·php
Best_Liu~1 小时前
TransactionTemplate 与@Transactional 注解的使用
java·开发语言·spring boot·后端
谈不譚网安1 小时前
初识Python
开发语言·python
我想吃余1 小时前
Linux学习笔记(一):Linux下的基本指令
linux·笔记·学习
LaughingZhu1 小时前
PH热榜 | 2025-04-24
运维·经验分享·搜索引擎·产品运营·jenkins
慕雪华年1 小时前
【Python】使用uv管理python虚拟环境
开发语言·python·ai·uv·mcp