C语言——深度理解指针(6)

1. sizeof和strlen的对⽐

1.1 sizeof

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

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

c 复制代码
#include <stdio.h>
int main()
{
	int a = 10;
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof a);
	printf("%d\n", sizeof(int));
	return 0;
}

运行结果

1.2 strlen

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

c 复制代码
size_t strlen ( const char * str );

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

c 复制代码
#include <stdio.h>
#include <string.h>//使用strlen,需要包含头⽂件 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;
}

1.3 sizeof 和 strlen的对⽐

(1)sizeof

1.sizeof是操作符

  1. sizeof计算操作数所占内存的

⼤⼩,单位是字节

  1. 不关注内存中存放什么数据

(2)strlen

1.strlen是库函数 ,使⽤需要包含头⽂件 string.h

  1. srtlen是求字符串⻓度的,统计的是 \0 之前字符的个数

  2. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界

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

数组名的意义:
1.sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。
2. &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表⽰⾸元素的地址。

2.1 ⼀维数组

c 复制代码
int a[] = {1,2,3,4}; 
printf("%d\n",sizeof(a));     //16  //单独放数组名,计算整个数组的⼤⼩
printf("%d\n",sizeof(a+0));   //4或8//不是单独放,所以这里的a表示首元素的地址,地址的大小只和操作系统位数有关
printf("%d\n",sizeof(*a));    //4   //*a表示首元素
printf("%d\n",sizeof(a+1));   //4或8//a+1表示第二个元素的地址
printf("%d\n",sizeof(a[1]));  //4   //a[1]表示第二个元素
printf("%d\n",sizeof(&a));    //4或8//&a表示整个数组的地址,但他还是一个地址
printf("%d\n",sizeof(*&a));   //16  //*&a就表示a,*和&抵消了,单独放数组名,计算整个数组的⼤⼩
printf("%d\n",sizeof(&a+1));  //4或8//&a+1表示跳过整个数组,指向最后一个元素4后面的地址
printf("%d\n",sizeof(&a[0])); //4或8//&a[0]表示首元素地址
printf("%d\n",sizeof(&a[0]+1));//4或8//&a[0]+1表示第二个元素的地址

2.2 字符数组

可以类比:p0== *( p + 0 )==*p

代码1:(求大小)

c 复制代码
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));      //6   //单独放数组名,求这个数组的大小
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//表示跳过整个数组,指向最后一个元素f的后面
printf("%d\n", sizeof(&arr[0]+1));//4或8//表示跳过首元素的地址,也就是第二个元素的地址

代码2:(求个数,\0前面的元素个数)

c 复制代码
#include <stdio.h>
#include <string.h>//strlen是库函数,使⽤需要包含头⽂件 <string.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));       //随机值  //表示首元素的地址,但数组中没有\0,往后找不知道什么时候遇到\0
	printf("%d\n", strlen(arr + 0));   //随机值  //表示首元素的地址,但数组中没有\0
	printf("%d\n", strlen(*arr));      //非法访问//*arr表示首元素,是'a',他的ACSLL是97,传给strlen函数,97会被当成地址,程序会崩溃
	printf("%d\n", strlen(arr[1]));    //随机值  //从第二个元素开始访问
	printf("%d\n", strlen(&arr));      //随机值  //虽然是数组地址,但数值上和首元素地址是一样的,所以还是从首元素往后访问
	printf("%d\n", strlen(&arr + 1));  //随机值  //从元素'f'后的地址往后开始访问
	printf("%d\n", strlen(&arr[0] + 1));//随机值 //从第二个元素开始往后开始访问
	return 0;
}

代码3:(求大小)

c 复制代码
#include <stdio.h>
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));        //7    //数组名单独放在sizeof里面,求整个数组的大小,不要忘记还有个\0
	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 //表示最后一个元素f后面的地址
	printf("%d\n", sizeof(&arr[0] + 1));//4或8 //表示第二个元素的地址
	return 0;
}

