奶奶都能看懂的 C++ —— 数组与指针

在上一篇中,我们讨论了 vector 和迭代器,用以遍历一个有序可变序列。而我们知道,在 vector 之下有一种更加基本的数据类型——数组,它是有序固定大小的序列。实际上,我们所涉及的迭代器(范围 for),在数组中也以某种形式可用。

单维数组

使用数组

奶奶都知道怎么创建数组:

/* by yours.tools - online tools website : yours.tools/zh/checkurl.html */
int a[100]={0};//创建数组,然后初始化所有元素为0
int a[100]={0,1,2};//创建,然后初始化前三个元素,后面的元素初始化为默认值 (0)

她也知道数组可以使用下标运算遍历(下标从零开始):

/* by yours.tools - online tools website : yours.tools/zh/checkurl.html */
for(int i = 0;i<100;i++){
    cout<<a[i]<<endl;
}

但是,数组不能直接赋值给另一个数组,这一点和 vector 不同,需要注意(大小都不一样且固定,怎么复制呢?)。

数组即指针

很好。但是你有没有想过,如果我直接把数组输出,会发生什么?

int a[100]={1,2};
cout<<a<<endl; // 0x7ffc4d858a10

WOW,这个输出是不是有些熟悉?

没错,在指针那一篇里,我们曾经见过。这是十六进制数,表示一个地址。也就是说,我们输出了数组,但是它的表现居然和指针一样,输出了一个对象的地址!

但是,这个地址究竟是什么?指向了哪个对象?

嗯,我们解引用试试:

cout<<*a<<endl; // 1

输出的是1。居然是数组的第一个元素!

也就是说,如果我们直接使用数组,却不指定下标,那么它会被当做一个指向第一个元素的指针

指针和迭代器

事情从这里开始就变得有趣起来了。实际上,数组在内存中是连续存储的,那么,我们可以用指针运算的方式,来遍历整个数组:

int a[5]={0};
for(int *p=a;p!=a+5;p++){
    cout<<*p<<endl;
}

在上面的代码中,我们先创建了指向第一个元素的指针,然后不断将指针向后移动,输出解引用后的对象,直到到达最后一个元素的后一个位置,立即退出。查看下面图片,了解具体过程。

数组指针的实际工作原理

实际上,std 提供了 begin()end(),来获取首个元素,和末尾元素的后一个位置。

#include <iterator> //先引入,再使用
int a[5]={0};
for(int *p=begin(a);p!=end(a);p++){
    cout<<*p<<endl;
}

是不是很熟悉?我们曾经提到过,范围 for 语句满足的条件:

begin,end 返回的是一个迭代器/迭代器可以自增

实际上,返回的是一个可运算的指针,也是可行的:

int a[5]={0};
for(int i:a){
    cout<<i<<endl;
}

上方的三种形式,都是等价的。也就是说,数组也可以用范围 for 语句(当然也可以设定为引用,然后修改)。

多维数组

更高的维度

之前我们创建的数组,是对象的固定有序序列。那么我们想一想,数组其中的对象能否是一个数组呢?实际上是可以的,这称为多维数组

int a[100][100]={}; // 全部初始化为默认值 (0)
int b[10][10]={{1,2},{2,3}}; // 初始化一部分

我们都知道我们现在所在的空间是三维的,这个维数,就是指的是有多少根轴,比如上下、左右、前后。而 Excel 表格是二维的,有 X Y 两根轴。因此,从名字上来说,多维数组可以理解为一片 N 维的空间,空间中的每个坐标点,每一个都包含一个独立的元素。

比如说,上面定义了两个二维数组,第一个全部初始化为默认值 0,第二个把第一行第一个数设为 1,第二个数设为 2;第二行第一个数设为 2,第二个数设为 3,其余设定为默认值 0。

cout<<b[0][0]<<endl; // 1
cout<<b[0][1]<<endl; // 2
cout<<b[1][0]<<endl; // 2
cout<<b[1][1]<<endl; // 3

下标运算符是可用的。在二维中,我们把第一个下标称为,第二个称为

这是多维数组含义上的理解。但是其本质是什么呢?

多维数组的本质

我们还得来看看这个初始化:

int b[10][10]={{1,2},{2,3}}; // 初始化一部分

不难发现,这个初始化语句的字面值,是一个数组中嵌套了另外一个数组。也就是说,可以这么理解这个声明:

创建包含 10 个元素的数组,其中所有元素的类型均为包含 10 个元素的数组。

这样我们就不难理解,第一个下标决定是哪一个包含 10 个元素数组,第二个下标决定获取到的数组中的哪一个元素。

推广下,三维也是一样的。

int c[10][10][10]={ { {1,2},{1,3} }, { {1,2},{3,2} }}; 

考考你,先想想下面的输出是什么,然后我再告诉你答案。

cout<<c[0][1][1]<<endl;
cout<<c[1][1][0]<<endl;

想好了吗?来看看输出吧。

输出是两个 3。我们来看看这个过程。

  1. 第一行
    1. 首先,取得下标为 0 的元素(数组):{ {1,2},{1,3} }
    2. 再从中取得下标为 1 的元素(数组):{1,3}
    3. 再取出下标为 1 的元素(int 类型变量):3
  2. 第二行
    1. 首先,取得下标为 1 的元素(数组):{ {1,2},{3,2} }
    2. 再从中取得下标为 1 的元素(数组):{3,2}
    3. 再取出下标为 0 的元素(int 类型变量):3

遍历多维数组

你当然可以用奶奶都会的 for 循环嵌套+下标来遍历:

int a[10][10]={};
for(int i=0;i<10;i++){
    for(int j=0;j<10;j++){
        cout<<a[i][j]<<endl;
    }
}

但我们之前提到了数组即为指针,这同样适用于多维数组:

for(int (*i)[10]=a;i!=a+10;i++){
    for(int *j=*i;j!=*i+10;j++){
        cout<<*j<<endl;
    }
}

我们来仔细看看这里究竟发生了什么。

先看外层循环。首先,我们创建了一个指向 a 这个数组首个元素的指针。我们刚才提到,a 的第一个元素是一个包含 10 个元素的数组,因此我们创建的指针必须具有正确的类型。*i 表示它是个指针,[10] 表示这个指针指向的是一个数组,且数组的长度为 10。

你应该注意到了那个括号,这是因为 [10] 的优先级更高。不打括号 int *i[10] 表示一个数组里存储 10 个 int 类型的指针,这和我们想要达到的目标完全不同。

接下来,我们每次循环时都把指针向后移动一个位置。由于已经指定了正确的类型,因此指针将移到 a 中的下一个元素,也就是第二个包含 10 个元素的数组。指针每次循环都会指向下一个数组,直到到达 a 中的最后一个数组的下一个位置,立即退出循环。

再来看内层循环。我们先定义了一个 int 类型的指针,这个指针初始化为 *i,也就是先获取外层指向包含 10 个元素数组的指针,然后解引用,获取到这个数组,而由于数组本质是一个指向首元素的指针,所以相当于 j 初始化为指向这个包含 10 个 int 的数组的首个元素的指针

接下来,输出 j 解引用后的那个 int 元素,然后 j 向后移动一个位置,直到到达该数组末尾的下一个位置,立即退出循环。

也就是说,多维数组本质就是多个数组的嵌套,而只要记住数组即为指向其第一个元素的指针,那么无论嵌套多少都不用关心——每一次嵌套,都只是让指针指向包含数组的数组而已。

当维数越来越多的时候,我们手动指定指针指向的是怎样的对象容易累死自己,因此建议使用 auto

for(auto i=a;i!=a+10;i++){
    for(auto j=*i;j!=*i+10;j++){
        cout<<*j<<endl;
    }
}

注意了,既然是指针,那么修改时,修改的元素是原本数组内的对象,而并非拷贝副本:

int a[3][3]={};
for(auto i=a;i!=a+3;i++){
    for(auto j=*i;j!=*i+3;j++){
        cout<<*j<<endl;
		*j=1;
    }
	cout<<endl;
}
// 输出都是 0
for(auto i=a;i!=a+3;i++){
    for(auto j=*i;j!=*i+3;j++){
        cout<<*j<<endl;
    }
	cout<<endl;
}
// 输出都是 1

当然,由于是数组,所以多维情况下 begin() end() 依然可用,而且和用指针运算相同,都能正确获取到指向对应数组首个数组元素的指针

for(auto i=begin(a);i!=end(a);i++){
    for(auto j=begin(*i);j!=end(*i);j++){
        cout<<*j<<endl;
		*j=1;
    }
	cout<<endl;
}

范围 for 语句

