Чому побітові зсуви (<< і >>) використовуються для cout і cin?


78

Питання справді в назві; Я впевнений, що є щось логічне, але поки я тупий!


11
Думаю, це тому, що вони нагадують стрілки, що вказують на потік якоїсь речовини.
Пойнті

1
Тільки здогадуюсь, але, я думаю, це тому, що ви "переносите" дані з файлу або з нього.
Марк Ренсом

9
Для повноти: у цьому контексті їх називають операторами вставки: cplusplus.com/reference/iostream/ostream/operator%3C%3C
ChristopheD

6
@Pointy: як щодо функцій типу read()і write()? Я думаю, що визначені користувачем оператори повинні мати подібну семантику, як вбудовані оператори, наприклад, +можна використовувати для додавання складних чисел або геометричних векторів. Але ostream::operator<<нічого не пов’язано зі зміною бітів. Деякі ранні дизайнерські рішення на C ++ зараз вважаються проблематичними, наприклад, автоматична генерація конструкторів копій, якщо присутній деструктор, тому не обов’язково має бути щось логічне щодо вибору operator<<.
Філіпп

1
@Crowstar: чи можу я змінити питання? Чому оператори вставки та вилучення використовуються для побітного зсуву? Особисто я використовую потоки частіше, ніж побітові маніпуляції;)
Matthieu M.

Відповіді:


67

Відповідно до п. 8.3.1 Проектування та еволюції С ++ :

Ідею забезпечення оператора виводу, а не іменованої функції виводу, запропонував Даг Макілрой за аналогією з операторами перенаправлення вводу-виводу в оболонці UNIX (>, >>, | та ін.)

[...]

Для операцій введення та виведення було розглянуто кілька операторів: оператор присвоєння був кандидатом як на введення, так і на вихід, але він пов’язує неправильний шлях. Це cout=a=bбуло б витлумачено як cout=(a=b), і, здавалося б , більшість людей вважали за краще, щоб оператор введення відрізнявся від оператора виводу. Операторів <і >судили, але значення "менше" і "більше ніж" були настільки міцно прищеплені у свідомості людей, що нові заяви вводу-виводу були для всіх практичних цілей нечитабельними (це, схоже, не стосується <<і >>) . Окрім цього, на більшості клавіатур «<» трохи вище «,», і люди писали такі вирази:

cout < x , y, z;

Дати хороші повідомлення про помилки для цього непросто.


18

Може тому, що це схоже на операцію додавання Unix, оскільки ви по суті додаєте до потоку вводу / виводу?

Напр

Вихідні дані

echo "foo" >> bar

Вхідні дані

sendmail -f test@domain.com << myemail.txt

(Приклад вкраденого введення від Zac Howland)


1
@Federico: насправді, це так. Ви можете використовувати <<в командному рядку UNIX робити вставки: sendmail -f test@domain.com << myemail.txt.
Zac Howland

@Zac дякую за приклад; Я додав його до відповіді, щоб зробити його більш повним
Abe Voelker

1
Це моє найкраще пояснення. Мені просто неприємно, як C ++ виступає за зміну значення операторів. Я намагаюся триматися подалі від БУДЬ-ЯКОЇ бібліотеки чи коду, що змінює це значення. IE MyWebRequest = " google.com "; фактично завантажуючи веб-сторінку через завдання. І хоча я розумію необхідність цього, я думаю, що перевантаження оператора надто зловживають. Я б набагато віддав перевагу, щоб вони використовували щось на зразок <- або ->, щоб не змінювати значення АБО не змінювати оператор зсуву бітів на щось інше.
Rahly

@rahly: Але маркер ->вже також має своє значення, як і пара лексем<-
Бен Войгт

Ви маєте рацію ... але я мав на меті не змінити значення існуючих операторів .... можливо:> та <: .... auto tmp = myvar << 12; не має реального значення
Рахлі

11

З "Мова програмування на C ++". Слова Строструпа (автори мови):

Перевантаження оператора, <<щоб означати `` поставити '', дає кращі позначення і дозволяє програмісту вивести послідовність об'єктів в одному операторі.