代码4:(求个数,\0前面的元素个数)

c 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));         //6       //表示首元素地址,从首元素开始统计\0之前的元素个数
	printf("%d\n", strlen(arr + 0));     //6       //表示首元素地址
	printf("%d\n", strlen(*arr));        //非法访问 //表示首元素'a',他的ACSLL是97,传给strlen函数,97会被当成地址,程序会崩溃
	printf("%d\n", strlen(arr[1]));      //非法访问 //表示元素'b',他的ACSLL是98,传给strlen函数,98会被当成地址,程序会崩溃
	printf("%d\n", strlen(&arr));        //6       //表示数组地址,但数值上和首元素地址一样,也是从首元素开始往后访问
	printf("%d\n", strlen(&arr + 1));    //随机值   //表示'f'后面的地址,从这个地址往后不知道什么时候遇到\0
	printf("%d\n", strlen(&arr[0] + 1)); //5       //表示第二个元素的地址
	return 0;
}

代码5:(求大小)

c 复制代码
#include <stdio.h>
int main()
{
	char* p = "abcdef"; 
	printf("%d\n", sizeof(p));        //4或8    //p是一个指针变量,大小就是4或8 
	printf("%d\n", sizeof(p + 1));    //4或8    //p+1,是'b'的地址
	printf("%d\n", sizeof(*p));       //1       //表示'a'
	printf("%d\n", sizeof(p[0]));     //1       //表示'a',p[0]== *( p + 0 )==*p
	printf("%d\n", sizeof(&p));       //4或8    //表示指针变量p的地址
	printf("%d\n", sizeof(&p + 1));   //4或8    //表示指针变量p的地址后面的一个地址
	printf("%d\n", sizeof(&p[0] + 1));//4或8    //表示的是'b'的地址
	return 0;
}

代码6:(求个数,\0前面的元素个数)

c 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char* p = "abcdef";
	printf("%d\n", strlen(p));        //6       //表示'a'的地址,从首元素开始统计\0之前的元素个数
	printf("%d\n", strlen(p + 1));    //5       //表示'b'的地址
	printf("%d\n", strlen(*p));       //非法访问 //表示首元素'a',他的ACSLL是97,传给strlen函数,97会被当成地址,程序会崩溃
	printf("%d\n", strlen(p[0]));     //非法访问 //表示首元素'a',他的ACSLL是97,传给strlen函数,97会被当成地址,程序会崩溃
	printf("%d\n", strlen(&p));       //随机值   //表示指针变量p的地址
	printf("%d\n", strlen(&p + 1));   //随机值   //表示指针变量p的地址后面的一个地址
	printf("%d\n", strlen(&p[0] + 1));//5       //表示'b'的地址
	return 0;
}

2.3 ⼆维数组

1、二维数组的每个元素是一个一维数组(即一行)。
2、每一行中的元素地址是连续的。
3、不同行之间的地址也是连续的,即整个二维数组在内存中是连续存储的。

c 复制代码
#include <stdio.h>
int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));          //48  //a是这个二维数组的数组名,单独放在sizeof表示计算整个数组的大小
	printf("%d\n", sizeof(a[0][0]));    //4   //表示第一行的第一个元素
	printf("%d\n", sizeof(a[0]));       //16  //a[0]是第一行这个数组的数组名,单独放,表示求第一行的大小
	printf("%d\n", sizeof(a[0] + 1));   //4或8//表示第二行的地址,是一个地址
	printf("%d\n", sizeof(*(a[0] + 1)));//4   //*(a[0] + 1)==*(*(a + 0) + 1),表示第一行的第一个元素
	printf("%d\n", sizeof(a + 1));      //4或8//表是第二行地址
	printf("%d\n", sizeof(*(a + 1)));   //16  //*(a + 1)==a[1],表示第二行的数组名,单独放数组名就是求整个数组的大小
	printf("%d\n", sizeof(&a[0] + 1));  //4或8//表示第二行的地址
	printf("%d\n", sizeof(*(&a[0] + 1)));//16 //表示第二行
	printf("%d\n", sizeof(*a));          //16 //表示第一行
	printf("%d\n", sizeof(a[3]));        //16 //a[3] 的语法是合法的,因为它可以解析为指针运算(等价于 *(a + 3))。实际越界访问发生在解引用时(如 a[3][0])。sizeof的内部表达式是不计算的,因此无运行时错误。
	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);           //因为&a + 1的类型是int(*)[5],所以要强制转化为int*
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