当然了,正是由于这些性质,因此范围 for 语句也可用:

int a[3][3]={};
for(int i[3]:a){ // Error
    for(int j:i){
        cout<<j<<endl;
    }
}

……但是上面的根本就没办法通过编译。这是因为,范围 for 的本质是会把 a 中的每个元素拷贝赋值给 i,但问题是,每个元素都是一个数组,而这样就会自动变为指向数组首个元素的指针。因此,类型并非是一个包含 3 个元素的数组,而是一个指针。

但如果我们使用正确的指针类型,那么内层的 for 就不对了,因为你无法把 i 当作一个数组,因为它只是一个指针,不能遍历。所以只能改成使用一般 for

for(int *i:a){ 
	for(int *j=i;j!=i+3;j++){
		cout<<*j<<endl; // OK
	}
}

但是等等,让我们回忆一下之前的范围 for 的介绍。我们提到过,如果想要修改,可以创建引用,那么就不会拷贝赋值了。

不会拷贝?仔细想想这意味着什么:

创建的引用指向的是原来的数组中的数组,也就是为内层数组创建了别名!这样的话,自动转换为指针就不存在了(注意这里和之前一样,也得加上括号避免优先级问题):

int a[3][3]={};
for(int (&i)[3]:a){ 
    for(int &j:i){
        cout<<j<<endl;
    }
}

当然,你把最内层的引用去掉也没事。毕竟最内层本身就是个 int,不会再遍历了,加不加取决于你要不要修改。你也可以加上 const 防止修改,同时避免赋值性能损耗,这点我们之前也提到过了。

当然,你也可以用 auto,省点事情:

for(auto &i:a){ 
    for(auto &j:i){
        cout<<j<<endl;
    }
}

总结

对于数组和指针,我们已经非常深入了。现在你应该已经知道了遍历数组的多种方法,如何使用指针控制你的循环,同时用上省事的范围 for 和 auto。下一篇,我们终于将离开这堆你已经有了深入了解的数据海洋,去拆解更多其它 C++ 的知识,同样是奶奶级哦。

本文作者:SamHou0。原文链接:https://www.cnblogs.com/samhou/p/19201817/cpp-array-with-pointer

本文的所有发布平台:About

本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 SamHou's blog!

