C语言从入门到实战————数组和指针的深入理解

前言

  • 在C语言中,数组和指针有的密切得联系,因为数组名本身就相当于一个指针常量。
  • 指针是一个变量,专门用来存储另一个变量的内存地址,通过这个地址可以访问和操作该变量的值,同时也包括数组。
  • 数组是一组连续存储的同类型数据的集合,它允许通过索引快速访问各个元素。同时数组名也是数组首元素的地址。

1.sizeof和strlen的对比

 1.1 sizeof

sizeof是C语言中的单目操作符,用于返回对象或类型所占的内存字节数。如果操作数是类型的化,则计算该变量类型的内存大小。

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

比如:

int main()
{
	int a = 10;
	printf("%d\n", sizeof(a)); //sizeof根据类型计算大小,a为整型,大小为4
	printf("%d\n", sizeof a); //少了括号,结果还是4,目的是想说明sizeof不是函数,仅仅只是一个操作符
	printf("%d\n", sizeof(int)); //给的是整型,结果大小为4
	return 0;
}

1.2 strlen 

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

size_t strlen ( const char * str );

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

比如:

int main()
{
	char arr1[3] = { 'a', 'b', 'c' };
	char arr2[] = "abc";
	printf("%d\n", strlen(arr1));//数组名不在sizeof后面,所以理解为首元素的地址,往后找'\0',所以为随机值
	printf("%d\n", strlen(arr2));//3
	printf("%d\n", sizeof(arr1));//arr1为数组名,所以在sizeof后面,计算的是所有元素的大小,大小3
	printf("%d\n", sizeof(arr2));//4
	return 0;
}


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

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

 2.1 一维数组

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a)); //a为数组名,在sizeof后面求的是整个数组的大小,16
	printf("%d\n", sizeof(a + 0));//a为数组名,但是不是单独出现在sizeof后面,所以是首元素的地址,sizeof求地址的大小,值为4/8
	printf("%d\n", sizeof(*a)); //a为首元素的地址,*a为1,1的类型为int,所以大小为4
	printf("%d\n", sizeof(a + 1));//a为首元素的地址,+1,就是往后位移一个int类型的大小,所以值为4
	printf("%d\n", sizeof(a[1]));//a[1] = *(a+1) = 2 , 所以大小为4
	printf("%d\n", sizeof(&a));//a为数组名,&a取出的是整个数组的地址,而sizeof求地址的大小,所以值为4/8
	printf("%d\n", sizeof(*&a));//*和&相互抵消,a为数组名,单独出现在sizeof后面,计算的是整个数组的大小,所以值为16
	printf("%d\n", sizeof(&a + 1));//&a取出是整个数组的地址,+1后跳过整个数组,本质上还是指针,所以值为4/8
	printf("%d\n", sizeof(&a[0]));//a[0] = *(a+0) = a = &a ,得到的是a的地址,大小为4/8
	printf("%d\n", sizeof(&a[0] + 1));//&a,指向a的地址,也就是首元素的地址,+1后得到第二个元素的地址,大小为4/8
	return 0;
}

 

2.2 字符数组 

代码1 

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr)); //arr为数组名,单独出现在sizeof后面,则计算整个数组元素的大小,值为6
	printf("%d\n", sizeof(arr + 0)); //arr为数组名,但并没有单独出现在sizeof后面,所以表示首元素的地址,所以值为4/8
	printf("%d\n", sizeof(*arr)); //arr为数组名,*arr = 'a',求大小,所以值为1
	printf("%d\n", sizeof(arr[1])); //arr[1] = *(arr + 1) = 'b' , 求大小,所以值为1
	printf("%d\n", sizeof(&arr)); //&arr取出的是整个数组的地址,本质上还是地址,sizeof求地址大小,所以值为4/8
	printf("%d\n", sizeof(&arr + 1));//&arr取出整个数组的地址,+1后跳出整个数组,本质上还是指针,求大小,值为4/8
	printf("%d\n", sizeof(&arr[0] + 1)); //arr[0] = *(arr + 1) = b = &b + 1 = c的地址,本质还是地址,求大小,值为4/8
	return 0;
}

代码2:

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr)); //arr为首元素的地址,数据类型为字符数组,其中并没有'\0',所以为随机值
	printf("%d\n", strlen(arr + 0)); //arr为首元素的地址,+0,还是一样,值为随机值
	//printf("%d\n", strlen(*arr)); //arr为首元素的地址,*arr = 'a',a的askii码值为97,程序报错
	//printf("%d\n", strlen(arr[1])); //arr[1] = *(arr + 1) = 'b',程序报错
	printf("%d\n", strlen(&arr)); //&arr取出的是整个数组的地址,和第一个没区别,还是会往后找'\0',值为随机值
	printf("%d\n", strlen(&arr + 1));//&arr取出的是整个数组的地址,+1后跳出数组,值为随机值
	printf("%d\n", strlen(&arr[0] + 1)); //arr[0] = *(arr + 0) = &'a' + 1 的到'b'的地址,结果也为随机值,比第一个小1
	return 0;
}