Але чому <<? Неможливо винайти нову лексичну лексему. Оператор присвоєння був кандидатом як на введення, так і на вихід, але, мабуть, більшість людей воліли використовувати різні оператори для введення та виведення. Крім того, = пов'язує неправильний шлях; тобто cout = a = b означає cout = (a = b), а не (cout = a) = b. Я пробував операторів <і >, але середні значення '' менше '' і '' більше, ніж '' були настільки міцно вжиті у свідомість людей, що нові заяви вводу-виводу були для всіх практичних цілей нечитабельними.


7

Отже, ви пам’ятаєте, що якщо ви думаєте cinяк клавіатура та coutяк монітор, те, що ви вводите, переходить у змінну

cin>>var;

Або вміст вашої змінної спрямовується на екран

cout<<var;

7

>>і <<є просто операторами, і ви можете реалізувати власні >>і <<для своїх класів.

Я припускаю, що їх "хтось" вибрав, оскільки: а) вони схожі на операції з файлами оболонки та б) для повторного використання існуючих операторів, оскільки немає необхідності створювати нові


6

Тому що вони мали більш-менш розумний перевагу і добре виглядали. У C ++ ви не можете створювати нові оператори або змінювати їх пріоритети або правила групування, ви можете лише перевантажувати існуючі та змінювати те, що вони насправді роблять.

Вибір <<і >>має якийсь прикрий побічний ефект, оскільки це якось штовхає думку про те, що вихід буде зроблений з дотриманням порядку. Незважаючи на те, що це вірно для фактичного виходу завдяки розумному фокусному трюку, однак воно є хибним для обчислень, і це дуже часто дивує.

Якщо бути більш конкретним у написанні

std::cout << foo() << bar() << std::eol;

НЕ означає, що fooбуде викликано раніше bar.

РЕДАГУВАТИ

З C ++ 17 проблема з послідовністю була "виправлена". Тепер порядок оцінки визначено бути зліва направо для <<і >>операторів. У C ++ все ще є місця, де порядок оцінки не визначений (або навіть неіснуючий зміст того, що оцінка може бути перемежована), але кілька поширених випадків зараз поводяться передбачувано та портативно. Подивіться на цю відповідь .


Але кого це цікавить? Якщо ви використовуєте функції з побічними ефектами в операторі виводу, ви все одно створюєте нечитабельний та неможливий код. (Інакше, звичайно, аргумент справедливий і для багатьох інших випадків. І є вагомий аргумент для наведення порядку - зробити помилки відтворюваними, коли ви помиляєтесь і отримуєте побічний ефект.)
Джеймс Канце

1
@JamesKanze: Я просто виявив, що багато програмістів на C ++ дійсно думають, що в прикладі код foo()гарантовано викликається раніше bar()... і вони пишуть код, як s << header() << body() << footer();де body()обчислює деякі підсумки, що використовуються в footer(). Помилка такого типу рідше зустрічається для параметрів функції.
6502

Я б не найняв програміста, який писав подібний код, навіть якщо замовлення було гарантоване. Такі приховані залежності - справжній кошмар для обслуговування.
Джеймс Канце,

3
@JamesKanze: Цей коментар звучить комічно (будь ласка, пам’ятайте, що ви говорите про бібліотеку з такими жахами, як setwі setfillв ній).
6502,

4

Вони не є побітовими операторами, їх у цьому контексті називають операторами вставки та вилучення.

http://www.cplusplus.com/doc/tutorial/basic_io/

Вони використовуються лише для візуальної інтерпретації. Якщо ви вивчаєте розробку власного потоку та перевантаження оператора, то ви можете побачити, що ви навіть можете використовувати + для введення та - для виводу :)


4

Переважно через їхню асоціативність. Оператори вставки та вилучення асоціюються зліва направо, отже

std::cout << "Hello" << ' ' << 4 << 2;

оцінює як слід: спочатку за "Hello", потім за ' 'і нарешті за допомогою 4і 2. Звичайно, оператор додавання operator+також співпрацює зліва направо. Але цей оператор та інші з асоціативністю зліва направо вже мають інше значення.


3

Ця відповідь є незадовільною, але правильною: вони не є побітовими операторами.

