C语言指针详解-包过系列(二)目录版

C语言指针详解-包过系列(二)目录版

    • 1、数组名的深入理解
      • 1.1、数组名的本质
      • 1.2、数组名本质的两个例外
        • 1.2.1、sizeof(数组名)
        • 1.2.2、&数组名
    • 2、使用指针访问数组
    • 3、一维数组传参本质
    • 4、二级指针
      • 4.1、二级指针介绍
      • 4.2、二级指针运算
    • 5、指针数组(模拟实现二维数组)

1、数组名的深入理解

1.1、数组名的本质

数组名的本质其实就是数组首元素的地址。我们以下面的代码测试为例:

#include<stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};printf("&arr[0] = %p\n ", &arr[0]);//取数组首元素地址printf("arr     = %p\n ", arr);//取数组数组名return 0;
}

在这里插入图片描述
可见二者一模一样,故验证了数组名即为数组首元素地址。
但是可能会有读者提出以下质疑:

#include<stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};printf("%d", sizeof(arr));return 0;
}

在这里插入图片描述
若数组名 arr 表示的是数组首元素的地址的话,那么输出结果应当为一个地址的大小,也就是4/8字节。这就涉及到了数组名是数组首元素地址的两个例外了,请见 1.2

1.2、数组名本质的两个例外

1.2.1、sizeof(数组名)

在关键字 sizeof 中单独放入数组名时,这个数组名表示的是整个数组,计算的是整个数组的大小,单位是字节。如上 1.1 中所示。
注意:只有是单独放入数组名时,数组名才表示整个数组,其余情况下均是计算数据类型大小。
如下面代码所示:

#include<stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};printf("%d", sizeof(arr + 1));//此时 arr 就又恢复成数组首元素的地址了return 0;
}

在 x86 环境下的运行结果:
在这里插入图片描述
在 x64 环境下的运行结果:
在这里插入图片描述
如上所示,当sizeof内部非单独放置数组名时,sizeof 计算的就是对应运行环境的地址的大小。

1.2.2、&数组名

&数组名 ,这里的数组名表示的是整个数组,取出的是整个数组的地址(注意:整个数组的地址与数组首元素地址是有区别的)
我们通过下面代码的演示使大家对此有更加清晰的认识。

#include<stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};printf("&arr[0] = %p\n", &arr[0]);printf("arr     = %p\n",  arr   );printf("&arr    = %p\n", &arr   );return 0;
}

在这里插入图片描述
我们发现三个输出结果完全一样。不急,我们通过对地址进行加减整数运算来使他们原形毕露。演示如下:

#include<stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};printf("&arr[0] = %p\n", &arr[0]);printf("&arr[0] + 1 = %p\n", &arr[0] + 1);printf("arr     = %p\n",  arr   );printf("arr + 1 = %p\n", arr + 1);printf("&arr     = %p\n", &arr);printf("&arr + 1 = %p\n", &arr + 1);return 0;
}

在这里插入图片描述
通过上述演示我们发现,在 x86 环境下,&arr[0] 和 &arr[0] + 1 之间相差4字节,arr 与 arr + 1 也是相差4字节。这是因为二者均表示首元素的地址, + 1 也就是跳过一个元素。
但是 &arr 和 &arr + 1 则相差40个字节,这就是因为 &arr 表示的是整个数组的地址,+ 1 操作是跳过整个数组的。

2、使用指针访问数组

由上文我们可知数组名表示数组首元素的地址,那么数组名加整数就可以访问首元素后面的元素。
请看下面代码:

#include<stdio.h>
int main()
{int arr[10] = { 0 };int i = 0;size_t sz = sizeof(arr) / sizeof(arr[0]);int* parr = arr;//将数组首元素地址存储到指针 parr 中//输入for (i = 0; i < sz; i++){scanf("%d", parr + i);//遍历数组地址//注意,这里parr+i就已经表示数组对应下标元素的地址了,&parr+i表示的就是指针变量parr的地址加减整数了。//scanf("%d", arr + i);也可以这样写}//输出for (i = 0; i < sz; i++){printf("parr[%d] = %d\n", i, *(parr + i));//输出数组元素}return 0;
}

在这里插入图片描述
在这里我们补充一点数组 arr[ i ] 在编译前系统会将其转化为 *(arr + i) 然后再进行编译。
在这里插入图片描述
由上所述,我们进一步分析,数组名 arr 是首元素地址,可以赋值给 parr ,其实数组名 arr 和 parr 在这里是等价的。既然我们可以使用 arr[i] 访问数组,那 parr[i]也应该能访问数组。如下演示:

#include<stdio.h>
int main()
{int arr[10] = { 0 };int i = 0;size_t sz = sizeof(arr) / sizeof(arr[0]);int* parr = arr;//将数组首元素地址存储到指针 parr 中//输入for (i = 0; i < sz; i++){scanf("%d", parr + i);}//输出for (i = 0; i < sz; i++){printf("parr[%d] = %d\n", i, parr[i]);//等价于arr[i]}return 0;
}

综上,我们可以总结出以下四点:
1、数组就是数组,是一块连续的空间(与数组大小,数组元素个数,数组元素类型都有关系)
2、指针变量就是指针变量,是一个变量(4/8bytes)
3、数组名除两个特例外,表示的都是数组首元素的地址
4、可以使用指针来访问数组,数组在系统编译时转化的内容就是指针。

3、一维数组传参本质

在之前函数章节,我们已经讲过数组可以作为参数传递给函数,在本节我们讲述一下数组传参本质。
我们先用一个问题来作引,我们之前在函数外部计算过数组元素的个数,在本节我们不妨尝试在函数内部计算求数组元素个数。

#include<stdio.h>void test(int arr[])
{size_t sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };size_t sz1 = sizeof(arr) / sizeof(arr[0]);printf("sz1 = %d\n", sz1);test(arr);return 0;
}

在 x86 环境下的结果:
在这里插入图片描述
我们可见 sz2 的值并不等于 sz1 。这就涉及到数组传参的本质了。数组传参的时候传递的是数组名,也就是数组首元素的地址。
所以函数形参部分理论上应当使用指针来接收数组首元素地址。故在函数内部我们所写的sizeof(arr)计算的其实是地址的大小在 x86 环境下就是4字节。sizeof(arr[0])计算的是数组首元素的大小即int类型,大小为4字节。故 sz2 的结果为1.
补充:在x64环境下的运行结果:
在这里插入图片描述

sizeof(arr)/sizeof(arr[0]) == 8/4 == 2

在 x64 环境下地址的大小为8字节。

#include<stdio.h>void test(int* arr)
{printf("%d\n", sizeof(arr));//计算一个指针变量的大小
}int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };size_t sz1 = sizeof(arr) / sizeof(arr[0]);//计算数组内元素个数多少。printf("sz1 = %d\n", sz1);test(arr);return 0;
}

综上所述,我们总结出以下两点:
1、数组传参的本质是传递了数组首元素的地址,所以形参访问的数组和实参的数组是同一个数组。故在形参数组中更改数组内容也会更改实参数组中的内容(传址调用由此区别于传值调用)。
2、形参的数组是不会再单独创建空间,所以形参的数组是可以省略数组大小的。

4、二级指针

4.1、二级指针介绍

指针变量也是变量,是变量就有地址,那指针变量的地址存放于何处?这就涉及到二级指针。

#include<stdio.h>
int main()
{int a = 0;int* p = &a;//一级指针p存储a的地址int** pp = &p;//二级指针存储p的地址printf("p = %p\n", p);printf("pp = %p\n", pp);return 0;
}

在这里插入图片描述

4.2、二级指针运算

1、*pp 通过对 pp 中地址的解引用,如此找到 p ,*pp访问的就是 p

#include<stdio.h>
int main()
{int a = 0;int b = 10;int* p = &a;//一级指针p存储a的地址int** pp = &p;//二级指针存储p的地址*pp = &b;//通过解引用更改一级指针 p 所指向的对象printf("%d", *p);return 0;
}

在这里插入图片描述
2、**pp 先通过*pp 找到 p 然后再通过对 p 解引用找到 a。

#include<stdio.h>
int main()
{int a = 0;int* p = &a;//一级指针p存储a的地址int** pp = &p;//二级指针存储p的地址**pp = 20;//通过两次解引用更改一级指针 p 所指向的对象的内容。//等价于*p =20//等价于 a = 20printf("%d", a);return 0;
}

在这里插入图片描述

5、指针数组(模拟实现二维数组)

我们知道有整型数组,字符型数组,浮点型数组等,由此可知指针数组就是存储的元素是指针的数组。指针数组的每个元素又指向一块区域。
我们用指针数组模拟二维数组为例:
首先先编写好3个一维数组:

#include<stdio.h>
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 6,7,8,9,10 };int arr3[] = { 11,12,13,14,15 };return 0;
}

然后来设置指针数组,让指针数组内存储一维数组的地址,由上文知就是数组名。

#include<stdio.h>
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 6,7,8,9,10 };int arr3[] = { 11,12,13,14,15 };int* arr[] = { arr1 , arr2 , arr3 };//编写指针数组int i = 0;for (i = 0; i < 3; i++)//打印模拟的二维数组{int j = 0;for (j = 0; j < 5; j++){printf("%d ", arr[i][j]);}printf("\n");}return 0;
}

在这里插入图片描述
在这里插入图片描述
上述代码只是模拟出了二维数组的效果,但并不是完全等于二维数组,因为上面模拟出的数组的每一行并不是连续的。

全文至此结束!!!
写作不易,不知各位老板能否给个一键三连或是一个免费的赞呢(-▽-)(-▽-),这将是对我最大的肯定与支持!!!谢谢!!!(-▽-)(-▽-)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8.9 C++

1.思维导图 2. 搭建一个货币的场景&#xff0c;创建一个名为 RMB 的类&#xff0c;该类具有整型私有成员变量 yuan&#xff08;元&#xff09;、jiao&#xff08;角&#xff09;和 fen&#xff08;分&#xff09;&#xff0c;并且具有以下功能&#xff1a; (1)重载算术运算符…

PCL 曲线4点细分算法

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 四点细分算法(Four-Point Subdivision Scheme)是一种用于生成平滑曲线的细分算法。与 Chaikin 逼近型细分算法不同,四点细分算法通过插入新的控制点来细化曲线,并生成一条逐步逼近的平滑曲线。该算法通常用于生…

高效管理:如何快速查询并跟踪批量快递物流信息

在现代快节奏的生活中&#xff0c;我们经常需要处理大量的快递单号&#xff0c;以跟踪货物的物流轨迹。无论是电商卖家、物流公司还是个人用户&#xff0c;都希望能够快速、准确地获取到快递的实时信息。为了解决这个问题&#xff0c;我们可以借助一款名为“固乔快递查询助手”…

八、MyBatis

一、MyBatis介绍 MyBatis 是持久层框架&#xff0c;它支持自定义 SQL、存储过程以及⾼级映射。MyBatis 去除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置 和映射原始类型、接口和 Java POJO&#xff08;Plain Old Java Obj…

最新版的AutoGPT,我搭建好了

最近AutoGPT不是更新了嘛 安装 我按照官方的教程 在本地搭建好了 改动 可见的改动&#xff0c;主要是把原来的纯命令行改成前后端的形式 看下前端界面 界面比较简单&#xff0c;主要分3个大块 监控 第一个是监控 主要是看你在 build 里构建的Agents的运行情况 build 第一个是Ag…

前端项目中的Server-sent Events(SSE)项目实践及其与websocket的区别

前端项目中的Server-sent Events(SSE)项目实践 前言 在前端开发中&#xff0c;实时数据更新是提升用户体验的重要因素之一。Server-SentEvents(SSE)是一种高效的技术&#xff0c;允许服务器通过单向连接将实时数据推送到客户端。下面将从SSE的基本改变&#xff0c;使用场景展…

大数据Flink(一百零九):阿里云Flink的基本名称概念

文章目录 阿里云Flink的基本名称概念 一、层次结构 二、​​​​​​​​​​​​​​概念说明 1、工作空间&#xff08;Workspace&#xff09; 2、项目空间&#xff08;Namespace&#xff09; 3、资源&#xff08;Resource&#xff09; 4、草稿&#xff08;Draft&#…

Spring-MyBatis整合:No qualifying bean of type ‘XXX‘ available: ...

1.看一下核心配置中有没有导入myBatis配置 2.看一下service和dao有没有相应注解 3.看一下MyBatisConfig中有没有对sqlSessionFactory和mapperScannerConfigurer注释成bean对象以及有没有配置映射文件路径

【扒网络架构】backbone、ccff

backbone CCFF 还不知道网络连接方式&#xff0c;只是知道了每一层 backbone backbone.backbone.conv1.weight torch.Size([64, 3, 7, 7])backbone.backbone.layer1.0.conv1.weight torch.Size([64, 64, 1, 1])backbone.backbone.layer1.0.conv2.weight torch.Size([64, 64,…