代码3: 

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr)); //arr单独出现在sizeof后面,表示求的是整个数组的大小,包括'\0',7
	printf("%d\n", sizeof(arr + 0)); //arr并没有单独出现在sizeof后面,所以arr为首元素的地址,sizeof求地址大小,值为4/8
	printf("%d\n", sizeof(*arr)); //arr为首元素的地址,*arr = a ,a为char类型,所以值为1
	printf("%d\n", sizeof(arr[1])); //arr[1] = *(arr+1) = b , b为char类型,所以值为1
	printf("%d\n", sizeof(&arr)); //&arr,取出的是整个数组的地址,本质上还是地址,值为4/8
	printf("%d\n", sizeof(&arr + 1)); //&arr+1,跳出整个数组,本质上还是地址,值为4/8
	printf("%d\n", sizeof(&arr[0] + 1)); //arr[0] = *(arr+0) = &a+1 = b的地址,本质上还是地址,值为4/8 
	return 0;
}

代码4:

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr)); //arr为首元素的地址,strlen就是往后找'\0',值为6
	printf("%d\n", strlen(arr + 0)); //arr为首元素地址,值为6
	//printf("%d\n", strlen(*arr)); //*arr = a ,a的ascii码值为97,程序崩溃
	//printf("%d\n", strlen(arr[1])); //arr[1] = *(arr + 1) = b , 程序崩溃
	printf("%d\n", strlen(&arr)); //&arr取出的是整个数组的地址,和第一个没区别,值为6
	printf("%d\n", strlen(&arr + 1)); //&arr+1,跳出数组范围,为随机值
	printf("%d\n", strlen(&arr[0] + 1)); //arr[0] = *(arr+0) = &a+1=b的地址,值为5
	return 0;
}

代码5:

int main()
{
	char* p = "abcdef";
	char arr[] = "abcdef";
	printf("%d\n", sizeof(p)); //为指针,4/8
	printf("%d\n", sizeof(p + 1)); //p并没有单独出现在sizeof后面,p为首元素的地址,+1后得到,b的地址,值为4/8
	printf("%d\n", sizeof(*p));  //p为首元素的地址,*p的到a,a为char类型,值为1
	printf("%d\n", sizeof(p[0]));  //p[0] = *(p+0) = a ,值为1
	printf("%d\n", sizeof(&p));  //&p取出的是指针的地址,本质上还是地址,值为4/8
	printf("%d\n", sizeof(&p + 1)); //&p+1,本质上还是地址,值为4/8
	printf("%d\n", sizeof(&p[0] + 1)); //p[0] = *(p+0) = &a+1 = b的地址,本质还是地址,值为4/8
	return 0;
}

 

代码6:

int main()
{
	char* p = "abcdef";
	printf("%d\n", strlen(p)); //p为指针,首元素的地址,值为6
	printf("%d\n", strlen(p + 1)); //p + 1指向b,则值为5
	//printf("%d\n", strlen(*p));  //*p = a ,ascii码值为97,程序崩溃
	//printf("%d\n", strlen(p[0])); //p[0] = *(p+0) = a,程序崩溃
	printf("%d\n", strlen(&p)); //p为指针,&p为指针的地址,所以为随机值
	printf("%d\n", strlen(&p + 1)); //本质还是地址,所以为随机值
	printf("%d\n", strlen(&p[0] + 1)); //p[0] = *(p+0) = &a + 1 = b的地址,所以值为5
	return 0;
}

2.3 二维数组 

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//a为数组名,单独出现在sizeof后面,表示求整个二维数组的大小,所以值为48
	printf("%d\n", sizeof(a[0][0])); //a[0][0] = 0 ,类型为int,所以值为4
	printf("%d\n", sizeof(a[0])); //a[0]为二维数组一行的数组名,单独出现在sizeof后面,求的是第一行数组的大小,16
	printf("%d\n", sizeof(a[0] + 1)); //a[0]为二维数组第一行的数组名,没有单独出现在sizeof后面,则表示首元素的地址,+1得到第二个元素的地址,4/8
	printf("%d\n", sizeof(*(a[0] + 1)));//4
	printf("%d\n", sizeof(a + 1)); //a为数组名,没有单独出现在sizeof后面,所以a为首元素的地址,+1后跳过整个二维数组,本质上还是地址,值为4/8
	printf("%d\n", sizeof(*(a + 1)));//a为数组名,但没有单独出现在sizeof之后,所以为首行的地址,+1后为第一行的地址,解引用得到第一行元素为16
	printf("%d\n", sizeof(&a[0] + 1)); //a[0] = *(a+0) = &0 + 1 = 第零行第一列的地址,值为4/8
	printf("%d\n", sizeof(*(&a[0] + 1)));//对第一行进行解引用,值为16
	printf("%d\n", sizeof(*a)); //a为数组名,并没有单独出现在sizeof后面,所以为首行的地址,解引用后得到第零行的元素,值为16
	printf("%d\n", sizeof(a[3]));//a[3] = *(a + 3) ,首行元素的地址,加三,得到第三行元素的地址,再解引用,得到第四行元素,值为16
	return 0;
}

 

