C++中数组的概念

一、数组的定义

  在 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;       // 解引用,输出第一个元素:10
    cout << "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] = 50

arr / &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,不是 arr

    return 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,后面的元素默认初始化为0
    int 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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值