使用 forrange 循环打印 argv

Printing argv with forrange loop

int main(int argc, const char** argv) {

    std::cout << "Hello" << std::endl;

    char arr2d[][4] = {"ABC", "DEF"};

    for (char *i : arr2d)
    {
        std::cout << i << std::endl;
    }

在这里,我是这样评价forrange的工作的:"For each character array in arr2d, print it it to console"。这行得通,所以,至少我的理解应该是正确的。上面代码片段的输出是,

muyustan@mint:~/Desktop/C_Files/oop$ g++ main.cpp -o main && ./main
Hello
ABC
DEF

符合预期。

但是,如果我尝试这个,

int main(int argc, const char** argv) {

    std::cout << "Hello" << std::endl;

    char arr2d[][4] = {"ABC", "DEF"};

    for (const char *i : argv)
    {
        std::cout << i << std::endl;
    }

首先 IDE 警告我

this range-based 'for' statement requires a suitable "begin" function and none was found

如果我尝试编译,我得到:

muyustan@mint:~/Desktop/C_Files/oop$ g++ main.cpp -o main && ./main
main.cpp: In function ‘int main(int, const char**)’:
main.cpp:30:26: error: ‘begin’ was not declared in this scope
     for (const char *i : argv)
                          ^~~~
main.cpp:30:26: note: suggested alternative:
In file included from /usr/include/c++/7/string:51:0,
                 from /usr/include/c++/7/bits/locale_classes.h:40,
                 from /usr/include/c++/7/bits/ios_base.h:41,
                 from /usr/include/c++/7/ios:42,
                 from /usr/include/c++/7/ostream:38,
                 from /usr/include/c++/7/iostream:39,
                 from main.cpp:1:
/usr/include/c++/7/bits/range_access.h:105:37: note:   ‘std::begin’
   template<typename _Tp> const _Tp* begin(const valarray<_Tp>&);
                                     ^~~~~
main.cpp:30:26: error: ‘end’ was not declared in this scope
     for (const char *i : argv)
                          ^~~~
main.cpp:30:26: note: suggested alternative:
In file included from /usr/include/c++/7/string:51:0,
                 from /usr/include/c++/7/bits/locale_classes.h:40,
                 from /usr/include/c++/7/bits/ios_base.h:41,
                 from /usr/include/c++/7/ios:42,
                 from /usr/include/c++/7/ostream:38,
                 from /usr/include/c++/7/iostream:39,
                 from main.cpp:1:
/usr/include/c++/7/bits/range_access.h:107:37: note:   ‘std::end’
   template<typename _Tp> const _Tp* end(const valarray<_Tp>&);

那么,为什么 argv 的行为与我的 arr2d[][4] 不同?两者不都是char指针(char数组或字符串(?))的指针吗?

如果我的理解有问题,用 forrange 打印 argv 的成分的结构应该是什么?

如果要将基于范围的 for 应用到 argv,最简单的方法可能是从创建包含参数的向量开始:

#include <iostream>
#include <vector>

int main(int argc, char **argv){ 
    std::vector<std::string> args(argv, argv+argc);

    for (auto const &arg : args) {
        std::cout << arg << "\n"; // don't use `endl`.
    }
}

至于 argv 与二维数组相比,区别相当简单。当你有一个数组声明时,数组可以通过引用传递给一个模板,模板可以计算出它的大小:

template <class T, size_t N>
size_t array_size(T (&array)[N]) {
    return N;
}

int foo[2][3];
std::cout << array_size(foo) << "\n";

char bar[12][13][14];
std::cout << array_size(bar) << "\n";

...但是,argv 没有这样的静态可见定义,编译器可以从中推断出它的大小。在典型情况下,有一些代码在 main 之外运行,它检查命令行并动态分配它。

range-for 表达式与迭代器一起使用(指针是一种迭代器),它需要一个指向范围开头和结尾的迭代器。它通过将范围传递给 std::beginstd::end.

来获取这些值

arr2d的类型是char[2][4]。作为一个数组,它携带有关其大小的信息作为其类型的一部分。 std::beginstd::end 的模板重载分别接受对数组的引用和 return 指向其第一个和最后一个元素的指针。

argv的类型是char**。它只是一个指向 char 的指针。编译器不知道这些指针指向数组的第一个元素,并且这些指针不携带有关它们所指向的数组长度的信息。因此,没有接受指针的 std::beginstd::end 的重载,因为 std::end 无法确定数组的结尾与开头的位置单独的指针。

要在范围内使用指针,您必须提供有关数组长度的信息。在这种情况下,您可以构建一个简单的数组视图,因为您从 argc:

知道它的长度
template <typename T>
class PointerRange
{
private:
    T* ptr_;
    std::size_t length_;

public:
    PointerRange(T* ptr, std::size_t length)
        : ptr_{ptr},
          length_{length}
    {
    }

    T* begin() const { return ptr_; }
    T* end() const { return ptr_ + length_; }
};

int main(int argc, char** argv)
{
    for (char* arg : PointerRange(argv, argc)) {
        std::cout << arg << "\n";
    }
}

Live Demo

一旦 C++20 可用,std::span 可以取代上面定​​义的 PointerRange

int main(int argc, char** argv)
{
    for (std::string_view arg : std::span{argv, argc}) {
        std::cout << arg << "\n";
    }
}

它们的行为不同,因为它们的类型不同。这让初学者感到困惑,但是:

char **

是指向 char 的指针。事实上,在 argv 的情况下,它指向一个指针序列,每个指针指向一个以 nul 结尾的字符串(它们是字符序列)。

迭代这些的问题是这些序列的大小是未知的。编译器无法知道 argc 与上述第一个序列相关。

但是:

char arr2d[][4] = {"ABC", "DEF"};

解析为类型:

char [2][4]

这是char数组的数组。在这种情况下,大小是已知的 (2),因此您可以对其进行迭代。

最后,编译器抱怨 std::begin,因为基于范围的 for 循环被转换为不同的等效代码,使用 std::begin 等进行迭代。

this is something I have been told wrong then, when I was dealing with C back then, at somewhere I have read that "arrays are also pointers!".

关于该陈述,有几点您必须了解。

  1. 在大多数情况下,数组会衰减为指针,但数组仍然不同于指针。

    • 当用作sizeof的参数时,以下两个将导致不同的答案。

      char const* ptr = "Some text.";
      char array[] = "some text.";
      
      std::cout << sizeof(ptr) << std::endl;    // prints sizeof the pointer.
      std::cout << sizeof(array) << std::endl;  // prints sizeof the array.
      
    • 当用作 addressof 运算符的参数时。

      char const* ptr1 = "Some text.";
      char array[] = "some text.";
      
      char const** ptr2 = &ptr1;      // OK.
      char** ptr3 = &array;           // Error. Type mismatch.
      char (*ptr4}[11] = &array;      // OK.
      
  2. 二维数组可以衰减为指向一维数组的指针,但它们不会衰减为指向指针的指针。

     int array1[10];
     int* ptr1 = array1;          // OK. Array decays to a pointer
    
    
     int array2[10][20];
     int (*ptr2)[20] = array2;    // OK. 2D array decays to a pointer to 1D array.
     int**  ptr3 = array2;        // Error. 2D array does not decay to a pointer to a pointer.
    

如果你的编译器有跨度 header(现在没有很多人这样做)我想这会起作用。

// Example program
#include <iostream>
#include <string_view>
#include <span>

int main(int argc, char **argv)
{
  for (std::string_view s : std::span{argv, argc})
  {
      std::cout << s << std::endl;
  }
  return 0;
}

我的示例的唯一开销是 string_view 找到空终端。我试过了 goldbolt.org 但似乎 none 的编译器可以找到跨度 header。所以请轻看我的建议。