在 C++ 中循环迭代太多元素

While loop iterating on too many elements in C++

当我 运行 代码时,我将所有这些数字作为输出,这意味着我的 while 循环似乎遍历了它不应该遍历的元素。为什么会这样? 对于上下文,我目前正在学习 C++ 并浏览 C++,我对指针和引用非常困惑。

这是我获得代码的地方: Buggy code in "A Tour of C++" or non-compliant compiler?

int counter(int* arr,int c){
    int counter = 0;
    while(*arr){
    cout << *arr<<"\n";
    if(*arr == c){
        ++counter;
    }
    ++arr;
    }
    return counter;
}

int main()
{
    int arr[3] = {1,2,3};
    int count = counter(arr,1);
    cout<< count;
}

示例运行:

/Users/benediktschesch/CLionProjects/untitled/cmake-build-debug/untitled
1
2
3
-945684358
-1153026697
-280532248
32766
1839025881
32767
1839025881
32767
1
Process finished with exit code 0

这与不为将用作字符串的字符数组提供空终止符非常相似。

while(*arr) 

表示找到零就停止。

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

不提供零,因此您无法控制循环何时停止。

TL;DR 解决方案:

使用 Library container. std::vector or std::array would be a good fit here, as would std::count from the <algorithm> library and std::begin and std::end from the <iterator> library.

#include <iostream>
#include <iterator>
#include <algorithm>

int main()
{
    int arr[] = { 1, 2, 3 };
    int count = std::count(std::begin(arr), std::end(arr), 1);
    std:: cout << count;
}

解释:

你可以提供一个零

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

注意我删除了显式数组大小。不需要它,因为编译器从初始值设定项中的元素数量知道。

另请注意,这将在到达第一个零时停止,因此

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

只会发现一个 1。这使得零成为一个非常糟糕的值来终止整数列表,除非保证 0 不在输入中。

要扫描整个数组并且只扫描数组,需要提供数组的大小。这可以通过传入大小参数来完成

int counter(int* arr, size_t len, int c)
{
    int counter = 0;
    while (len--)
    {
        std::cout << *arr << "\n";
        if (*arr == c)
        {
            ++counter;
        }
        ++arr;
    }
    return counter;
}

int main()
{
    int arr[3] = { 1, 2, 3 };
    int count = counter(arr, std::size(arr), 1);
    std:: cout << count;
}

但现代 C++ 中的首选解决方案是使用容器代替数组。容器知道它们的大小并提供各种其他工具来使编写代码更容易和更少 error-prone.

#include <iostream>
#include <vector>

int counter(const std::vector<int> & arr, int c)
{
    int counter = 0;
    for (const auto & val: arr)
    {
        std::cout << val << "\n";
        if (val == c)
        {
            ++counter;
        }
    }
    return counter;
}

int main()
{
    std::vector<int> arr = { 1, 2, 3 };
    int count = counter(arr, 1);
    std:: cout << count;
}

注意使用 range-based for loop 来简化代码。 const auto & valarr 的内容和 auto 推导出 val 的类型。该值不会因循环而改变,因此我们将其声明为 const 以防止意外并将其作为引用,因为也许编译器可以执行一些额外的优化巫术。此外,如果容器或容器中的数据类型发生变化,您可以继续重用这个确切的语句而无需更改任何东西。这样可以防止以后维护代码时出错。

您也可以使用 std::array 并使 counter 成为检测 std::array 大小的模板函数,但此时有点过分了。

下一个演变利用了 <algorithm> 库。

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
    std::vector<int> arr = { 1, 2, 3 };
    int count = std::count(arr.begin(), arr.end(), 1);
    std:: cout << count;
}

在这种情况下,使用迭代器而不是指定长度。这使您可以轻松扫描容器的子集。

这允许我们通过利用 std::beginstd::end 将数组转换为一对迭代器来回到使用原始数组的方式:

#include <iostream>
#include <iterator>
#include <algorithm>

int main()
{
    int arr[] = { 1, 2, 3 };
    int count = std::count(std::begin(arr), std::end(arr), 1);
    std:: cout << count;
}

这让我们想到了 TL;DR 解决方案。

除包含字符串的字符数组外,所有其他数组(如果您不故意使用标记值)不包含表示数组结束的零元素。因此,如果数组不包含等于 0 的元素(如您的情况),您的函数可以调用未定义的行为。

所以通常你的函数应该有一个参数来指定数组中元素的数量并且应该看起来像

size_t counter( const int *arr, size_t n, int value )
{
    size_t count = 0;

    for ( const int *p = arr; p != arr + n; ++p )
    {
        if ( *p == value ) ++count;
    }

    return count;
}

并称赞

int main()
{
    int arr[] = { 1, 2, 3 };
    const size_t N = sizeof( arr ) / sizeof( *arr );

    size_t count = counter( arr, N, 1 );

    std::cout << count << '\n';
}

如果您的编译器支持 C++ 17,那么您可以包含在 header <iterator> 中声明的标准函数 std::size() 而不是表达式 sizeof( arr ) / sizeof( *arr ),例如 std::size( arr ) .

否则你可以使用表达式

代替表达式sizeof( arr ) / sizeof( *arr )
std::extent<decltype( arr )>::value

前提是包含 header <type_traits>

考虑到在 header <algorithm> 中声明了执行相同任务的标准算法 std::count。这是一个演示程序

#include <iostream>
#include <iterator>
#include <algorithm>

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

    auto count = std::count( std::begin( arr ), std::end( arr ), 1 );

    std::cout << count << '\n';
}

我推荐使用std::array and range based for loops

#include <array>
#include <iostream>

int counter(const std::array<int, 3> &arr, int c){
    int counter = 0;
    for (auto const a : arr) {
        std::cout << a << "\n";
        if(a == c){
            ++counter;
        }
    }
    return counter;
}

int main()
{
    std::array<int, 3> arr = {1,2,3};
    int count = counter(arr,1);
    std::cout << count;
}

你的问题的原因是在行

while(*arr){

声明

*arr

被评估为布尔值。 *arr == 0 为假,其他情况为真。在您的代码中,您需要获取数组的大小或最后一个值为 0 的元素。有不同的方法。您可以添加带有 0 的最后一个元素,也可以将大小传递给函数。但是 C++ 标准提供了 stl 容器,可以在没有开销的情况下解决您的问题。 std::array 就是这样一个包含数据和数组大小的容器。它是一个模板 class,因此大小不需要额外的数据。首先,您应该学习如何使用语言提供的工具,如 stl 容器和算法。稍后您可以学习如何使用低级函数和数据类型。