【C++入门】数组:从基础到实践

目录

一、数组概述:数据存储的基石

[1.1. 核心价值](#1.1. 核心价值)

[1.2. 主要类型对比](#1.2. 主要类型对比)

二、数组的基本概念

[2.1 什么是数组](#2.1 什么是数组)

[2.2 数组的声明和初始化](#2.2 数组的声明和初始化)

[2.2.1 声明数组](#2.2.1 声明数组)

[2.2.2 初始化数组](#2.2.2 初始化数组)

[2.3 访问数组元素](#2.3 访问数组元素)

三、一维数组

[3.1 一维数组的遍历](#3.1 一维数组的遍历)

[3.2 一维数组的常见操作](#3.2 一维数组的常见操作)

四、二维数组

[4.1 二维数组的概念](#4.1 二维数组的概念)

[4.2 二维数组的声明和初始化](#4.2 二维数组的声明和初始化)

[4.3 二维数组的遍历](#4.3 二维数组的遍历)

[4.4 二维数组的应用场景](#4.4 二维数组的应用场景)

五、多维数组

[5.1 三维数组的声明和初始化](#5.1 三维数组的声明和初始化)

[5.2 多维数组的遍历](#5.2 多维数组的遍历)

六、数组与函数

[6.1 数组作为函数参数](#6.1 数组作为函数参数)

[6.2 函数返回数组](#6.2 函数返回数组)

七、数组的优缺点

[7.1 优点](#7.1 优点)

[7.2 缺点](#7.2 缺点)

八、内存机制深度剖析

九、高级操作技巧

十、性能优化实战

十一、现代C++特性应用

十二、常见问题解决方案

十三、总结

十四、参考资料


在 C++ 编程中,数组是一种非常基础且重要的数据结构。它允许存储和管理一组相同类型的数据元素。无论是处理简单的数据集合,还是构建复杂的算法和程序,数组都扮演着不可或缺的角色。

一、数组概述:数据存储的基石

1.1. 核心价值

  • 连续内存空间存储相同类型元素

  • 支持随机访问(时间复杂度O(1))

  • 编译期确定大小(静态数组)或运行时动态分配(动态数组)

  • 内存效率极高,无额外管理开销

1.2. 主要类型对比

特性 内置数组 std::array(C++11) std::vector
大小确定 编译时 编译时 运行时
内存管理 自动/手动 自动 自动
边界检查 at()方法提供 at()方法提供
迭代器支持 原生指针 标准迭代器 标准迭代器
传递效率 退化指针 值/引用传递 引用传递

二、数组的基本概念

2.1 什么是数组

数组是一种由相同类型的元素组成的集合,这些元素在内存中是连续存储的。每个元素都可以通过一个唯一的索引来访问,索引通常是一个整数,从 0 开始计数。例如,一个包含 5 个整数的数组可以表示为int arr[5],其中arr是数组的名称,5是数组的大小。

2.2 数组的声明和初始化

2.2.1 声明数组

在 C++ 中,声明数组需要指定数组的类型和大小。以下是一些声明数组的示例:

cpp 复制代码
// 声明一个包含5个整数的数组
int arr1[5];

// 声明一个包含10个字符的数组
char arr2[10];

// 声明一个包含3个双精度浮点数的数组
double arr3[3];

需要注意的是,数组的大小必须是一个常量表达式,即在编译时就可以确定的值。

2.2.2 初始化数组

数组可以在声明时进行初始化,也可以在声明后再进行赋值。以下是几种常见的初始化方式:

①逐个元素初始化

cpp 复制代码
int arr[5] = {1, 2, 3, 4, 5};

声明了一个包含 5 个整数的数组,并使用大括号{}对每个元素进行了初始化。数组的第一个元素是1,第二个元素是2,以此类推。

② 部分元素初始化

cpp 复制代码
int arr[5] = {1, 2};

在这种情况下,数组的前两个元素被初始化为12,其余元素会被自动初始化为 0。

③省略数组大小

cpp 复制代码
int arr[] = {1, 2, 3, 4, 5};

当使用大括号初始化数组时,可以省略数组的大小,编译器会根据初始化列表中的元素个数自动确定数组的大小。

④默认初始化

cpp 复制代码
int arr[5];

如果只声明数组而没有进行初始化,数组中的元素将是未定义的。对于全局数组和静态数组,元素会被自动初始化为 0;对于局部数组,元素的值是不确定的。

2.3 访问数组元素

数组元素可以通过索引来访问。索引是一个整数,从 0 开始计数,最大索引为数组大小减 1。以下是一个访问数组元素的示例:

cpp 复制代码
#include <iostream>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    std::cout << "数组的第一个元素是: " << arr[0] << std::endl;
    std::cout << "数组的第三个元素是: " << arr[2] << std::endl;
    return 0;
}

通过索引02分别访问了数组的第一个和第三个元素,并将其输出。

三、一维数组

3.1 一维数组的遍历

遍历数组意味着依次访问数组中的每个元素。常见的遍历方式有使用for循环和while循环。

①使用for循环遍历

cpp 复制代码
#include <iostream>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    for (int i = 0; i < 5; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    return 0;
}

使用for循环从索引 0 开始,依次访问数组的每个元素,并将其输出。

②使用while循环遍历

cpp 复制代码
#include <iostream>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int i = 0;
    while (i < 5) {
        std::cout << arr[i] << " ";
        i++;
    }
    std::cout << std::endl;
    return 0;
}

使用while循环实现了同样的遍历功能。

3.2 一维数组的常见操作

**①查找元素:**查找数组中是否存在某个特定的元素是一个常见的操作。以下是一个简单的查找函数:

cpp 复制代码
#include <iostream>

bool findElement(int arr[], int size, int target) {
    for (int i = 0; i < size; i++) {
        if (arr[i] == target) {
            return true;
        }
    }
    return false;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int target = 3;
    if (findElement(arr, 5, target)) {
        std::cout << "元素 " << target << " 存在于数组中。" << std::endl;
    } else {
        std::cout << "元素 " << target << " 不存在于数组中。" << std::endl;
    }
    return 0;
}

定义了一个findElement函数,用于查找数组中是否存在指定的元素。

②求数组元素的和

计算数组中所有元素的和也是一个常见的操作。以下是一个求和函数:

cpp 复制代码
#include <iostream>

int sumArray(int arr[], int size) {
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i];
    }
    return sum;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int total = sumArray(arr, 5);
    std::cout << "数组元素的和是: " << total << std::endl;
    return 0;
}

定义了一个sumArray函数,用于计算数组中所有元素的和。

③数组排序

对数组进行排序可以使数组元素按照一定的顺序排列。C++ 标准库提供了std::sort函数来实现排序功能。

cpp 复制代码
#include <iostream>
#include <algorithm>

int main() {
    int arr[5] = {5, 3, 1, 4, 2};
    std::sort(arr, arr + 5);
    for (int i = 0; i < 5; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    return 0;
}

使用std::sort函数对数组进行了升序排序,并将排序后的数组输出。

四、二维数组

4.1 二维数组的概念

二维数组可以看作是一个表格,由行和列组成。它可以用来表示矩阵、棋盘等数据结构。在 C++ 中,二维数组的声明和初始化方式与一维数组类似。

4.2 二维数组的声明和初始化

① 声明二维数组

cpp 复制代码
// 声明一个3行4列的整数二维数组
int arr[3][4];

声明了一个包含 3 行 4 列的整数二维数组。

②初始化二维数组

cpp 复制代码
// 初始化一个3行4列的整数二维数组
int arr[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

使用嵌套的大括号对二维数组进行了初始化。每一行的元素用一个小括号括起来,不同行之间用逗号分隔。

4.3 二维数组的遍历

二维数组的遍历需要使用嵌套循环。外层循环控制行,内层循环控制列。

cpp 复制代码
#include <iostream>

int main() {
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            std::cout << arr[i][j] << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}

4.4 二维数组的应用场景

二维数组在很多领域都有广泛的应用,例如图像处理、游戏开发等。在图像处理中,二维数组可以用来表示图像的像素矩阵;在游戏开发中,二维数组可以用来表示游戏地图。

五、多维数组

除了一维数组和二维数组,C++ 还支持多维数组。多维数组的概念和操作与二维数组类似,只是维度更多。例如,三维数组可以看作是由多个二维数组组成的。

5.1 三维数组的声明和初始化

cpp 复制代码
// 声明一个2个平面,每个平面3行4列的三维整数数组
int arr[2][3][4];

// 初始化三维数组
int arr[2][3][4] = {
    {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    },
    {
        {13, 14, 15, 16},
        {17, 18, 19, 20},
        {21, 22, 23, 24}
    }
};

5.2 多维数组的遍历

多维数组的遍历需要使用多层嵌套循环。例如,遍历三维数组需要使用三层嵌套循环。

cpp 复制代码
#include <iostream>

int main() {
    int arr[2][3][4] = {
        {
            {1, 2, 3, 4},
            {5, 6, 7, 8},
            {9, 10, 11, 12}
        },
        {
            {13, 14, 15, 16},
            {17, 18, 19, 20},
            {21, 22, 23, 24}
        }
    };
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 4; k++) {
                std::cout << arr[i][j][k] << " ";
            }
            std::cout << std::endl;
        }
        std::cout << std::endl;
    }
    return 0;
}

用三层嵌套的for循环遍历了三维数组的每个元素,并将其输出。

六、数组与函数

数组可以作为参数传递给函数,也可以作为函数的返回值。

6.1 数组作为函数参数

当数组作为函数参数时,实际上传递的是数组的首地址。以下是一个示例:

cpp 复制代码
#include <iostream>

void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    printArray(arr, 5);
    return 0;
}

定义了一个printArray函数,用于打印数组的元素。函数的第一个参数是一个整数数组,第二个参数是数组的大小。

6.2 函数返回数组

在 C++ 中,函数不能直接返回数组,但可以返回指向数组的指针。以下是一个示例:

cpp 复制代码
#include <iostream>

int* createArray() {
    int* arr = new int[5];
    for (int i = 0; i < 5; i++) {
        arr[i] = i + 1;
    }
    return arr;
}

int main() {
    int* arr = createArray();
    for (int i = 0; i < 5; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    delete[] arr; // 释放动态分配的内存
    return 0;
}

定义了一个createArray函数,用于创建一个包含 5 个整数的数组,并返回该数组的指针。在main函数中,调用createArray函数获取数组指针,并打印数组的元素。最后,使用delete[]释放了动态分配的内存,以避免内存泄漏。

七、数组的优缺点

7.1 优点

  • 高效的随机访问:数组元素在内存中是连续存储的,因此可以通过索引快速访问任意元素,时间复杂度为 O (1)。
  • 简单易用:数组的声明和操作都比较简单,容易理解和掌握。

7.2 缺点

  • 大小固定 :数组的大小在声明时就已经确定,不能在运行时动态调整。如果需要动态调整大小,可以使用std::vector
  • 插入和删除操作效率低:在数组中插入或删除元素需要移动大量的元素,时间复杂度为 O (n)。

八、内存机制深度剖析

①内存布局示例

cpp 复制代码
int arr[5] = {10,20,30,40,50};

内存结构:

cpp 复制代码
地址    | 值
0x1000 | 10  // arr[0]
0x1004 | 20  // arr[1]
0x1008 | 30  // arr[2]
0x100C | 40  // arr[3]
0x1010 | 50  // arr[4]

②指针与数组关系

cpp 复制代码
int* ptr = arr;        // 等价于 &arr[0]
ptr += 3;             // 指向arr[3]
*(ptr-1) = 100;       // 修改arr[2]

// 数组名不可修改
arr = ptr;           // 编译错误

③数组退化的陷阱

cpp 复制代码
void process(int arr[]) {  // 实际退化为int* 
    cout << sizeof(arr);  // 输出指针大小(如8字节)
}

int main() {
    int arr[5] = {0};
    cout << sizeof(arr);  // 输出20(5*4字节)
    process(arr);
}

九、高级操作技巧

①数组引用(C++特性)

cpp 复制代码
// 保持数组类型信息
template<size_t N>
void print_array(int (&arr)[N]) {  // 精确匹配数组大小
    for(int i=0; i<N; ++i)
        cout << arr[i] << " ";
}

int arr[] = {1,2,3};
print_array(arr);  // 正确传递数组引用

②动态内存管理

cpp 复制代码
// 创建动态数组
int* dynArr = new int[10]; 

// 初始化动态数组
int* dynArr2 = new int[5]{1,2,3,4,5};  // C++11

// 释放内存
delete[] dynArr;  // 必须配对使用

③数组与算法结合

cpp 复制代码
#include <algorithm>
#include <numeric>

int arr[] = {3,1,4,2,5};

// 排序
std::sort(std::begin(arr), std::end(arr));

// 累加求和
int sum = std::accumulate(arr, arr+5, 0);

// 查找元素
auto it = std::find(arr, arr+5, 4);
if(it != arr+5) cout << "Found at index: " << it - arr;

十、性能优化实战

①缓存友好访问模式

cpp 复制代码
// 优先行序访问(内存连续)
int matrix[1000][1000];
for(int i=0; i<1000; ++i)       // 外层行循环
    for(int j=0; j<1000; ++j)   // 内层列循环
        matrix[i][j] = i+j;

// 避免跳行访问
for(int j=0; j<1000; ++j)       // 错误示范
    for(int i=0; i<1000; ++i)
        matrix[i][j] = i+j;     // 缓存命中率低

②SIMD矢量化

cpp 复制代码
// 使用编译器指令优化
#pragma omp simd
for(int i=0; i<N; ++i) {
    a[i] = b[i] + c[i];
}

// 手动使用AVX指令
#include <immintrin.h>
void add_arrays(float* a, float* b, float* c, int n) {
    for(int i=0; i<n; i+=8) {
        __m256 va = _mm256_load_ps(&a[i]);
        __m256 vb = _mm256_load_ps(&b[i]);
        __m256 vc = _mm256_add_ps(va, vb);
        _mm256_store_ps(&c[i], vc);
    }
}

③循环展开优化

cpp 复制代码
// 手动展开循环
for(int i=0; i<1024; i+=4) {
    arr[i] = i;
    arr[i+1] = i+1;
    arr[i+2] = i+2;
    arr[i+3] = i+3;
}

十一、现代C++特性应用

①结构化绑定(C++17)

cpp 复制代码
std::array<int,3> get_point() { return {10,20,30}; }

auto [x,y,z] = get_point();  // 解包数组元素
cout << "X: " << x << ", Y: " << y;

②constexpr数组(C++11)

cpp 复制代码
constexpr int fib[] = {0,1,1,2,3,5,8,13};
static_assert(fib[7] == 13, "Fibonacci error");

③范围视图(C++20)

cpp 复制代码
#include <ranges>
int arr[] = {1,2,3,4,5,6};

// 过滤偶数并反转
for(int v : arr | views::filter([](int x){return x%2==0;})
               | views::reverse) 
{
    cout << v << " ";  // 输出6 4 2
}

十二、常见问题解决方案

① 数组越界预防

cpp 复制代码
template<typename T, size_t N>
class SafeArray {
    T data[N];
public:
    T& operator[](size_t index) {
        if(index >= N) throw std::out_of_range("Index overflow");
        return data[index];
    }
};

②动态数组内存管理

cpp 复制代码
// 使用智能指针(C++11)
auto dynArr = std::make_unique<int[]>(100);  // 自动释放内存
dynArr[50] = 123;

// 容器封装
std::vector<int> safeDynArr(100);  // 推荐替代方案

③深拷贝实现

cpp 复制代码
// 内置数组深拷贝
int src[5] = {1,2,3,4,5};
int dest[5];
std::copy(std::begin(src), std::end(src), dest);

// std::array自动深拷贝
std::array<int,5> a = {1,2,3,4,5};
auto b = a;  // 完全独立拷贝

十三、总结

数组是 C++ 编程中非常基础和重要的数据结构。数组具有高效的随机访问能力,但也存在大小固定和插入删除操作效率低的缺点。在实际编程中,需要根据具体的需求选择合适的数据结构。

希望本文能够帮助初学者全面掌握数组的使用,为进一步学习 C++ 编程打下坚实的基础。


十四、参考资料

  • **《C++ Primer(第 5 版)》**这本书是 C++ 领域的经典之作,对 C++ 的基础语法和高级特性都有深入讲解。
  • **《Effective C++(第 3 版)》**书中包含了很多 C++ 编程的实用建议和最佳实践。
  • 《C++ Templates: The Complete Guide(第 2 版)》 该书聚焦于 C++ 模板编程,而using声明在模板编程中有着重要应用,如定义模板类型别名等。
  • C++ 官方标准文档:C++ 标准文档是最权威的参考资料,可以查阅最新的 C++ 标准(如 C++11、C++14、C++17、C++20 等)文档。例如,ISO/IEC 14882:2020 是 C++20 标准的文档,可从相关渠道获取其详细内容。
  • cppreference.com:这是一个非常全面的 C++ 在线参考网站,提供了详细的 C++ 语言和标准库文档。
  • LearnCpp.com:该网站提供了系统的 C++ 教程,配有丰富的示例代码和清晰的解释,适合初学者学习和理解相关知识。

相关推荐
癞皮狗不赖皮1 小时前
WEB攻防- PHP反序列化&属性权限特征&原生类 TIPS&字符串逃逸&CVE 绕过漏洞
开发语言·php
UpUpUp……1 小时前
C++继承与组合完结
开发语言·c++·笔记
淘小欣1 小时前
10分钟打造专属AI助手:用ms-swift实现自我认知微调
开发语言·人工智能·ai·swift·模型微调
搬砖工程师Cola2 小时前
<C#> 详细介绍.net 三种依赖注入:AddTransient、AddScoped、AddSingleton 的区别
开发语言·c#·.net
C++ 老炮儿的技术栈4 小时前
设计模式,如单例模式、观察者模式在什么场景下使用
c++·笔记·学习·算法
画个逗号给明天"5 小时前
C#从入门到精通(1)
开发语言·c#
JavaPub-rodert6 小时前
golang 的 goroutine 和 channel
开发语言·后端·golang
lly2024066 小时前
Matplotlib 柱形图
开发语言
_Matthew7 小时前
JavaScript |(四)正则表达式 | 尚硅谷JavaScript基础&实战
开发语言·javascript·正则表达式
Vitalia8 小时前
⭐算法OJ⭐二叉树的后序遍历【树的遍历】(C++实现)Binary Tree Postorder Traversal
开发语言·c++·算法·二叉树