Значення оператора визначається типом даних, який відображається зліва. У випадку з cin і cout (та іншими типами потоків) << та >> оператори переміщують значення до і з потоків. У випадку, якщо лівий операнд є цілим числом, операція - це побітова операція, яку ви вже знаєте з C.

Значення оператора не є фіксованим, хоча його пріоритет є.


1

Бьярн вибрав їх для практичного переваги, асоціативності та мнемонічної цінності.

Пріоритет не ідеальний, наприклад, логічний та бітовий оператори викликають проблеми.

Але це цілком нормально.


1

Оператор вставки >>і <<використовуються відповідно з вхідним потоком та вихідним потоком, оскільки вхідний потік означає потік даних у вашу програму, а вихідний потік означає потік даних із вашої програми. Оскільки ці оператори вставки виглядають як оператор направлення (Показує напрямок потоку даних), так >>обирається для вхідного потоку та <<вихідного потоку.

Погляньте на частину коду ...

int Num1;
cin >> Num1;

тут, якщо ви уважно спостерігаєте >>, показує потік даних до змінної (заявленої в програмі), що означає потік даних до програми, що є роботою вхідного потоку (тут cin).

так само йде з cout,

int Num2 = 5;
cout << Num2;

Тут <<показано потік даних із програми (як Num2частина програми), що є роботою вихідного потоку.

Сподіваюся, все це має для вас сенс.


1
Привіт! Ласкаво просимо до StackOverflow. Я щойно надіслав редакцію з розміткою вашого коду (він переглядається). Ви можете зробити відступ за допомогою {}кнопки або відступом через чотири пробіли. Ви також можете розмітити вбудований код зворотними позначками (`).
rrauenza

0
cout << "Output sentence"; // prints Output sentence on screen
cout << 120;               // prints number 120 on screen
cout << x;                 // prints the content of x on screen 

Оператор << вставляє дані, що слідують за ним, у потік, що передує йому. У наведених вище прикладах він вставляє константний рядок Вихідне речення, числову константу 120 та змінну x у стандартний вихідний потік cout.

Стандартним пристроєм введення, як правило, є клавіатура. Обробка стандартного вводу в C ++ здійснюється шляхом застосування перевантаженого оператора вилучення (>>) до потоку cin. За оператором повинна слідувати змінна, яка буде зберігати дані, які збираються витягти з потоку. Наприклад:

int age;
cin >> age;

0

Я припускаю, що ви знаєте, що C ++ дозволяє перевантаження оператора. Як правило, ви перевантажуєте оператори, лише якщо семантика повністю передається (наприклад, перевантажуючи додавання для векторного класу, щоб додати два вектори разом). Думаю, ваше запитання стосується того, чому можна використовувати оператори біт-зсуву, перевантажувати їх для iostream і надавати їм зовсім іншого значення, ніж їхнє початкове призначення. Причиною цього може бути те, що операції бітового зсуву настільки віддалені від того, що роблять iostream, так що ніхто не може заплутатися в думці, що << або >> робить бітсдвиг на iostream. І тому, чому їх зручно використовувати, також полягає в тому, що їх упорядкування полягає в тому, щоб спочатку оцінити операнд ліворуч, потім той праворуч і виконати операцію.

Але, до вихідного питання, чому? Я насправді не знаю, мені просто здається, що << і >> досить легко зрозуміти як отримання інформації від однієї сутності та розміщення її в іншій. Чому причина повинна бути більш складною, ніж ця? Виглядає розумно використовувати їх, оскільки їх значення очевидне .. що краще попросити у оператора?


4
-1: Порядок оцінки лівої та правої сторони <<та >>не гарантується. Це справді іноді є джерелом помилок, коли люди пишуть подібні речі s << foo() << bar()та очікують, що fooїх покличуть раніше bar. Гарантоване те, що результат fooбуде надіслано в потік до результату, barале порядок обчислення НЕ гарантується. Тільки ,, ||і &&двійкові оператори дають таку гарантію ... і ця гарантія в будь-якому випадку є лише в тому випадку, якщо ви не перевантажуєте їх.
6502
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.