数组名的意义:

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

3.指针运算笔试题解析

3.1 题目1 

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()
{
	printf("%p\n", p + 0x1); //0x100000 + 20 = 00100014 
							 //此处环境为32位,每4位可以用一个十六进制表示,所以,总共是有8个十六进制,所以结果在前面补了两个0
	printf("%p\n", (unsigned long)p + 0x1); //0x100000 + 1 = 00100001
	printf("%p\n", (unsigned int*)p + 0x1); //0x100000 + 4 = 00100004
	return 0;
}

 3.3 题目3

int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) }; // {{1,3},{5,0},{0,0}}
                                              // 逗号表达式
	int* p;
	p = a[0];
	printf("%d", p[0]);//1
	return 0;
}

 

3.4 题目4

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;
}

 

 3.5 题目5

 

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));// 10 5
	return 0;
}

 

3.6 题目6

int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

 

3.7 题目7 

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;
}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/458639.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

离线安装数据库 mysql 5.7 linux

离线安装数据库 mysql 5.7 linux 方法一 参考链接Linux(Debian10.2)安装MySQL5.7.24环境 赋予文件执行权限chmod x 文件名 使用root用户sudo su解压文件tar xvf mysql-5.7.42-linux-glibc2.12-x86_64.tar.gz重命名mv mysql-5.7.42-linux-glibc2.12-x86_64 mysql将桌面的mys…

【WSL】Windows wsl2 子系统忘记密码,重置修改用户密码

1.问题 windows 子系统 ubuntu 忘记密码,sudo 命令无法使用,需要重置密码 2. 解决 使用 wsl 命令进行修改,打开 cmd 窗口 # root 打开 wsl --user root # 修改 root 密码 passwd root # 修改用户密码 passwd username

【ARM】DS中Coretex-M处理器的常用寄存器介绍

【更多软件使用问题请点击亿道电子官方网站查询】 1、 文档目标 了解ArmDS中Coretex-M处理器的常用寄存器的名称及作用。 2、 问题场景 在对Coretex-M处理器进行开发时,了解常用寄存器的名称及作用,可以: 编写正确的程序: 寄存器是程序员用…

AI会取代低代码吗?——探讨两者在软件开发中的角色和关系

引言 在当今快速发展的数字化时代,软件开发已成为企业和商户必不可少的一项工作。为了应对不断增长的需求和日益复杂的业务要求,开发人员和企业正在寻求更加高效、快速的软件开发解决方案。在这样的背景下,低代码开发平台和人工智能&#xf…

【嵌入式开发·Arduino板】I2C接口通讯及应用 | 串口通讯实例 | I2C的类库函数,I2C接口的应用

“跟猫学,保持冷漠,适当撒娇,几乎不动心。跟猪学,保持食欲,充足睡眠,几乎不烦恼。” 🎯作者主页: 追光者♂🔥 🌸个人简介: 💖[1] 计算机专业硕士研究生💖 🌿[2] 2023年城市之星领跑者TOP1(哈尔滨)🌿 🌟[3] 2022年度博客之星人工智能领域…

集合系列(四) -LinkedHashMap详解

一、摘要 在集合系列的第一章,咱们了解到,Map的实现类有HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties等等。 本文主要从数据结构和算法层面,探讨LinkedHashMap的实现。 二、简介 LinkedHashMap可…

虚拟机网络链接

在虚拟网络设置中找到如下界面: "子网 IP" 192.168.79.0/24 表示一个局域网络,它有254个可能的IP地址可供分配(192.168.79.1到192.168.79.254),255.255.255.0 是子网掩码,定义了网络和主机部分。…

python练习一