//解析:
//a + 1表示第二个元素的地址,所以*(a + 1)的值为2
//&a + 1表示跳过整个数组,所以他的地址就是元素5后面的地址,把这个地址给了ptr,则ptr - 1就是元素5的地址

3.2 题⽬2

c 复制代码
//在X86环境下 1
//假设结构体的⼤⼩是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;
}

//解析:
//0x1是十六进制数的1//指针+-数字表示跳过一个类型,构体的⼤⼩是20个字节,地址是16进制,所以要将20转换为0x14,进行加减,所以答案是00100014
//(unsigned long)p + 0x1,是将p储存的地址强制转化为整数,所以就是整数加整数,所以答案是00100001
//(unsigned int*)p + 0x1,是将p储存的地址强制转化为int*,所以跳过,是加4个字节

3.3 题⽬3

c 复制代码
#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}
//所以p[0]==*(p+0)=*p,所以答案是1

3.4 题⽬4

c 复制代码
//假设环境是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;
}
//解析
// &p[4][2],4表示按照p的类型向后跳4次,2在表示在最后一次跳跃中,从其中的首元素地址开始向后跳两次
//第一次打印:%p是打印地址的,直接将-4转换成原码,再转换成补码,在转换为16进制,就得FFFFFFFC了
//第二次打印:就是-4

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(*)[2][5],所以要强制转化为int*
	int* ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}
//解析:
//&aa + 1表示跳过整个数组,指向10后面的地址
//aa + 1表示跳过首元素地址
//所以答案是10和5

3.6 题⽬6

c 复制代码
#include <stdio.h>
int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}
//解释
//第一次pa里面放的是首元素的地址
//后面经过pa++就变成了第二个元素的地址

3.7 题⽬7

c 复制代码
#include <stdio.h>
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };  // {"FIRST","POINT","NEW","ENTER"}
	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;
}
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1bbea701d2fa41ccbf12fb28eb33494d.png) 复制代码
**"别走!你的三连还在我这儿寄存着呢~点赞是门票,投币是VIP,收藏是复活甲,下次更新记得来取哦!"**

完.
相关推荐
小陈的进阶之路1 小时前
爬虫三大解析库:lxml, jsonpath, bs4
开发语言·笔记·python
wuyk5551 小时前
第1章:无刷电机核心原理与运行机制
c语言·开发语言·stm32·单片机·嵌入式硬件·机器学习
SomeB1oody1 小时前
【RustyML入门】2.10. 主成分分析
开发语言·后端·机器学习·rust·教程
2603_965148111 小时前
抖音/快手直播选品:用API快速匹配爆款与低价货源
开发语言·python·自动化·api
RobinDevNotes1 小时前
轻量级动画引擎Anime.js迎来V4大版本更新
开发语言·前端·javascript·ecmascript·动画·c4前端
lv__pf1 小时前
servlet与jsp
java·开发语言·servlet
坚持编程的菜鸟1 小时前
模拟实现strncat
c语言·算法·模拟实现strncat
wabs6662 小时前
关于哈希表【力扣1.两数之和的思考】
数据结构·算法·leetcode·散列表·图论
大辉狼_音频架构2 小时前
第 02 篇:时域的语言 —— 卷积、LTI 系统与冲激响应
算法·音视频开发
Hhy_11072 小时前
【从零开始学数据结构 ⑥】:二叉树之堆——打破规则的优先队列
c语言·开发语言·数据结构·学习·visual studio