当前位置: 首页 > news >正文

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 + 10x104
&arr + 10x100 + 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];

读法:

  1. pArr[3] → 数组,含3个元素
  2. * → 元素是指针
  3. (*pArr[3])[3] → 每个指针指向一个 int[3] 数组

7.2 示例解析

int *(*f())[3];

步骤一:从标识符 f 开始读,我们按照“由内向外,遇到括号先处理”的原则来解析。

拆解顺序:

  1. 最里层是 f() → f 是一个函数,接受 无参数。
  2. (*f()) → 返回的是一个指针。
  3. (*f())[3] → 这个指针指向一个包含 3 个元素的数组。
  4. 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],我们可以将它映射为一个一维数组,这样就能够在内存中按行排列数据。

  1. 理解数组的内存布局:二维数组 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]
  1. 映射为一维数组:我们将 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;

http://www.mrgr.cn/news/97661.html

相关文章:

  • 【Linux高级IO(三)】Reactor
  • fastGPT—前端开发获取api密钥调用机器人对话接口(HTML实现)
  • java线程安全-单例模式-线程通信
  • 自动化框架及其设计搭建浅谈(三)--自动化测试框架设计最佳实践
  • Crow介绍及使用
  • Vue3+Vite+TypeScript+Element Plus开发-08.登录设计
  • CMake使用
  • MVS 无监督学习
  • Java垃圾回收的隐性杀手:过早晋升的识别与优化实战
  • Vue3实战三、Axios封装结合mock数据、Vite跨域及环境变量配置
  • Proximal Policy Optimization (PPO)2017
  • Qwen - 14B 怎么实现本地部署,权重参数大小:21GB
  • opencv无法设置禁用RGB转换问题
  • aosp13增加摄像头控制功能实现
  • 嵌入式笔试(一)
  • 【设计模式】创建型 -- 单例模式 (c++实现)
  • 单次 CMS Old GC 耗时长问题分析与优化
  • selenium元素获取
  • 初学STM32之编码器测速以及测频法的实现
  • 团体程序设计天梯赛题解(L2)