Datawhale AI夏令营第四期 | AIGC文生图——可图Kolors-LoRA风格故事 Task1笔记

Hi&#xff0c;大家好&#xff0c;我是半亩花海。最近在尝试学习AIGC的内容&#xff0c;并报名参加了Datawhale举办的2024年AI第四期夏令营&#xff0c;主要学习内容是从零入门AI生图原理和实践。本次活动基于魔搭社区“可图Kolors-LoRA风格故事挑战赛”进而开展的项目实践学习…

【最新】推荐6款论文ai写论文软件推荐网站平台

在当前的学术研究和写作环境中&#xff0c;AI写作工具已经成为许多学者和学生的重要助手。这些工具不仅能够提高写作效率&#xff0c;还能帮助优化文章结构、润色语言以及进行查重等任务。以下将推荐六款优秀的AI写论文软件&#xff0c;并详细介绍它们的功能和特点。 1. 千笔-…

C语言 ——— 学习并使用memset函数

目录 memset函数的功能 学习memset函数​编辑 使用memset函数 memset函数的功能 memset函数是内存设置函数&#xff0c;将数据设置成传递的参数&#xff0c;以字节来设置 学习memset函数 函数的参数&#xff1a; void* ptr&#xff1a;数据要设置的起始位置的指针 int …

【代码随想录】区间和——前缀和方法

本博文为《代码随想录》学习笔记&#xff0c;原文链接&#xff1a;代码随想录 题目 原题链接&#xff1a;58. 区间和&#xff08;第九期模拟笔试&#xff09; 题目描述 给定一个整数数组 Array&#xff0c;请计算该数组在每个指定区间内元素的总和。 输入描述 第一行输入为…

VS /PROFILE(性能工具探查器)的使用

/PROFILE&#xff08;性能工具探查器&#xff09; 在 Visual Studio 开发环境中设置此链接器选项 打开项目的“属性页” 对话框。 有关详细信息&#xff0c;请参阅在 Visual Studio 中设置 C 编译器和生成属性。 选择“配置属性”>“链接器”>“高级”属性页。 修改配…

动态规划之——背包DP(完结篇)

文章目录 概要说明分组背包模板例题1思路code模板例题2思路code 有依赖的背包问题模板例题思路code 背包问题求方案数模板例题思路code 背包问题求具体方案模板例题思路code 概要说明 本文讲分组背包、有依赖的背包、 背包问题求方案数以及背包问题求具体方案 入门篇(01背包和…

STM32G070KBT6的RTC HAL库使用

*配置问题 首先使能时钟源&#xff0c;这里在时钟配置中选择LSI&#xff0c;为什么后面会说&#xff0c;然后使能Calender结构体&#xff0c;保证可以对RTC的年月日时分秒等进行写入和读取&#xff1b;alarmA和alarmB是闹钟&#xff0c;这里不用就Disable&#xff1b; Tam…

ShardingSphere之ShardingProxy集群部署

文章目录 介绍使用Zookeeper进行集群部署统一ShardingJDBC和ShardingProxy配置通过Zookeeper注册中心同步配置直接使用ShardingProxy提供的JDBC驱动读取配置文件 介绍 开发者手册 在conf/server.yaml配置文件中有下面这一段配置&#xff0c;就是关于集群部署的 mode: # typ…

极狐GitLab CICD Catalog Beta 功能介绍

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门面向中国程序员和企业提供企业级一体化 DevOps 平台&#xff0c;用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规&#xff0c;而且所有的操作都是在一个平台上进行&#xff0c;省事省心省钱。可以一键安装极狐GitL…

视觉SLAM中的数学基础01 -3D空间的位置表示

在视觉SLAM中&#xff0c;理解和表示3D空间中的位置是至关重要的。这涉及到多种数学概念和工具&#xff0c;如坐标系、向量、矩阵、旋转和平移等。这些数学基础构成了视觉SLAM算法的核心。以下是3D空间位置表示的基本数学概念。 这是一个表示世界坐标系和相机坐标系之间关系的3…

JNPF快速开发平台赋能数字办公方式转变

随着信息技术的飞速发展&#xff0c;数字化转型已成为各行各业提升效率、优化流程的重要手段。JNPF快速开发平台正是在这样的背景下应运而生&#xff0c;它通过简化开发流程&#xff0c;使得非技术人员也能参与到应用的构建中来&#xff0c;从而加速了数字办公方式的转变。 数字…