C++ 数组详解,很全,很详细

数组 (C++)

数组是相同类型的对象序列,它们占据一块连续的内存区。 传统的 C 样式数组是许多 bug 的根源,但至今仍很常用,尤其是在较旧的代码库中。 在新式 C++ 中,我们强烈建议使用 std::vectorstd::array,而不是本部分所述的 C 样式数组。 这两种标准库类型都将其元素存储为连续的内存块。 但是,它们提供更高的类型安全性,并支持保证指向序列中有效位置的迭代器。

堆栈声明

在 C++ 数组声明中,数组大小在变量名称之后指定,而不是像在其他某些语言中那样的在类型名称之后指定。 以下示例声明了要在堆栈上分配的 1000 个双精度值的数组。 元素数量必须以整数文本或常量表达式的形式提供。 这是因为,编译器必须知道要分配多少堆栈空间;它不能使用在运行时计算的值。 为数组中的每个元素分配默认值 0。 如果你不指定默认值,则每个元素最初包含恰好位于该内存位置的任意随机值。

    constexpr size_t size = 1000;// Declare an array of doubles to be allocated on the stackdouble numbers[size] {0};// Assign a new value to the first elementnumbers[0] = 1;// Assign a value to each subsequent element// (numbers[1] is the second element in the array.)for (size_t i = 1; i < size; i++){numbers[i] = numbers[i-1] * 1.1;}// Access each elementfor (size_t i = 0; i < size; i++){std::cout << numbers[i] << " ";}

数组中的第一个元素是第 0 个元素。 最后一个元素是 (n-1) 元素,其中 n 是数组可以包含的元素数量。 声明中的元素数量必须是整数类型,且必须大于 0。 你需要负责确保程序永远不会将大于 (size - 1) 的值传递给下标运算符。

仅当数组是 structunion 中的最后一个字段并启用了 Microsoft 扩展(未设置 /Za/permissive-)时,零大小的数组才是合法的。

分配和访问基于堆栈的数组的速度比基于堆的数组更快。 但是,堆栈空间是有限的。 数组元素的数量不能太大,以免占用过多的堆栈内存。 多大的数量算作“太大”取决于程序。 可以使用分析工具来确定数组是否太大。

堆声明

你可能需要一个太大的、以致无法在堆栈上分配的数组,或者需要一个其大小在编译时未知的数组。 可以使用 new[\] 表达式在堆上分配此数组。 运算符返回指向第一个元素的指针。 下标运算符处理指针变量的方式与处理基于堆栈的数组的方式相同。 还可以使用指针算术将指针移到数组中的任意元素。 你需要负责确保:

  • 始终保留原始指针地址的副本,以便可以在不再需要数组时删除内存。
  • 不会将指针地址递增或递减至超过数组边界。

以下示例演示在运行时如何在堆上定义一个数组。 其中演示了如何使用下标运算符和指针算术来访问数组元素:

void do_something(size_t size)
{// Declare an array of doubles to be allocated on the heapdouble* numbers = new double[size]{ 0 };// Assign a new value to the first elementnumbers[0] = 1;// Assign a value to each subsequent element// (numbers[1] is the second element in the array.)for (size_t i = 1; i < size; i++){numbers[i] = numbers[i - 1] * 1.1;}// Access each element with subscript operatorfor (size_t i = 0; i < size; i++){std::cout << numbers[i] << " ";}// Access each element with pointer arithmetic// Use a copy of the pointer for iteratingdouble* p = numbers;for (size_t i = 0; i < size; i++){// Dereference the pointer, then increment itstd::cout << *p++ << " ";}// Alternate method:// Reset p to numbers[0]:p = numbers;// Use address of pointer to compute bounds.// The compiler computes size as the number// of elements * (bytes per element).while (p < (numbers + size)){// Dereference the pointer, then increment itstd::cout << *p++ << " ";}delete[] numbers; // don't forget to do this!}
int main()
{do_something(108);
}

初始化数组

可以在循环中、以每次一个元素的方式或者在单个语句中初始化数组。 以下两个数组的内容是相同的:

    int a[10];for (int i = 0; i < 10; ++i){a[i] = i + 1;}int b[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

将数组传递给函数

将数组传递给函数时,该数组将作为指向第一个元素的指针传递,无论它是基于堆栈的数组还是基于堆的数组。 指针不包含任何其他大小或类型信息。 此行为称为指针衰减。 将数组传递给函数时,始终必须在单独的参数中指定元素数量。 此行为还意味着将数组传递给函数时不会复制数组元素。 若要防止函数修改元素,请将参数指定为指向 const 元素的指针。

以下示例演示了一个接受数组和长度的函数。 指针指向原始数组而不是副本。 由于参数不是 const,因此该函数可以修改数组元素。

void process(double *p, const size_t len)
{std::cout << "process:\n";for (size_t i = 0; i < len; ++i){// do something with p[i]}
}

将数组参数 p 声明并定义为 const,使其在函数块中为只读:

void process(const double *p, const size_t len);

也可以用这些方式声明相同的函数,而无需改变行为。 数组仍作为指向第一个元素的指针传递:

// Unsized array
void process(const double p[], const size_t len);// Fixed-size array. Length must still be specified explicitly.
void process(const double p[1000], const size_t len);

多维数组

从其他数组构造的数组是多维数组。 通过按顺序放置多个括起来的常数表达式来指定这些多维数组。 例如,考虑此声明:

int i2[5][7];

它指定类型为 int 的数组,从概念上以五行七列的二维矩阵排列,如下图所示:

Conceptual layout of a multidimensional array.

该图像是一个 7 个单元格宽、5 个单元格高的网格。 每个单元格都包含单元格的索引。 第一个单元格索引标记为 0,0。 该行中的下一个单元格是 0,1,依此类推,该行中的最后一个单元格是 0,6。 下一行以索引 1,0 开始。 之后单元格的索引为 1,1。 该行最后一个单元格的索引为 1,6。 此模式会重复,直到最后一行(从索引 4,0 开始)。 最后一行的最后一个单元格索引为 4,6。 :::image-end

可以声明具有初始化表达式列表的多维数组(如初始化表达式中所述)。 在这些声明中,可以省略指定第一维的边界的常数表达式。 例如:

// arrays2.cpp
// compile with: /c
const int cMarkets = 4;
// Declare a float that represents the transportation costs.
double TransportCosts[][cMarkets] = {{ 32.19, 47.29, 31.99, 19.11 },{ 11.29, 22.49, 33.47, 17.29 },{ 41.97, 22.09,  9.76, 22.55 }
};

前面的声明定义四列三行的数组。 行表示工厂,列表示从工厂装运到的市场。 值是从工厂运输到市场的成本。 忽略数组的第一个维度,但编译器会通过检查该初始值设定项来填充它。

对 n 维数组类型使用间接寻址运算符 (*) 将生成 n-1 维数组。 如果 n 为 1,则将生成标量(或数组元素)。

C++ 数组按行优先顺序存储。 行优先顺序意味着最后一个下标变化最快。

示例

还可以在函数声明中省略多维数组第一个维的边界规范,如下所示:

// multidimensional_arrays.cpp
// compile with: /EHsc
// arguments: 3
#include <limits>   // Includes DBL_MAX
#include <iostream>const int cMkts = 4, cFacts = 2;// Declare a float that represents the transportation costs
double TransportCosts[][cMkts] = {{ 32.19, 47.29, 31.99, 19.11 },{ 11.29, 22.49, 33.47, 17.29 },{ 41.97, 22.09,  9.76, 22.55 }
};// Calculate size of unspecified dimension
const int cFactories = sizeof TransportCosts /sizeof( double[cMkts] );double FindMinToMkt( int Mkt, double myTransportCosts[][cMkts], int mycFacts);using namespace std;int main( int argc, char *argv[] ) {double MinCost;if (argv[1] == 0) {cout << "You must specify the number of markets." << endl;exit(0);}MinCost = FindMinToMkt( *argv[1] - '0', TransportCosts, cFacts);cout << "The minimum cost to Market " << argv[1] << " is: "<< MinCost << "\n";
}double FindMinToMkt(int Mkt, double myTransportCosts[][cMkts], int mycFacts) {double MinCost = DBL_MAX;for( size_t i = 0; i < cFacts; ++i )MinCost = (MinCost < TransportCosts[i][Mkt]) ?MinCost : TransportCosts[i][Mkt];return MinCost;
}

Output

The minimum cost to Market 3 is: 17.29

编写函数 FindMinToMkt,以便添加不需要更改任何代码而仅需重新编译的新工厂。

初始化数组

具有类构造函数的对象数组由构造函数初始化。 如果初始化表达式列表中的项少于数组中的元素,则默认的构造函数将用于剩余元素。 如果没有为类定义默认构造函数,初始化表达式列表必须完整,即数组中的每个元素都必须有一个初始化表达式。

考虑定义了两个构造函数的Point 类:

// initializing_arrays1.cpp
class Point
{
public:Point()   // Default constructor.{}Point( int, int )   // Construct from two ints{}
};// An array of Point objects can be declared as follows:
Point aPoint[3] = {Point( 3, 3 )     // Use int, int constructor.
};int main()
{
}

aPoint 的第一个元素是使用构造函数 Point( int, int ) 构造的;剩余的两个元素是使用默认构造函数构造的。

静态成员数组(是否为 const)可在其定义中进行初始化(类声明的外部)。 例如:

// initializing_arrays2.cpp
class WindowColors
{
public:static const char *rgszWindowPartList[7];
};const char *WindowColors::rgszWindowPartList[7] = {"Active Title Bar", "Inactive Title Bar", "Title Bar Text","Menu Bar", "Menu Bar Text", "Window Background", "Frame"   };
int main()
{
}

访问数组元素

您可以使用数组下标运算符 ([ ]) 访问数组的各个元素。 如果使用不带下标的一维数组的名称,它将评估为指向数组第一个元素的指针。

// using_arrays.cpp
int main() {char chArray[10];char *pch = chArray;   // Evaluates to a pointer to the first element.char   ch = chArray[0];   // Evaluates to the value of the first element.ch = chArray[3];   // Evaluates to the value of the fourth element.
}

使用多维数组时,可以在表达式中使用各种组合。

// using_arrays_2.cpp
// compile with: /EHsc /W1
#include <iostream>
using namespace std;
int main() {double multi[4][4][3];   // Declare the array.double (*p2multi)[3];double (*p1multi);cout << multi[3][2][2] << "\n";   // C4700 Use three subscripts.p2multi = multi[3];               // Make p2multi point to// fourth "plane" of multi.p1multi = multi[3][2];            // Make p1multi point to// fourth plane, third row// of multi.
}

在前面的代码中,multidouble 类型的三维数组。 p2multi 指针指向大小为三的 double 类型的数组。 在此示例中,该数组与一个、两个和三个下标一起使用。 尽管更为常见的是指定所有下标(如在 cout 语句中),但有时选择数组元素的特定子集会很有用,如 cout 后面的语句中所示。

重载下标运算符

与其他运算符相似,下标运算符 ([]) 也可由用户重新定义。 如果没有重载下标运算符,下标运算符的默认行为是使用以下方法组合数组名称和下标:

*((array_name) + (subscript))

像涉及指针类型的所有加法中一样,缩放将自动执行以调整类型的大小。 结果值不是来自 array_name 的 n 个字节,而是数组的第 n 个元素。 有关此转换的详细信息,请参阅加法运算符。

同样,对于多维数组,将使用以下方法获取地址:

((array_name) + (subscript1 * max2 * max3 * ... * maxn) + (subscript2 * max3 * ... * maxn) + ... + subscriptn))

表达式中的数组

当数组类型的标识符出现在 sizeof、address-of (&) 或引用的初始化以外的表达式中时,该标识符将转换为指向第一个数组元素的指针。 例如:

char szError1[] = "Error: Disk drive not ready.";
char *psz = szError1;

指针 psz 指向数组 szError1 的第一个元素。 与指针不同,数组不是可修改的左值。 因此,以下赋值是非法的:

szError1 = psz;

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

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

相关文章

Python基础入门第七课笔记(自定义函数 define)

函数 函数必须先定义再调用 函数必须先定义再调用 函数必须先定义再调用 定义函数&#xff1a; def 函数名&#xff08;形参&#xff09;&#xff1a; 代码1 代码2 ………. 调用函数&#xff1a; 函数名&#xff08;实参&#xff09; 形参&…

视频智能分析支持摄像头异常位移检测,监测摄像机异常位移变化,保障监控状态

我们经常在生产场景中会遇到摄像头经过风吹日晒&#xff0c;或者异常的触碰&#xff0c;导致了角度或者位置的变化&#xff0c;这种情况下&#xff0c;如果不及时做出调整&#xff0c;会导致原本的监控条件被破坏&#xff0c;发生事件需要追溯的时候&#xff0c;查不到对应位置…

SSM在线员工订餐网站平台----计算机毕业设计

项目介绍 本项目分为前后台&#xff0c;前台为普通用户登录&#xff0c;后台为管理员登录&#xff1b; 用户角色包含以下功能&#xff1a; 用户登录与注册,查看首页,查看菜品详情,查看购物车,提交订单,查看订单,修改个人信息等功能。 管理员角色包含以下功能&#xff1a; 管…

Linux基础——进程地址空间

1. 地址空间的验证 之前我们在学习语言时&#xff0c;曾知道有下面这张图 对于这个图我们可以用下面的代码验证 运行后我们可以发现 其对应关系如下 我们使用fork函数&#xff0c;来分别对父子进程中的g_val进行修改&#xff0c;即 运行后我们可以发现 在子进程修改了g_val后…

基于springboot的课程作业管理系统

&#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;一 、设计说明 1.1背景及意义 随…

LLM之RAG实战(十三)| 利用MongoDB矢量搜索实现RAG高级检索

想象一下&#xff0c;你是一名侦探&#xff0c;身处庞大的信息世界&#xff0c;试图在堆积如山的数据中找到隐藏的一条重要线索&#xff0c;这就是检索增强生成&#xff08;RAG&#xff09;发挥作用的地方&#xff0c;它就像你在人工智能和语言模型世界中的可靠助手。但即使是最…

[C#]使用OpenCvSharp实现二维码图像增强超分辨率

【官方框架地址】 github.com/shimat/opencvsharp 【算法介绍】 借助于opencv自带sr.prototxt和sr.caffemodel实现对二维码图像增强 【效果展示】 【实现部分代码】 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin…

[MySQL]视图索引以及连接查询案列

目录 1.视图 1.1视图是什么 1.2视图的作用 1.3操作 1.3.1创建视图 1.3.2视图的修改 1.3.3删除视图 1.3.4查看视图 2.索引 2.1什么是索引 2.2为什么要使用索引 2.3索引的优缺点 2.3.1优点 2.3.2缺点 2.4索引的分类 3.连接查询案列 4.思维导图 1.视图 1.1视图是什么 视图…

基于华为云解析服务实现网站区域封禁

前言 中国大陆以外的网络攻击不断&#xff0c;个人博客时常遭受不明个人或组织的攻击&#xff0c;给网站的安全运行带来了巨大的风险&#xff0c;同时DDoS、CC攻击等还会消耗服务器的资源&#xff0c;站长可能需要因此支付高昂的服务器、CDN的流量费用。 因此&#xff0c;如果…

GPT商店要来了,手把手教你制作一个GPT

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 OpenAI 刚刚宣布下周将推出 GPT 商店&#xff0c;这对早期采用者来说是一个巨大的机会。 这里有一个简单的四步教程&#xff0c;教你如何现在就制作一个 GPT。 第…

MySQL之视图案例

目录 一.视图1.1 含义1.2 操作 二.案例三.思维导图 一.视图 1.1 含义 虚拟表&#xff0c;和普通表一样使用 1.2 操作 1.创建视图 create view 视图名 as 查询语句&#xff1b; 2.视图的修改 方式一&#xff1a; create or replace view 视图名 as 查询语句 方式二&#x…

uniapp 【专题详解 -- 时间】云数据库时间类型设计,时间生成、时间格式化渲染(uni-dateformat 组件的使用)

云数据表的时间类型设计 推荐使用时间戳 timestamp "createTime": {"bsonType": "timestamp","label": "创建时间&#xff1a;" }时间生成 获取当前时间 Date.now() .add({createTime: Date.now() })时间格式化渲染 下载安…