使用 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::begin
和 std::end
.
来获取这些值
arr2d
的类型是char[2][4]
。作为一个数组,它携带有关其大小的信息作为其类型的一部分。 std::begin
和 std::end
的模板重载分别接受对数组的引用和 return 指向其第一个和最后一个元素的指针。
argv
的类型是char**
。它只是一个指向 char
的指针。编译器不知道这些指针指向数组的第一个元素,并且这些指针不携带有关它们所指向的数组长度的信息。因此,没有接受指针的 std::begin
和 std::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";
}
}
一旦 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!".
关于该陈述,有几点您必须了解。
在大多数情况下,数组会衰减为指针,但数组仍然不同于指针。
当用作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.
二维数组可以衰减为指向一维数组的指针,但它们不会衰减为指向指针的指针。
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。所以请轻看我的建议。
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::begin
和 std::end
.
arr2d
的类型是char[2][4]
。作为一个数组,它携带有关其大小的信息作为其类型的一部分。 std::begin
和 std::end
的模板重载分别接受对数组的引用和 return 指向其第一个和最后一个元素的指针。
argv
的类型是char**
。它只是一个指向 char
的指针。编译器不知道这些指针指向数组的第一个元素,并且这些指针不携带有关它们所指向的数组长度的信息。因此,没有接受指针的 std::begin
和 std::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";
}
}
一旦 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!".
关于该陈述,有几点您必须了解。
在大多数情况下,数组会衰减为指针,但数组仍然不同于指针。
当用作
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.
二维数组可以衰减为指向一维数组的指针,但它们不会衰减为指向指针的指针。
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。所以请轻看我的建议。