总结 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 位置传入指针,违反底层的实现,会导致编译错误。