C++中数组的概念
文章目录
- 一、数组的定义
- 二、什么是一维数组?
- 2.1 一维数组的声明
- 2.2 一维数组的初始化
- 2.3 一维数组的使用
- 三、什么是一维数组的数组名?
- 四、一维数组与指针的关系
- 五、数组指针和指针数组的区别
- 5.1 指针数组(array of pointers)
- 5.2 数组指针(pointer to array)
- 六、函数参数中使用数组指针
- 6.1 为什么用数组指针?
- 6.1 一维数组 + 数组指针
- 6.3 多维数组 + 数组指针
- 七、指向数组的指针数组
- 7.1 概念介绍
- 7.2 示例解析
- 八、二维数组的定义和使用
- 8.1 二维数组的定义
- 8.2 二维数组的初始化方式
- 8.3 访问二维数组元素和二维数组的大小
- 8.4 二维数组作为函数参数传递
- 九、二维数组的数组名
- 9.1 数组名到底是什么?
- 9.2 a 的类型是什么?
- 9.3 二维数组名的实际用途
- 十、一维数组模拟二维数组
- 十一、二维数组与指针的转换
- 11.1 二维数组和指针的关系
- 11.2 指针与数组的转换
- 11.3 二维数组与指针数组的区别
- 11.4 二维数组与指针结合使用的实际应用
一、数组的定义
在 C++ 中,数组(Array)是一种用于存储固定大小、相同类型元素的容器。数组在内存中是连续存储的,因此可以通过索引快速访问每一个元素。
二、什么是一维数组?
一维数组是相同类型的数据元素的线性集合,在内存中是连续存储的。
2.1 一维数组的声明
类型名 数组名[数组大小];
示例:
int scores[5]; // 声明一个可以保存5个整数的一维数组
2.2 一维数组的初始化
完全初始化:
int scores[5] = {90, 85, 78, 92, 88};
部分初始化(未写的部分默认为 0):
int scores[5] = {90, 85}; // 相当于 {90, 85, 0, 0, 0}
自动推导大小:
int scores[] = {90, 85, 78}; // 系统自动推断大小为3
2.3 一维数组的使用
访问数组元素:
std::cout << scores[0]; // 访问第一个元素(注意索引从0开始)
scores[2] = 100; // 修改第三个元素
注意事项:
- 数组大小固定,不能动态变化。
- 数组索引从 0 开始,到 n-1 结束。
- 访问越界会导致未定义行为,不要访问 scores[5]。
- 数组名本质上是一个指向第一个元素的指针
三、什么是一维数组的数组名?
在 C++ 中,一维数组的数组名其实表示的是数组第一个元素的地址。
比如:
int arr[5] = {10, 20, 30, 40, 50};
这里的 arr 就相当于 &arr[0],是一个指向第一个元素的指针。
举例说明:
#include <iostream>
using namespace std;int main() {int arr[5] = {10, 20, 30, 40, 50};cout << "arr = " << arr << endl; // 输出数组名,其实是地址cout << "&arr[0] = " << &arr[0] << endl; // 也是地址cout << "*arr = " << *arr << endl; // 解引用,输出第一个元素:10cout << "arr[2] = " << *(arr + 2) << endl; // 使用指针方式访问第三个元素return 0;
}
虽然数组名表现得像指针,但它不是普通的指针变量:
int* p = arr; // 正确
arr = p; // ❌ 错误,数组名不能作为左值
也就是说,数组名是不能被赋值的,它在声明时就绑定到那块连续内存了。
总结一句话:一维数组的数组名本质是一个指向首元素的指针常量,在很多情况下可以像指针一样使用,但它不是一个可以改变的变量。
3.1 数组名与&arr和&arr[0]的区别
先看三者的含义:
假设有下面的数组:
int arr[5] = {10, 20, 30, 40, 50};
3.2 arr 和 &arr[0] 的区别?
- 相同点:它们的值是一样的,都是首元素的地址。
- 不同点:它们的类型不同!
arr // 类型是 int*
&arr[0] // 类型也是 int*
虽然类型相同,语义稍有区别:
- arr 是数组名,表示“数组首元素的地址”
- &arr[0] 是明确取“第一个元素的地址”
3.3 &arr 是什么?
- &arr 表示“整个数组的地址”
- 它的类型是:int (*)[5],即“指向长度为5的数组的指针”
这个指针不是指向某个元素,而是指向整个数组块!
举个例子:
#include <iostream>
using namespace std;int main() {int arr[5] = {10, 20, 30, 40, 50};cout << "arr = " << arr << endl;cout << "&arr[0] = " << &arr[0] << endl;cout << "&arr = " << &arr << endl;cout << "arr + 1 = " << arr + 1 << endl;cout << "&arr[0] + 1 = " << &arr[0] + 1 << endl;cout << "&arr + 1 = " << &arr + 1 << endl;return 0;
}
输出分析(假设起始地址为 0x100):
内存图示(假设int是4字节):
地址 内容
0x100 arr[0] = 10
0x104 arr[1] = 20
0x108 arr[2] = 30
0x10C arr[3] = 40
0x110 arr[4] = 50arr / &arr[0] → 0x100
&arr → 0x100,类型不同但地址一样
arr + 1 → 0x104
&arr + 1 → 0x100 + 20(整个数组的地址+1个数组块)
总结对比表:
四、一维数组与指针的关系
在 C++ 中,一维数组名其实就是一个指向首元素的指针,所以可以用指针来访问数组中的元素。
示例:数组和指针的基本结合用法
#include <iostream>
using namespace std;int main() {int arr[5] = { 10, 20, 30, 40, 50 };int* p = arr; // 指针p指向数组首元素cout << "用数组方式:arr[2] = " << arr[2] << endl;cout << "用指针方式:*(p + 2) = " << *(p + 2) << endl;return 0;
}
用指针遍历数组:
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;for (int i = 0; i < 5; ++i) {cout << *(p + i) << " ";
}
或者使用指针本身进行++操作:
int* end = arr + 5; // 指向最后一个元素的下一个地址
while (p < end) {cout << *p << " ";++p;
}
数组与指针的对比:
把数组传给函数(本质上传的是指针)
void printArray(int* p, int size) {for (int i = 0; i < size; ++i) {cout << p[i] << " ";}
}int main() {int arr[3] = {10, 20, 30};printArray(arr, 3); // arr会退化为指针传给函数
}
等价于:
void printArray(int arr[], int size); // 实际上跟 int* 一样
注意事项:
数组名是一个指针常量,它的值(指向地址)不能修改,而普通指针可以:
int arr[5];
int* p = arr;
p = p + 1; // OK
arr = arr + 1; // ❌ 错误:数组名不可修改
用指针访问时要特别小心越界,指针不像数组访问有明显的范围检查。
五、数组指针和指针数组的区别
5.1 指针数组(array of pointers)
指针数组:数组里面每个元素是指针。
定义方式:
int* pArr[3]; // 一个数组,包含3个 int* 类型的元素
pArr 是一个有 3 个元素的数组,每个元素是 int* 指针。
场景举例:多个指向不同变量的指针
#include <iostream>
using namespace std;int main() {int a = 10, b = 20, c = 30;int* pArr[3] = { &a, &b, &c };for (int i = 0; i < 3; ++i) {cout << *pArr[i] << " "; // 输出:10 20 30}return 0;
}
5.2 数组指针(pointer to array)
数组指针:一个指向整个数组的指针。
定义方式:
int (*p)[5]; // 一个指向长度为5的int数组的指针
p 是一个指针,它指向一个 int[5] 的数组。
场景举例:传递整个数组的地址
#include <iostream>
using namespace std;int main() {int arr[5] = { 1, 2, 3, 4, 5 };int (*p)[5] = &arr; // p 指向整个数组cout << (*p)[2] << endl; // 输出3,相当于 arr[2]return 0;
}
语法对比表:
更直观理解(类比):
六、函数参数中使用数组指针
6.1 为什么用数组指针?
普通的数组参数比如:
void func(int arr[]) { ... }
在函数中其实就是 退化成 int* 指针,你拿不到原始数组的长度等信息。而数组指针能保留「整块数组」的概念。
6.1 一维数组 + 数组指针
#include <iostream>
using namespace std;void printArray(int (*p)[5]) {for (int i = 0; i < 5; ++i) {cout << (*p)[i] << " ";}
}int main() {int arr[5] = { 10, 20, 30, 40, 50 };printArray(&arr); // 注意传的是 &arr,不是 arrreturn 0;
}
为什么要用 &arr?
- arr → 是 int*
- &arr → 是 int (*)[5](数组指针)
- 这和函数的参数 int (*p)[5] 类型匹配
6.3 多维数组 + 数组指针
#include <iostream>
using namespace std;void print2D(int (*p)[4], int rows) {for (int i = 0; i < rows; ++i) {for (int j = 0; j < 4; ++j) {cout << p[i][j] << " ";}cout << endl;}
}int main() {int arr[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};print2D(arr, 3); // arr 自动转换成 int(*)[4]return 0;
}
总结数组指针函数参数写法:
七、指向数组的指针数组
7.1 概念介绍
指向数组的指针数组就是:一个数组,里面的每个元素是指针,这些指针指向数组。
拆词理解:
- 数组 → 有一组元素
- 指针数组→ 每个元素是个指针(T*)
- 指向数组的指针数组 → 每个指针指向一个数组(如 int (*)[3])
#include <iostream>
using namespace std;int main() {int a[3] = { 1, 2, 3 };int b[3] = { 4, 5, 6 };int c[3] = { 7, 8, 9 };// 定义:指向3个元素的数组的指针,每一个元素都是一个数组int (*pArr[3])[3]; // pArr 是一个指针数组,包含3个指向数组的指针pArr[0] = &a;pArr[1] = &b;pArr[2] = &c;for (int i = 0; i < 3; ++i) {for (int j = 0; j < 3; ++j) {cout << (*pArr[i])[j] << " ";}cout << endl;}return 0;
}
对比其他相关定义:
阅读技巧:从变量名“pArr”开始读:
int (*pArr[3])[3];
读法:
- pArr[3] → 数组,含3个元素
- * → 元素是指针
- (*pArr[3])[3] → 每个指针指向一个 int[3] 数组
7.2 示例解析
int *(*f())[3];
步骤一:从标识符 f 开始读,我们按照“由内向外,遇到括号先处理”的原则来解析。
拆解顺序:
- 最里层是 f() → f 是一个函数,接受 无参数。
- (*f()) → 返回的是一个指针。
- (*f())[3] → 这个指针指向一个包含 3 个元素的数组。
- int *(f())[3] → 这个数组的元素类型是 int。
所以完整意思是:f 是一个函数,没有参数,返回一个指针,这个指针指向一个有 3 个元素的数组,而这个数组的每个元素是 int*。换句话说就是f 返回int* 类型的指针组成的数组(长度为 3)的指针。
举个例子(实际使用)
#include <iostream>
using namespace std;int* arr1[3] = {nullptr, nullptr, nullptr};int* (*f())[3] {return &arr1; // 返回指向数组的指针
}int main() {int* (*p)[3] = f();// 访问 p[0][0], p[0][1], etc.
}
小结口诀(读复杂声明):
- 从变量名(标识符)开始读
- 遇到 () 说明是函数
- 遇到 [] 说明是数组
- * 是指针,看结合谁
int (*(*x())[5])(); //函数返回数组,数组里是函数指针
我们从 x 开始读,遵循优先级规则。
拆解步骤:
- x() → x 是一个函数,没有参数。
- (*x()) → 返回一个指针
- (*x())[5] → 这个指针指向一个有 5 个元素的数组
- (*x())[5] 中每个元素是:(*x())[i]
- (*x())i → 说明每个元素是函数指针
- int (*(*x())[5])() → 每个函数指针,指向的函数返回 int,参数未知
总结一句话:x 是一个函数(无参数),它返回一个指针,这个指针指向一个长度为 5 的数组,数组里的每个元素是返回 int 的函数指针。
举个例子(实际用法)
#include <iostream>
using namespace std;int func1() { return 1; }
int func2() { return 2; }
int func3() { return 3; }
int func4() { return 4; }
int func5() { return 5; }int (*funcArray[5])() = {func1, func2, func3, func4, func5};int (*(*x())[5])() {return &funcArray; // 返回指向函数指针数组的指针
}int main() {auto funcs = x(); // funcs 是 int (*[5])()for (int i = 0; i < 5; ++i) {cout << funcs[0][i]() << " "; // 注意:funcs 是指向数组的指针,先解引用}return 0;
}
八、二维数组的定义和使用
8.1 二维数组的定义
二维数组定义的一般形式是:
类型说明符 数组名[常量表达式1][常量表达式2]
定义了一个三行四列的数组,数组名为a其元素类型为整型,该数组的元素个数为3*4,即:
二维数组a是按行进行存放的,先存放a[0]行,再存放a[1]行、a[2]行,并且每行有四个元素,也是依次存放的。
8.2 二维数组的初始化方式
1. 行列都写出来:
int a[2][3] = {{1, 2, 3},{4, 5, 6}
};
2. 不写行数,让编译器推断:
int a[][3] = {{1, 2, 3},{4, 5, 6}
}; // 自动推断为 a[2][3]
列数必须指定,因为内存布局要连续
8.3 访问二维数组元素和二维数组的大小
#include <stdio.h>
#include <stdlib.h>int main() {//int a[3][4] = {1, 2, 3}; //前三个元素初始化为1,2,3,后面的元素默认初始化为0int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};int n = sizeof(a) / sizeof(a[0][0]); //二维数组的个数int line = sizeof(a) / sizeof(a[0]); //行数=二维数组总大小除以一行的大小int clu = sizeof(a[0]) / sizeof(a[0][0]); //列数:行大小除以一个元素的大小printf("%d %d %d\n", n, line, clu);return 0;
}
8.4 二维数组作为函数参数传递
1. 固定列数传递
void print(int arr[][3], int rows) {for (int i = 0; i < rows; ++i)for (int j = 0; j < 3; ++j)cout << arr[i][j] << " ";
}
2. 使用指针传递
void print(int (*arr)[3], int rows) {for (int i = 0; i < rows; ++i)for (int j = 0; j < 3; ++j)cout << arr[i][j] << " ";
}
九、二维数组的数组名
9.1 数组名到底是什么?
在 C++ 中,数组名(如 a)本质上是一个常量指针,但又稍有不同:
- 对于 一维数组,a 会自动退化为指向首元素的指针(类型是 int*)
- 对于 二维数组,a 也会退化,但是指向“行”的指针!
9.2 a 的类型是什么?
举个例子:
int a[2][3] = {{1, 2, 3},{4, 5, 6}
};
内存结构示意图(int a[2][3])
a → &a[0] → a[0][0] a[0][1] a[0][2] | a[1][0] a[1][1] a[1][2]
- a 的类型是 int (*)[3],是指向含3个int的一维数组的指针
- 所以 a + 1 会跳过一整行,也就是 3 * sizeof(int) 字节
实验:打印地址
cout << a << endl; // 地址1:等价于 &a[0]
cout << a + 1 << endl; // 地址2:等价于 &a[1]
cout << &a[0][0] << endl; // 地址3:首元素地址
9.3 二维数组名的实际用途
你可以把二维数组名当作函数参数传递,比如:
void print(int (*arr)[3], int rows) {for (int i = 0; i < rows; ++i)for (int j = 0; j < 3; ++j)cout << arr[i][j] << " ";
}
然后这样调用:
print(a, 2); // 传入二维数组名 a
所以二维数组名 a,在表达式中会退化为指向一维数组(行)的指针,其类型是 int (*)[列数]。
如下示例:
#include <stdio.h>
#include <stdlib.h>int main() {int a[2][3] = {0};printf("a[0][0]=%d\n", a[0][0]); //第0行第0个元素printf("&a[0][0]=%d\n", &a[0][0]); //第0行第0个元素的地址printf("a[0]=%d\n", a[0]); //代表第0行一维数据的数组名, a[0]=&a[0][0]printf("&a[0]=%d\n", &a[0]); //第0行的地址printf("a=%d\n", a); //二维数组的数组名,代表二维数组,也代表首行地址printf("&a=%d\n", &a); //二维数组的地址printf("-----------------------------------------------\n");printf("&a[0][0]+1=%d\n", &a[0][0]+1); //元素地址加1,跨过一个元素printf("a[0]+1=%d\n", a[0]+1); //元素地址加1,跨过一个元素printf("&a[0]+1=%d\n", &a[0]+1); //行地址加1,跨过一行printf("a+1=%d\n", a+1); //行地址加1,跨过一行printf("&a+1=%d\n", &a+1); //二维数组地址加1,跨过整个数组return 0;
}
输出结果:
十、一维数组模拟二维数组
假设我们有一个二维数组 a[3][4],我们可以将它映射为一个一维数组,这样就能够在内存中按行排列数据。
- 理解数组的内存布局:二维数组 a[3][4] 其实是一个 3 行 4 列的数组,它在内存中是按行优先(Row-major)存储的:
a[0][0], a[0][1], a[0][2], a[0][3]
a[1][0], a[1][1], a[1][2], a[1][3]
a[2][0], a[2][1], a[2][2], a[2][3]
- 映射为一维数组:我们将 a[3][4] 映射为一个一维数组 b[12],它的元素依然保持行优先顺序存储。
数组模拟图:
示例代码:一维数组模拟二维数组
#include <iostream>
using namespace std;int main() {int rows = 3, cols = 4;int b[12]; // 一维数组,模拟 3x4 的二维数组// 模拟二维数组赋值for (int i = 0; i < rows; ++i) {for (int j = 0; j < cols; ++j) {b[i * cols + j] = i * cols + j + 1; // 填充 1~12 的值}}// 打印模拟的二维数组for (int i = 0; i < rows; ++i) {for (int j = 0; j < cols; ++j) {cout << b[i * cols + j] << " "; // 模拟访问二维数组}cout << endl;}return 0;
}
代码说明:
模拟二维数组赋值:
- b[i * cols + j] 计算当前元素在一维数组中的索引,这里 i 是行索引,j 是列索引。
- i * cols + j 的方式保证了数据是按行优先顺序存储的。
打印模拟的二维数组:
- b[i * cols + j] 用来模拟访问二维数组 a[i][j]。
十一、二维数组与指针的转换
11.1 二维数组和指针的关系
二维数组其实是一个指向数组的指针,而指针的行为与数组紧密相关。在 C++ 中,二维数组和指针之间有很多有趣的转换方式。
假设我们有一个二维数组 a[3][4],它是一个包含 3 行 4 列的数组。
int a[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}
};
11.2 指针与数组的转换
1. 指向二维数组的指针
对于一个二维数组 a[3][4],它在内存中是按行优先顺序存储的,数组名 a 代表的是指向第一行的指针。我们可以定义一个指向一维数组的指针来访问二维数组。
// 指向包含 4 个元素的数组的指针
int (*ptr)[4] = a; // 这里的 a 就是一个指向包含 4 个 int 元素的数组的指针cout << ptr[0][0] << endl; // 输出 1,即访问第一行的第一个元素
cout << ptr[1][2] << endl; // 输出 7,即访问第二行的第三个元素
解释:ptr 是一个指向 int[4] 类型数组的指针,指向 a[0],即数组的第一行。
2. 将二维数组转换为指向单个元素的指针
二维数组的元素就是一维数组(即数组的某一行),可以通过指向元素的指针来访问:
// 指向二维数组中单个元素的指针
int* ptrElem = &a[1][2]; // 指向第二行第三列的元素cout << *ptrElem << endl; // 输出 7
解释:ptrElem 是指向单个 int 元素的指针,指向 a[1][2],即二维数组中的第 2 行第 3 列的元素。
3. 使用指针遍历二维数组
使用指针来遍历二维数组是很常见的操作,可以通过不同方式来模拟数组访问。
int* ptr = &a[0][0]; // 指向数组的首元素for (int i = 0; i < 3; ++i) {for (int j = 0; j < 4; ++j) {cout << *(ptr + i * 4 + j) << " "; // 计算出对应的地址偏移}cout << endl;
}
4. 使用数组指针的方式遍历
int (*ptr)[4] = a; // 指向包含4个元素的数组的指针for (int i = 0; i < 3; ++i) {for (int j = 0; j < 4; ++j) {cout << ptr[i][j] << " "; // 访问每一行的元素}cout << endl;
}
5. 通过 ptr + i * 4 + j 访问
int* ptr = &a[0][0];for (int i = 0; i < 3; ++i) {for (int j = 0; j < 4; ++j) {cout << *(ptr + i * 4 + j) << " "; // 通过地址偏移来访问}cout << endl;
}
解释:ptr + i * 4 + j 是通过指针偏移来访问二维数组元素的方式。这里 i * 4 用来计算行的偏移,j 用来计算列的偏移。
11.3 二维数组与指针数组的区别
有时候可能会把指针数组和指向数组的指针搞混。
- 指向数组的指针:是一个指针,指向一个一维数组(即数组的一行)。
- 指针数组:是一个数组,数组中的每个元素都是指向单个元素的指针。
示例:指向数组的指针
int a[3][4]; // 二维数组
int (*ptr)[4] = a; // 指向一维数组的指针
示例:指针数组
int* ptr[3]; // 指向 int 的指针数组
ptr[0] = &a[0][0]; // 指向 a[0][0]
ptr[1] = &a[1][0]; // 指向 a[1][0]
ptr[2] = &a[2][0]; // 指向 a[2][0]
11.4 二维数组与指针结合使用的实际应用
二维数组可以作为参数传递给函数,可以使用指针来接受这个参数。
void print(int (*arr)[4], int rows) {for (int i = 0; i < rows; ++i) {for (int j = 0; j < 4; ++j) {cout << arr[i][j] << " ";}cout << endl;}
}int main() {int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};print(a, 3); // 传递二维数组
}
使用指针动态分配二维数组:
int** arr = new int*[3]; // 为3行分配内存
for (int i = 0; i < 3; ++i) {arr[i] = new int[4]; // 为每一行分配4列
}// 释放内存
for (int i = 0; i < 3; ++i) {delete[] arr[i];
}
delete[] arr;