总结 C++ 数组遍历

总结 C++ 数组遍历

C++ 中求数组元素个数的方式

1. 使用 sizeof 运算符

sizeof(array) / sizeof(array[0])
  • sizeof(array) 返回数组总的字节数。
  • sizeof(array[0]) 返回数组单个元素的字节数。
  • 将总字节数除以单个元素的字节数,即可得到数组的元素个数。

int arr[5];
std::cout << sizeof(arr) / sizeof(arr[0]);  // 输出 5

  • 优点:简单、直接,适用于固定大小的数组。
  • 缺点
    • 只适用于静态数组(即数组的大小在编译时已知)。
    • 对于指向数组的指针(如动态分配的数组或函数参数中的数组),无法使用 sizeof 计算元素个数。

2. 使用 std::size (C++17及以后)

std::size(array)
  • C++17 引入了 std::size 函数,它返回数组元素的个数。
  • 它直接适用于静态数组,且语法简洁。

#include <iostream>
#include <iterator>  // 包含 std::size
int arr[5];
std::cout << std::size(arr);  // 输出 5

  • 优点:语法简洁、清晰,直接返回元素个数,适用于静态数组。
  • 缺点
    • 仅适用于静态数组,不能用于指向数组的指针。

3. 使用 std::vector::size()(动态数组)

std::vector<T> vec;
vec.size();
  • 对于动态分配的数组(如 std::vector),可以直接使用 std::vector::size() 来获取元素个数。
  • std::vector 是 C++ 标准库中提供的动态数组容器,它在运行时自动管理内存,并提供了 size() 成员函数。

#include <vector>
std::vector<int> vec = {1, 2, 3, 4, 5};
std::cout << vec.size();  // 输出 5

  • 优点:适用于动态数组,可以动态增加或减少元素,并直接获取元素个数。
  • 缺点:相较于原生数组,std::vector 需要额外的内存开销。

4. std::array(C++11及以后)

std::array<T, N> arr;
arr.size();
  • std::array 是 C++11 引入的一个容器类模板,用于处理固定大小的数组。通过 .size() 可以获取元素个数。

#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << arr.size();  // 输出 5

  • 优点:适用于固定大小的数组,且比普通数组更具现代 C++ 风格,支持更多的成员函数。
  • 缺点
    • 仅适用于固定大小的数组,不能处理动态数组。

对于静态数组 优先使用 std::size(如果支持 C++17)或 sizeof 方式。

对于动态数组:使用 std::vector,并直接调用 .size() 获取元素个数。


使用 begin 和 end 函数,对于静态数组或容器,end - begin 计算的是元素的个数。

/**
   *  @brief  Return an iterator pointing to the first element of the array.
   *  @param  __arr  Array.
   */
  template<typename _Tp, size_t _Nm>
    [[__nodiscard__]]
    inline _GLIBCXX14_CONSTEXPR _Tp*
    begin(_Tp (&__arr)[_Nm]) noexcept
    { return __arr; }

  /**
   *  @brief  Return an iterator pointing to one past the last element
   *          of the array.
   *  @param  __arr  Array.
   */
  template<typename _Tp, size_t _Nm>
    [[__nodiscard__]]
    inline _GLIBCXX14_CONSTEXPR _Tp*
    end(_Tp (&__arr)[_Nm]) noexcept
    { return __arr + _Nm; }

可以看到 begin 返回数组的第一个元素的指针,end 返回数组最后一个元素的下一个位置的指针

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

    std::cout << std::end(arr) - std::begin(arr) << std::endl;

    std::cout << std::is_same_v<decltype(std::end(arr)), int*>;

    return 0;
}

上面的例子结果是

5
1

C++ 中遍历数组元素的方式

C++ 中遍历数组的方法有多种,主要可以分为以下几类:

1. 使用普通 for 循环(索引访问)

这是最基本的遍历方式,直接使用 for 循环按索引访问数组元素:

#include <iostream>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);  // 获取数组长度
    
    for (int i = 0; i < size; i++) {
        std::cout << arr[i] << " ";
    }
    return 0;
}

2. 使用 while 循环(索引访问)

while 循环的写法与 for 循环类似,只是控制条件写法不同:

#include <iostream>

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

3. 使用 range-based for loop(C++11 引入)

C++11 引入的 range-based for loop(范围 for 循环)可以更方便地遍历数组:

#include <iostream>

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

    for (int elem /* auto &elem */ 
            : arr /* container */ ) {   /* 拷贝机制 */
        std::cout << elem << " ";
    }
    return 0;
}

修改数组元素

如果需要修改数组元素,可以使用引用:

for (auto &elem : arr) {    /* 引用机制,等价于地址解引用 */
    elem *= 2;
}

避免修改数组元素

如果避免修改数组元素,可以使用引用:

for (const auto &elem : arr) {
    elem *= 2;          /* 编译报错,表达式必须是可修改的左值,但是这不表示 container 中的元素不可修改 */
}

4. 使用指针遍历

(1) 直接使用指针

#include <iostream>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *ptr = arr;  // 指针指向数组首元素
    
    for (int i = 0; i < 5; i++) {
        std::cout << *(ptr + i) << " ";
    }
    return 0;
}

(2) 通过指针自增

#include <iostream>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *ptr = arr;
    
    while (ptr < arr + 5) {  // 终止条件:指针到达数组末尾
        std::cout << *ptr << " ";
        ptr++;      // 指针自增,增加的是指针的基类型字节数
    }
    return 0;
}

以上两种方式是等价的,只是写法不同。


5. 使用 std::begin()std::end()(C++11 引入)

C++11 引入了 std::begin()std::end(),可以避免手动计算数组大小:

#include <iostream>
#include <iterator>

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

    for (auto it = std::begin(arr); it != std::end(arr); ++it) {
        std::cout << *it << " ";
    }
    return 0;
}

6. 使用 std::for_each()(C++11 引入)

std::for_each() 是标准库提供的遍历方式:

#include <iostream>
#include <algorithm>

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

    std::for_each(std::begin(arr), std::end(arr), [](int elem) {
        std::cout << elem << " ";
    });

    return 0;
}

7. 遍历 std::array(C++11 引入)

C++11 引入 std::array,可以用 range-based for迭代器 遍历:

#include <iostream>
#include <array>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};

    for (int elem : arr) {
        std::cout << elem << " ";
    }
    return 0;
}

或者使用迭代器:

for (auto it = arr.begin(); it != arr.end(); ++it) {
    std::cout << *it << " ";    // 前面说过 begin 和 end 返回数组元素的指针,这里我们要解引用 
}

8. 遍历 std::vector(动态数组)

使用 std::vector 时,可以使用:

  • 普通 for 循环
  • 范围 for
  • 迭代器
  • std::for_each()
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    for (const auto &elem : vec) {  // 范围 for
        std::cout << elem << " ";
    }

    return 0;
}

使用指针的方式遍历二维数组

#include <iostream>

int main()
{
    int arr[3][4] = {};

    auto ptr = std::begin(arr);         /* ptr 是 int (*)[4] 的数组指针 */

    while (ptr != std::end(arr))
    {
        auto ptr2 = std::begin(*ptr);   /* 前面说过 ptr 是指针,在传入函数中需要解引用 */
        /* ptr2 指向一维数组的地址 */
        while (ptr2 != std::end(*ptr))  /* ptr2 直到指向最后一个一维数组 */
        {
            std::cout << *ptr2 << std::endl;
            ++ptr2;                     /* ptr2 指向一维数组中各个元素 */ 
        }
        ptr++;                          /* ptr 指向下一个一维数组 */

    }
    
    return 0;
}

为什么在 range-based for loop 中没法使用指针完成数组的遍历

在 C++ 中,range-based for loop 适用于容器或数组,但它的机制决定了无法直接用于指针表示的二维数组的遍历。

for (auto &elem /* auto elem 可能造成类型退化 或者使用 decltype(auto) elem 避免类型退化 */ 
            : container) {  /* elem 作为 container 中的元素,如果避免误操作 container 中的元素可以使用 const auto &elem */
    // 处理 elem
}

如果使用指针(例如 int *ptr = arr),无法做到下面的操作

int arr = {1, 2, 3, 4, 5}; 
int *ptr = arr;
for (auto &elem : ptr /* this arg must be a container */ ) {  // 编译错误,不符合语法规范
    std::cout << elem << " ";
}

也就是说,使用 range-based for loop 无法使用指针实现一维数组的遍历。

我们使用 cpp insights 看一下 range-based for loop 的语法实现,例如下面这段代码:

#include <iostream>

int main()
{

    int arr[] = {1, 2, 3, 4, 5, 6};
  
    for(auto &i : arr)
    {
      
    }

    return 0;
}

cpp insights 给出的实现如下:

#include <iostream>

int main()
{
  int arr[6] = {1, 2, 3, 4, 5, 6};
  {
    int (&__range1)[6] = arr;
    int * __begin1 = __range1;
    int * __end1 = __range1 + 6L;
    for(; __begin1 != __end1; ++__begin1) {
      int & i = *__begin1;
    }
    
  }
  return 0;
}

可以看到 range-based for 还是靠 begin 和 end 函数在内建 for
循环中实现的

可以看到 __range1 只是 arr 的引用而已,__begin1 是 int 类型的指针指向 __range1 的第一个元素,
__end1 是 int 类型的指针指向 __range1 的最后一个元素的下一个位置,然后执行 for 循环。

如果我们在 range-based for 的 container 位置传入指针,违反底层的实现,会导致编译错误。