Печать argv с помощью цикла forrange - PullRequest
2 голосов
/ 06 апреля 2020
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 следующим образом: "Для каждого массива символов в arr2d выведите его на консоль" . И это работает, поэтому мое понимание, по крайней мере, должно быть правильным. Вывод приведенного выше фрагмента кода:

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 предупреждает меня с помощью ,

для этого оператора for, основанного на диапазоне, требуется подходящая функция begin, и ничего не найдено

И если я попытаюсь скомпилировать, я получу:

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]? Разве оба они не являются указателями на символьные указатели (массивы или строки (?))?

И если что-то не так с моим пониманием, какой должна быть структура печати ingreditens argv с помощью forrange?

Ответы [ 5 ]

3 голосов
/ 06 апреля 2020

это то, что мне сказали неправильно тогда, когда я имел дело с C тогда, где-то я читал, что "массивы также являются указателями!".

Есть несколько более тонких моментов, которые нужно понять относительно этого утверждения.

  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. 2D-массивы могут распадаться на указатели на одномерные массивы, но они не распадаются на указатели на указатели.

     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.
    
2 голосов
/ 06 апреля 2020

Выражение range-for работает с итераторами (указатели являются типом итератора) и требует итератор для начала и конца диапазона. Он получает их, передавая диапазон std::begin и std::end.

. Тип arr2d равен char[2][4]. Как массив, он несет информацию о своем размере как часть своего типа. Существуют перегрузки шаблона для std::begin и std::end, которые принимают ссылку на массив и возвращают указатель на его первый и один элемент после последнего, соответственно.

Тип 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";
    }
}

Live Demo

Once C + +20 становится доступным, std::span может занять место PointerRange, определенного выше:

int main(int argc, char** argv)
{
    for (std::string_view arg : std::span{argv, argc}) {
        std::cout << arg << "\n";
    }
}
2 голосов
/ 06 апреля 2020

Они ведут себя по-разному, потому что у них разные типы. Это сбивает с толку новичков, но:

char **

- указатель на указатель на char. Фактически, в случае argv, он указывает на последовательность указателей, каждый из которых указывает на строку с нулевым символом в конце (которая является последовательностью символов).

Проблема состоит в том, что размер из этих последовательностей не известно. Компиляторы не могут знать, argc относится к первой последовательности, упомянутой выше.

Однако:

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

разрешается в типе:

char [2][4]

Что является массив массивов char. В этом случае размер известен (2), поэтому вы можете перебирать его.

Наконец, компилятор жалуется на std::begin, поскольку основанный на диапазоне для l oop преобразуется в другой эквивалентный код, который использует std::begin et c. сделать итерацию.

2 голосов
/ 06 апреля 2020

Если вы хотите применить диапазон 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, который проверяет командную строку и выделяет ее динамически.

1 голос
/ 06 апреля 2020

Если у вашего компилятора есть заголовок span (не так много у них сейчас), я думаю, что это сработает.

// 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, но, похоже, ни один из компиляторов не может найти заголовок span. Так что примите мой совет слегка.

...