1. 五个PPT上的界面打印【print、input函数】 print("\t\t\t\t\t英雄联盟商城登录界面\n~ * ~ * ~ * ~ * ~ * ~ * ~ * ~ * ~ * ~ * ~ * ~ * ~ * ~ * ~\n\t\t\t\t\t1. 用户登录\n\t\t\t\t\t2. 新用户注册\n\t\t\t\t\t3. 退出系统\n" "~ * ~ * ~ * ~ * ~ * ~ * ~…

代码随想录算法训练营第二十三天 | 77. 组合

回溯 77. 组合 题目链接&#xff1a;https://leetcode.cn/problems/combinations/ 文章讲解&#xff1a;https://programmercarl.com/0077.%E7%BB%84%E5%90%88.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1ti4y1L7cv/ class Solution { private:vector<…

菜刀HTTPTCP后门分析+防范

“菜刀”对于渗透测试者来说耳熟能详&#xff0c;但是大家用的菜刀真的安全吗&#xff1f;你能保证你所使用的工具不会被别人偷偷的塞入后门吗&#xff1f; 如果菜刀中被塞入后门 那我们岂不是成了别人的苦力。辛辛苦苦打下的shell就这样不知不觉的被别人窃取&#xff0c;怎能…

springboot271制造装备物联及生产管理ERP系统

制造装备物联及生产管理ERP系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装制造装备物联及…

Grass推出Layer 2 Data Rollup

Grass推出Layer 2 Data Rollup Grass邀请链接最新资讯 Grass邀请链接 欢迎使用我的邀请码进行注册: 邀请链接 如果你还不知道注册流程&#xff1a;详见Grass: 出售闲置带宽实现被动收入 最新资讯 简讯&#xff1a;2024年3月13日&#xff0c;Grass宣布正在建立基于Solana的La…

低噪声、低电压、低功耗运放,应用广泛—— D722 9MHz,轨对轨I/O CMOS运放

产品简介 D722是低噪声、低电压、低功耗运放&#xff0c;应用广泛。D722具有9MHz的高增益带宽积&#xff0c;转换速率为8.5V/μs&#xff0c;静态电流为1.7mA&#xff08;5V电源电压&#xff09;。 D722具有低电压、低噪声的特点&#xff0c;并提供轨到轨输出能力&#xff0c;D…

矿洞隧道漫游可视化:探索地心深处的奇幻世界

在这个充满好奇与探索的时代&#xff0c;我们总是渴望揭开世界的神秘面纱&#xff0c;探寻那些深藏在地球内部的奥秘。 矿洞隧道漫游可视化系统通过先进的计算机图形学、虚拟现实和三维建模技术&#xff0c;将矿洞隧道的真实场景进行高精度还原&#xff0c;让我们仿佛置身于一个…

云原生应用(4)之阿里云镜像加速以及镜像仓库

一、容器镜像加速器 1.1 获取阿里云容器镜像加速地址 登录阿里云。 1.2 配置Docker daemon加速器 添加daemion.json文件。 添加daemon.json配置文件 # vim /etc/docker/daemon.json # cat /etc/docker/daemon.json { "registry-mirrors": ["https://s…

基于Java+Springmvc+vue+element实现大学生科技创新创业项目管理系统

基于JavaSpringmvcvueelement实现大学生科技创新创业项目管理系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐…

酷开科技深度切入生活,为品牌方带来确定性增长

在流量稀缺的年代&#xff0c;如何能让营销更出众&#xff1f;流量所在&#xff0c;就是营销所在&#xff0c;就是消费互联网的主战场。社交平台和信息流平台的融合&#xff0c;是两个互联网底层逻辑的驱动&#xff0c;而流量搅动的背后&#xff0c;是互联网世界的底层逻辑在一…

什么是React属性钻取(Prop Drilling)

一、介绍 在React开发过程中&#xff0c;状态管理是一个绕不开的话题。无论是新手还是有经验的开发者&#xff0c;都会面临如何有效管理组件状态的挑战。React为我们提供了多种状态管理方案&#xff0c;如直接的状态传递&#xff08;俗称"属性钻取"&#xff09;、Co…

什么是分段锁?

1、典型回答 分段锁是一种将锁细化到每个段(Segment) 级别的锁设计。在 ConcurrentHashMap 中&#xff0c;它将整个数据结构分成多个段&#xff0c;每个段只锁定自己的一部分数据。每个段可以看作是一个独立的分组&#xff0c;只锁定该段(Segment)内部的数据操作&#xff0c;不…

Go函数全景:从基础到高阶的深度探索

目录 一、Go函数基础1.1 函数定义和声明基础函数结构返回值类型和命名返回值 1.2 参数传递方式值传递引用传递 二、Go特殊函数类型2.1 变参函数定义和使用变参变参的限制 2.2 匿名函数与Lambda表达式何为匿名函数Lambda表达式的使用场景 2.3 延迟调用函数&#xff08;defer&…