源码直接下载地址: https://pan.quark.cn/s/be6e03a195a4 该资源为"108大型通信技术有限公司"提供的网站源代码,其功能完整性和经过测试的状态已得到确认。这段源代码可作为极具价值的参考资料,不仅适用于通信技术在网站开发中的学习应用,也适合作为毕业设计项目的基础。以下列出了源代码中可能涵盖的关键知识点及详尽说明: 1. **前端框架库**:网站极有可能运用了主流的前端框架,例如Bootstrap或AngularJS,或者自行设计的HTML、CSS和JavaScript。这些框架提供了一套完整的用户界面组件,有效简化了页面的布局和交互设计过程。 2. **响应式设计**:鉴于该网站属于大型通信技术公司,源代码大概率集成了响应式设计,确保网站在不同设备(包括手机、平板及桌面电脑)上均能提供优质的用户体验。 3. **通信协议API**:源代码可能涉及HTTP/HTTPS协议,用于客户端服务器之间的数据传输。若网站具备实时通信功能,则可能采用了WebSocket或其他实时通信协议。此外,可能还包括其他服务或系统的API接口,例如RESTful API。 4. **后端技术**:网站的后端可能选用了Java(Spring Boot)、Python(Django或Flask)、Node.js(Express)等服务器端框架。后端主要负责处理业务逻辑、数据库操作和用户认证等任务。 5. **数据库管理**:源代码中可能包含SQL查询,表明使用了关系型数据库(如MySQL、PostgreSQL),或者NoSQL数据库(如MongoDB)。数据库设计对于存储和检索大量通信数据具有关键作用。 6. **安全性**:源代码应包含多项安全措施,如C...
内容概要:本文系统阐述了基于Wasserstein生成对抗网络(W-GAN)的光伏出力场景生成方法,旨在通过深度学习技术精准刻画光伏发电功率的时间序列不确定性。研究构建了完整的W-GAN模型架构,详细介绍了其在网络结构设计、损失函数优化(引入Wasserstein距离提升训练稳定性)及训练流程上的关键技术细节,并提供了完整的Python代码实现。该方法能够生成高保真、多样化的光伏功率出力场景,有效克服传统方法在捕捉复杂概率分布和时序相关性方面的局限性,为处理新能源出力不确定性问题提供了先进的数据驱动解决方案。; 适合人群:具备Python编程能力及机器学习基础知识,专注于新能源发电预测、电力系统规划、电力市场分析、综合能源系统优化等领域研究的高校研究生、科研机构研究人员及电力企业技术人员。; 使用场景及目标:①掌握W-GAN在新能源时序数据生成中的高级应用技巧;②构建高精度的光伏出力不确定性模型,生成用于鲁棒优化随机规划的多场景集;③支撑高比例可再生能源接入下的电力系统可靠性评估、储能配置、经济调度及风险管控等关键决策; 阅读建议:建议读者务必结合所提供的Python代码进行动手实践,深入理解模型的超参数设置、训练动态监控收敛性判断方法,并尝试将该方法文中提及的DDPM、条件GAN等其他前沿生成模型进行对比实验,以深化对不同生成模型特性的认识,拓展解决实际科研问题的技术路径。
内容概要:本文详细阐述了工业母机技术领域中“高级结构设计工程师”这一岗位的全方位任职要求职业发展路径,涵盖职位对标、目标企业、学历证书要求、年龄范围、管理半径、晋升关键点、必备工作经验年限以及薪资待遇区间。重点突出该岗位对高端数控机床核心结构(如床身、主轴箱、导轨等)设计能力的要求,强调有限元分析、精度控制、热变形补偿、振动抑制等核心技术能力,并明确指出需具备项目主导经验、团队管理能力和跨部门协作经验。同时,根据不同企业类型和发展阶段,给出了清晰的年薪划分标准,体现了市场对该岗位的技术深度综合能力的高度认可。; 适合人群:具备5年以上工业母机或高端机床结构设计经验,致力于向高级工程师、技术专家或管理岗位发展的结构设计从业者;或希望转型进入高端装备制造业的精密机械研发人员。; 使用场景及目标:①用于求职者精准定位职业发展方向,评估自身高级岗位之间的能力差距;②辅助企业制定人才招聘标准薪酬体系;③指导技术人员规划技能提升路径,聚焦核心技术积累项目经验沉淀。; 阅读建议:建议结合个人职业发展阶段对照文中各项指标进行自我诊断,重点关注“晋升关键点”“必备年限”部分,有针对性地补齐技术短板、积累主导项目经验,并注重专利成果团队管理能力的培养,以全面提升竞争力。
代码转载自:https://pan.quark.cn/s/a4b39357ea24 KeymouseGo 简体中文 | English 功能:记录用户的鼠标键盘操作,通过触发按钮自动执行之前记录的操作,可设定执行的次数,可以理解为 的 。 用途:在进行某些操作简单、单调重复的工作时,使用本软件就可以很省力了。 自己只要做一遍,然后接下来就让电脑来做。 目录 安装 使用方法 + 基本操作 + 提示 + 脚本语法说明 关于作者 开源贡献者 安装 该软件通过 语言编写,已打包为可执行文件,未安装 的用户可直接下载 release 版本 ,直接点击 运行 源码打包可执行文件 打包完成后,可执行文件在项目路径的文件夹内。 使用方法 基本操作 桌面模式 1、点击 按钮,开始录制。 2、在计算机上进行任意操作,如点击鼠标、键盘输入,这部分的动作会被记录下来。 3、点击 按钮,结束录制。 4、点击 按钮,计算机会重复执行一遍第2步中所录制的动作。 命令行模式 直接运行指定脚本: 运行指定脚本3次: 提示 1、可设置脚本重复执行的次数,如果为 即为无限循环。 2、默认启动热键为 ,功能等同于 按钮;默认终止热键为 ,按下后将会停止正在运行的脚本。 3、录制时只记录鼠标点击动作和键盘动作,不记录鼠标移动轨迹。 4、每次录制结束后都会在 目前下生成一个新的脚本文件。 5、运行前可以在列表中选择一个需要执行的脚本。 6、 下的脚本文件内容可以修改,修改时可参考如下所述 。 7、热键设置中的指代鼠标中键,指代鼠标侧键 8、由于程序速度受限,当输入的鼠标速度大于一定值时脚本将无法以预期的输入速度执行 9、部分系统环境中,可能出现无法录制完整的鼠标事件的情况,请以管理员身份/root身份运行...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值