数组是一种按照特定顺序排列的对象集合体,数组中的每个对象称为“元素”。数组的每个元素都用“数组名+下标”的形式来表示,并且同一数组内的所有元素类型相同。数组可以由任何类型的数据构成(除 void
外),且数组的概念可以类比为数学中的向量或矩阵。
数组是一种按照特定顺序排列的对象集合体,数组中的每个对象称为“元素”。数组的每个元素都用“数组名+下标”的形式来表示,并且同一数组内的所有元素类型相同。数组可以由任何类型的数据构成(除 void
外),且数组的概念可以类比为数学中的向量或矩阵。
数组的声明
数组是一种用户自定义的数据类型,因此在使用之前必须先进行类型声明。声明数组时,首先需要指定数组元素的类型以及数组的大小。
数据类型 数组名[数组大小];
-
数据类型:表示数组中每个元素的数据类型,如
int
、float
、char
等。 -
数组名:用于标识该数组的名字。
-
数组大小:指定数组可以存储的元素个数。数组大小必须是一个整数常量,表示数组中可以存储的元素总数。
数据类型 标识符[常量表达式 1][常量表达式 2]…;
-
常量表达式:包括数组的维数(例如一维、二维等)以及每一维的大小【数组的结构】
每个下标表达式表示该维的元素个数,数组的元素总数为各下标表达式的乘积
数组的使用
数组只能对各个元素单独进行操作
数组名[下标表达式 1][下标表达式 2]…
-
(1) 下标表达式可以是合法的算术表达式,但其结果必须为整数。
-
(2) 下标值不得超过数组声明时的上下界,否则会导致数组越界错误,可能引发运行时错误。
范围 for
语句遍历数组
在 C++11 标准中,for
循环提供了一种新形式“范围 for
语句”,适用于遍历数组、vector
、string
等容器类型。范围 for
语句按元素顺序逐一访问序列中的每个元素,配合 auto
关键字自动推断元素类型,可以快速实现数组的遍历
int array[] = {1, 2, 3, 4, 5};
for (auto element : array) {
cout << element << " ";
}
此代码将逐个输出数组 array
中的元素
auto
的作用是自动推断 array
数组中元素的类型,而无需手动指定
auto
是 C++ 中的一个关键字,用于让编译器根据上下文自动推断变量的类型。
示例:
#include <iostream>
using namespace std;
int main() {
int a[10], b[10];
// 初始化数组 a,并将其反转存储到数组 b
for (int i = 0; i < 10; i++) {
a[i] = i * 2 - 1; // a[i] 为奇数序列
b[10 - i - 1] = a[i]; // 反转存储到 b 中
}
// 使用范围 for 循环输出 a 中每个元素
for (const auto &e : a) {
cout << e << " ";
}
cout << endl;
// 使用下标迭代循环输出 b 中每个元素
for (int i = 0; i < 10; i++) {
cout << b[i] << " ";
}
cout << endl;
return 0;
}
数组的存储
-
顺序存储: 数组的元素在内存中是顺序且连续存储的。对于一维数组,元素按照下标的顺序连续存放。对于二维数组及以上的多维数组,元素的存储顺序会影响到数组操作的效率和正确性。
-
一维数组存储: 一维数组可以看作一个列向量,例如
int arr[5];
会在内存中占用连续的5个存储单元,按下标从0到4的顺序存放。 -
二维数组存储: 例如
int m[2][3];
表示一个2行3列的矩阵。在C++中,二维数组以“行优先”的顺序存储,意味着先存放第一行的元素,再存放第二行的元素。 -
多维数组存储: 以此类推,对于三维及以上的数组,元素也按行优先的方式存放。每个维度的下标变化规则类似:最右边的下标变化最快,最左边的下标变化最慢。
数组的初始化
初始化概念: 数组初始化是指在声明数组时给其元素赋初值。对于基本类型的数组,可以直接赋值;对于对象数组,每个元素需要调用构造函数进行初始化
int a[3] = {1, 1, 1}; // 声明并初始化一个具有3个元素的整型数组
int b[] = {2, 4, 6}; // 可以省略元素个数,编译器会自动推断为3
部分初始化: 如果在初始化时没有给出所有元素的初值,未初始化的元素会被默认初始化为0(对于基本数据类型)
int c[5] = {1, 2}; // c[0] = 1, c[1] = 2, c[2] = 0, c[3] = 0, c[4] = 0
内存分配: 数组在内存中占用的空间是连续的,因此对于大数组要小心内存的使用,以免造成内存不足或溢出。
越界访问: 数组的下标从0开始,任何超出范围的访问(如访问负值或超出定义大小的下标)会导致未定义行为,因此必须保证下标的合法性。
二维数组的行优先存储: 这种存储方式在对数据的遍历和处理时需要特别注意,尤其是在使用嵌套循环进行访问时,外层循环控制行,内层循环控制列。
动态数组: C++还支持动态数组,可以使用
new
关键字在堆上分配数组空间,创建后需要手动释放(delete
)。
数组作为函数参数
数组的元素和数组名都可以作为函数的参数,以实现函数间的数据传递和共享。
-
使用数组元素:可以将数组元素作为调用函数时的实参,这与使用单个变量(或对象)作为实参是完全相同的。
-
使用数组名:如果使用数组名作为函数的参数,则实参和形参都应该是数组名,且类型要相同。与普通变量参数不同,使用数组名传递数据时,传递的是数组的地址。形参数组和实参数组的首地址重合,后面的元素按照各自在内存中的存储顺序进行对应。因此,实参数组的元素个数不应该少于形参数组的元素个数。
-
注意事项:如果在被调函数中对形参数组的元素值进行改变,主调函数中的实参数组的相应元素值也会改变
#include <iostream>
using namespace std;
// 函数 rowSum 计算二维数组 a 每行元素的值的和,nRow 是行数
void rowSum(int a[][4], int nRow)
{
for (int i = 0; i < nRow; i++)
{ // 遍历每一行
for (int j = 1; j < 4; j++) // 从第二列开始加和
a[i][0] += a[i][j]; // 将每行的和存储在第一个元素中
}
}
int main()
{
// 声明并初始化数组
int table[3][4] = { {1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6} };
// 输出数组元素
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
cout << table[i][j] << " "; // 输出每个元素
cout << endl; // 换行
}
rowSum(table, 3); // 调用子函数,计算各行的和
// 输出计算结果
for (int i = 0; i < 3; i++)
cout << "Sum of row " << i << " is " << table[i][0] << endl; // 输出每行的和
return 0;
}
C++语言对数组不作越界检查,使用时要注意
对象数组
数组的元素不仅可以是基本数据类型,也可以是自定义类型。
对象数组的元素是对象,它们不仅具有数据成员,还包括函数成员。
声明
类名 数组名[常量表达式];
例如::Student students[5];
访问对象数组中的元素,可以使用数组的下标来进行访问,然后通过.
操作符访问其中的成员变量或成员函数
数组名[下标].成员名
比如,访问 students
数组中第 3 个 Student
对象的 name
成员变量:students[2].name;
对象数组初始化和析构
数组中每一个元素对象被创建时,系统都会调用类构造函数初始化该对象
-
显式初始化的元素:通过初始化列表赋值的元素会调用带参构造函数。
-
未显式初始化的元素:未指定初始值的数组元素将自动调用默认构造函数初始化。
-
构造顺序:当对象数组被创建时,数组中的对象按照从第一个元素到最后一个元素的顺序调用构造函数。
-
析构顺序:当对象数组被销毁时,数组中的对象按照从最后一个元素到第一个元素的顺序依次调用析构函数。
构造和析构是镜像操作
数组中第一个构造的对象会是最后一个析构的对象。
// Point.h
#ifndef POINT_H // 如果未定义 POINT_H,则定义它,防止重复包含头文件
#define POINT_H
class Point { // Point 类的定义
public:
Point(); // 默认构造函数
Point(int x, int y); // 带参数的构造函数
~Point(); // 析构函数
void move(int newX, int newY); // 移动点到新位置的成员函数
int getX() const { return x; } // 获取 x 坐标值的函数
int getY() const { return y; } // 获取 y 坐标值的函数
static void showCount(); // 静态函数成员,用于显示 Point 对象的数量(这里未实现)
private:
int x, y; // 点的坐标
};
#endif // POINT_H 的结束标记
// Point.cpp
#include <iostream>
#include "Point.h" // 包含 Point 类的定义
using namespace std;
Point::Point() : x(0), y(0) { // 默认构造函数,初始化 x 和 y 为 0
cout << "Default Constructor called." << endl;
}
Point::Point(int x, int y) : x(x), y(y) { // 带参数的构造函数,初始化 x 和 y 为传入的值
cout << "Constructor called." << endl;
Point::~Point() { // 析构函数
cout << "Destructor called." << endl;
}
void Point::move(int newX, int newY) { // 移动点到新的坐标
cout << "Moving the point to (" << newX << ", " << newY << ")" << endl;
x = newX; // 更新 x 坐标
y = newY; // 更新 y 坐标
}
// 6-3.cpp
#include "Point.h" // 包含 Point 类的定义
#include <iostream>
using namespace std;
int main() {
cout << "Entering main..." << endl;
Point a[2]; // 创建 Point 对象数组 a[2],调用默认构造函数初始化每个元素
for (int i = 0; i < 2; i++) { // 遍历数组 a 的每个元素
a[i].move(i + 10, i + 20); // 调用 move 函数,将每个点移动到新坐标 (10, 20) 和 (11, 21)
}
cout << "Exiting main..." << endl; // 程序结束,准备退出 main 函数,自动调用析构函数销毁对象
return 0;
}
-
构造函数调用顺序:从第一个数组元素开始,按顺序调用。
-
析构函数调用顺序:当对象数组被销毁时,析构函数按逆序调用,从最后一个元素开始