В чем разница между использованием перегрузки оператора для << и cout?

avatar
Khalid Owl Walid
9 августа 2021 в 05:58
191
2
-1

Я искренне не понимаю разницы. Вот несколько примеров:

Использование std::cout

#include <iostream>
#include <list>
using namespace std;

void print(std::list<std::string> const &list)
{
    for (auto const &i: list)
    {
        cout << i << endl;
    }
}

int main()
{
    std::list<std::string> list = { "blue", "red", "green" };

    print(list);

    return 0;
}

Следующий вывод:

blue
red
green

Для второго примера я также буду использовать std::cout:

#include <iostream>
#include <list>
using namespace std;

int main()
{
    std::list<std::string> list = { "blue", "red", "green" };

    cout << list << endl;

    return 0;
 }

Однако я получаю следующую ошибку:

error: no type named ‘type’ in ‘struct std::enable_if<false, std::basic_ostream<char>&>’

Итак, я использую перегрузку оператора следующим образом:

#include <iostream>
#include <list>
using namespace std;


std::ostream &operator<<(std::ostream &os, std::list<std::string> &list)
{
    os << list << endl;
    return os;
}

int main()
{
    std::list<std::string> list = { "blue", "red", "green" };

    cout << list << endl;

    return 0;
}

Сначала это компилируется нормально, но когда я его запускаю, я получаю ошибку времени выполнения:

Segmentation fault (core dumped)

Почему мы не можем просто cout все из списка, и почему мы должны использовать цикл, чтобы напечатать каждый элемент внутри списка?

Кроме того, как мне решить проблему второго примера? Я заметил, что когда я отлаживаю свой код, он застревает внутри перегрузки оператора, которая, как я полагаю, вызывает сбой.

Заранее спасибо!

Источник
churill
9 августа 2021 в 06:02
2

В первом примере у вас есть правильный код для печати каждого элемента списка. Во втором фрагменте кода вы ожидаете, что os << list << endl; волшебным образом сделает это. Но на самом деле это вызывает перегруженный operator<<, который снова вызывает сам себя, который снова вызывает себя, который снова вызывает себя ... до тех пор, пока ваш стек не переполнится.

Khalid Owl Walid
9 августа 2021 в 06:05
0

Я только что добавил новый пример между std::cout и перегрузкой оператора, если кого-то смущает, почему он упомянул о втором фрагменте кода, являющемся перегрузкой оператора.

Peter
9 августа 2021 в 06:26
1

По умолчанию стандартные контейнеры (например, std::list) не имеют операторов потоковой передачи, поэтому, если они вам нужны, вам необходимо их предоставить — отсюда и ошибка компиляции во втором случае. Это ограничение означает, что контейнер можно использовать для хранения набора элементов, у которых нет собственных операторов потоковой передачи. Ваш третий пример бесконечно рекурсивен (ваш operator<<() безоговорочно вызывает себя в операторе os << list << endl, следовательно (практически) либо ошибка времени выполнения (из-за исчерпания стека), либо бесконечный цикл (если компилятор переводит рекурсивные вызовы в итерацию).

Ответы (2)

avatar
eerorika
9 августа 2021 в 06:04
2

Вы вставляете список в поток символов следующим образом: cout << list << endl;. Это вызывает вашу перегрузку, которая вставляет список в поток символов следующим образом: os << list << endl;. Это вызывает вашу перегрузку, которая вставляет список в поток символов, которая вызывает вашу перегрузку, которая вставляет список в поток символов, которая вызывает вашу перегрузку и так далее...

Можете определить проблему? Проблема в том, что ваша функция (перегрузка оператора) вызывает себя рекурсивно, безоговорочно. Эта рекурсия никогда не заканчивается или никогда не закончилась бы, если бы не исчерпание ограниченного стека вызовов, что приводит к ошибке переполнения стека.

почему мы должны использовать цикл

Мы используем цикл, чтобы повторить операцию для каждого элемента. Циклы нужны для повторения.

Тем не менее, мы не "должны" сами писать цикл. В качестве альтернативы мы можем вызвать один из стандартных алгоритмов, которые написали для нас цикл, например, std::for_each или std::copy в сочетании с std::ostream_iterator (или их std::ranges эквиваленты).

Кроме того, как мне решить проблему второго примера?

Реализуйте перегрузку оператора аналогично реализации функции print, используя цикл.

std::ostream& operator<<(std::ostream &os, std::list<std::string> const &list)
{
    for (auto const &i: list)
    {
        os << i << endl;
    }
    return os;
}
Khalid Owl Walid
9 августа 2021 в 06:13
0

Я вроде понимаю, но не совсем понимаю про зацикливание :')

Khalid Owl Walid
9 августа 2021 в 06:28
0

Спасибо за ответ, но что касается вашего последнего решения, разве оно не распечатает каждый элемент, который есть в моем списке? Что, если я просто хочу напечатать один список, например, вывод будет примерно таким: { "blue", "red", "green" }

eerorika
9 августа 2021 в 06:33
0

@KhalidOwlWalid В чем разница между печатью каждого элемента одного списка и печатью одного списка? Разве вы не хотите, чтобы каждый элемент печатался при печати списка?

Khalid Owl Walid
9 августа 2021 в 07:36
0

Idk, я все еще новичок в C ++, и раньше я использовал python, и я использовал печать для проверки переменных, и я подумал, что могу просто напечатать это так. Надеюсь, это имеет смысл, но справедливое замечание @eeroika

user253751
9 августа 2021 в 09:47
0

@KhalidOwlWalid Компьютер не знает, что вы от него хотите. Вы должны сказать это. Если вы хотите, чтобы он печатал {, вы пишете, например, os << "{";.

avatar
avonbied
9 августа 2021 в 06:24
0

Как упомянул @eeronika, существует больше способов, чем просто перебрать std::list для вывода его содержимого.

Почему мы не можем просто выделить все из списка

Причина этого в том, что std::list не перегружает << для извлечения потока (https://en.cppreference.com/w/cpp/language/operators ).

В подобных случаях вы должны перегрузить operator<< и использовать методы структуры данных для создания желаемого вывода.

Спецификация

std::list: https://en.cppreference.com/w/cpp/container/list