[0]zero-index clytie0x/zero-index

15.2 cin และ cout

C++ รับข้อมูลด้วย cin และแสดงผลด้วย cout ข้อดีที่สุดคือไม่ต้องจำตัวกำหนดรูปแบบอีกแล้ว ไม่มี %d %lld %lf ไม่ต้องใส่ & หน้าตัวแปร ชนิดของตัวแปรเป็นตัวบอกเองว่าจะอ่านหรือพิมพ์แบบไหน ความผิดพลาดแบบลืมเปลี่ยน %d เป็น %lld ในบทที่ 1 จึงหายไปทั้งหมด หน้านี้สอนวิธีใช้ ส่วนเรื่องความเร็วอยู่ในหน้าถัดไป

cout พิมพ์ทุกชนิดด้วย <<

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int score = 85;
    long long population = 3000000000;
    double height = 1.75;
    char grade = 'A';
    cout << "score = " << score << '\n';
    cout << "population = " << population << '\n';
    cout << "height = " << height << '\n';
    cout << "grade = " << grade << '\n';
    return 0;
}
score = 85
population = 3000000000
height = 1.75
grade = A

<< ส่งค่าทางขวาไปที่ cout ทีละชิ้น เรียงจากซ้ายไปขวา หนึ่งบรรทัดจึงส่งได้หลายชิ้นต่อกัน เรียกว่าการต่อเป็นสาย (chain) แต่ละชิ้นจะเป็นข้อความ ตัวแปร หรือผลคำนวณก็ได้ เทียบกับ printf ที่เรียนมา

ชนิดprintfcout
intprintf("%d\n", score);cout << score << '\n';
long longprintf("%lld\n", population);cout << population << '\n';
charprintf("%c\n", grade);cout << grade << '\n';
หลายค่าprintf("%d %d\n", a, b);cout << a << ' ' << b << '\n';

แถวสุดท้ายสำคัญ cout ไม่เติมช่องว่างระหว่างชิ้นให้ ถ้าอยากได้ช่องว่างต้องส่ง ' ' เองทุกครั้ง ลืมแล้วตัวเลขจะติดกัน ดูได้ในส่วนลองทำให้พัง ส่วน double ไม่อยู่ในตาราง เพราะมีเรื่องต้องรู้มากกว่านั้น อยู่ในหัวข้อจัดทศนิยมด้วย fixed และ setprecision

cin รับข้อมูลด้วย >>

กลับไปที่ที่ดินกว้าง 50000 เมตร ยาว 50000 เมตรในหน้า 1.3 คราวนี้รับด้วย cin

#include <bits/stdc++.h>
using namespace std;

int main()
{
    long long width, length;
    cin >> width >> length;
    cout << "area = " << width * length << '\n';
    return 0;
}
50000 50000
area = 2500000000
  • ไม่มี & และไม่มี %lld แค่ประกาศเป็น long long แล้ว cin ก็อ่านเป็น long long ให้เอง
  • cin >> width >> length; อ่านสองตัวเรียงกัน เหมือน scanf("%lld %lld", ...)
  • เหมือน scanf ตรงที่ข้ามช่องว่างและการขึ้นบรรทัดใหม่ระหว่างตัวเลขให้เอง ข้อมูลจะอยู่บรรทัดเดียวกันหรือคนละบรรทัดก็อ่านได้

cin ไม่ได้ช่วยเรื่อง overflow

cin แค่อ่านให้ตรงชนิด การเลือกชนิดยังเป็นหน้าที่ของน้อง ถ้าประกาศ width กับ length เป็น int ผลคูณก็ยังล้นแบบเดียวกับหน้า 1.3 ทุกอย่าง ต้องคิดขนาดจากขอบเขตในโจทย์เหมือนเดิม

จำทิศของลูกศรด้วยการนึกว่าข้อมูลไหลไปทางไหน cin >> x ข้อมูลไหลจากแป้นพิมพ์เข้าไปใน x ส่วน cout << x ค่าของ x ไหลออกไปที่หน้าจอ ถ้าเขียนลูกศรกลับด้าน คอมไพล์ไม่ผ่าน

cin อ่าน char ได้ด้วย และข้ามช่องว่างกับการขึ้นบรรทัดใหม่ข้างหน้าให้เอง เหมือน " %c" ในบทที่ 2 จึงไม่ต้องกลัวกับดักตัวอักษรค้างแบบ scanf("%c")

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int room;
    char grade;
    cin >> room >> grade;
    cout << "room " << room << " grade " << grade << '\n';
    return 0;
}
4
B
room 4 grade B

endl กับ '\n'

ขึ้นบรรทัดใหม่ใน cout ทำได้สองแบบ

#include <bits/stdc++.h>
using namespace std;

int main()
{
    cout << "line 1" << endl;
    cout << "line 2" << '\n';
    cout << "line 3\n";
    return 0;
}
line 1
line 2
line 3

ผลเหมือนกันทั้งสามแบบ '\n' ในเครื่องหมายคำพูดเดี่ยวคือ char หนึ่งตัว ส่วนบรรทัดที่ 8 เอา \n ไปไว้ท้ายข้อความเลย ทั้งสองแบบเป็นตัวขึ้นบรรทัดใหม่ตัวเดียวกับที่ใช้ใน printf แต่ endl ทำมากกว่านั้นหนึ่งอย่าง

cout ไม่ได้ส่งตัวอักษรออกไปทีละตัว มันเก็บไว้ในที่พักข้อมูลชั่วคราวที่เรียกว่า buffer ก่อน พอสะสมได้เยอะแล้วค่อยส่งออกไปทีเดียวทั้งก้อน เหมือนรถขนของที่รอให้ของเต็มคันก่อนค่อยออกรถ ไม่ใช่วิ่งไปส่งทีละชิ้น endl คือ '\n' บวกคำสั่ง flush ที่บังคับให้ส่งของใน buffer ออกไปทันทีแม้จะยังไม่เต็ม

พิมพ์ไม่กี่บรรทัดไม่เห็นความต่าง แต่ถ้าอยู่ในลูปที่พิมพ์เป็นล้านบรรทัด endl ทำให้ออกรถเป็นล้านเที่ยว หน้า 15.3 จะวัดให้ดูว่าช้าลงหลายเท่า จึงมีกฎง่ายๆ ว่า ขึ้นบรรทัดใหม่ด้วย '\n' เสมอ ไม่ต้องห่วงว่าข้อมูลจะไม่ออก ตอนโปรแกรมจบ ของที่ค้างใน buffer จะถูกส่งออกให้ครบเอง

อ่านจนหมดข้อมูล

ในบทที่ 4 อ่านจนหมดด้วย while (scanf("%d", &x) == 1) แบบ cin เขียนสั้นกว่า

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int score;
    int count = 0;
    long long total = 0;
    while (cin >> score) {
        count++;
        total += score;
    }
    cout << count << " scores, total " << total << '\n';
    return 0;
}
72 85 64
90 58
5 scores, total 369

cin >> score ใช้เป็นเงื่อนไขได้ อ่านสำเร็จเป็นจริง อ่านไม่ได้เพราะข้อมูลหมดหรือเจอตัวที่ไม่ใช่ตัวเลขเป็นเท็จ ทดลองในหน้าต่างดำก็จบข้อมูลด้วย Enter Ctrl+Z Enter เหมือนเดิม

สังเกตว่าตัวแปรชื่อ count อยู่ใน main จึงไม่ชนกับ count ใน std แบบที่ 2 ในหน้า 15.1 ชนเพราะประกาศไว้นอก main

จัดทศนิยมด้วย fixed และ setprecision

cout พิมพ์ double ไม่เหมือน printf("%f") ลองดูก่อน

#include <bits/stdc++.h>
using namespace std;

int main()
{
    double average = 245 / 3.0;
    cout << average << '\n';
    cout << 1234567 / 3.0 << '\n';
    cout << 98765432.0 << '\n';
    cout << 2.0 << '\n';
    return 0;
}
81.6667
411522
9.87654e+07
2

ถ้าไม่บอกอะไร cout จะพิมพ์ให้สั้นที่สุดโดยใช้ตัวเลขรวมไม่เกิน 6 หลัก (นับทุกหลักที่มีความหมาย ไม่ใช่นับแค่หลังจุด) 81.666666... จึงเหลือ 81.6667 ตัวเลขที่ใหญ่หน่อยอย่าง 411522.333... หลักหลังจุดหายหมด ตัวที่ใหญ่กว่านั้นอีกจะกลายเป็นรูปแบบวิทยาศาสตร์ 9.87654e+07 ซึ่งแปลว่า 9.87654 × 107 และ 2.0 ก็พิมพ์แค่ 2 ไม่มีจุดเลย ไม่มีแบบไหนตรงกับที่โจทย์สั่งว่า "ทศนิยม 2 ตำแหน่ง"

วิธีบอกให้พิมพ์แบบที่ต้องการคือส่งตัวจัดรูปแบบสองตัวเข้าไปใน cout

#include <bits/stdc++.h>
using namespace std;

int main()
{
    double average = 245 / 3.0;
    cout << fixed << setprecision(2); // (1)!
    cout << average << '\n';
    cout << 1234567 / 3.0 << '\n';
    cout << 98765432.0 << '\n';
    cout << 2.0 << '\n';
    cout << 17 << '\n'; // (2)!
    return 0;
}
  1. fixed แปลว่าใช้จำนวนตำแหน่งทศนิยมตายตัว และ setprecision(2) บอกว่าตายตัวที่ 2 ตำแหน่ง สองตัวนี้รวมกันได้ผลแบบเดียวกับ %.2f ส่งเข้าไปครั้งเดียวแล้วมีผลกับ double ทุกตัวที่พิมพ์หลังจากนั้น ไม่ต้องใส่ซ้ำทุกบรรทัด setprecision อยู่ในไฟล์ iomanip ซึ่ง bits/stdc++.h ดึงมาให้แล้ว
  2. จำนวนเต็มไม่โดนผลกระทบ 17 ยังพิมพ์เป็น 17 ไม่ใช่ 17.00
81.67
411522.33
98765432.00
2.00
17

โปรแกรมที่ต้องตอบเป็นทศนิยมให้ใส่ cout << fixed << setprecision(N); ไว้ต้น main ครั้งเดียว แล้วพิมพ์ตามปกติ

เลขที่ตกตรงครึ่งพอดี

เรื่องปัดเศษตรงครึ่งในหน้า 2.1 ยังต้องระวังเหมือนเดิม บนเครื่องที่เขียนเอกสารนี้ cout พิมพ์ 0.125 แบบสองตำแหน่งได้ 0.12 และ 0.25 แบบหนึ่งตำแหน่งได้ 0.2 ซึ่งต่างจาก printf ในโปรแกรมภาษา C บนเครื่องเดียวกันที่ได้ 0.13 กับ 0.3 ไม่ต้องจำว่าแบบไหนได้อะไร จำแค่ว่าเลขตรงครึ่งพอดีเชื่อไม่ได้ คนตั้งโจทย์จึงหลีกเลี่ยงอยู่แล้ว

ลองทำให้พัง

แบบที่ 1 ลูกศรกลับด้าน

ในโปรแกรมที่รับคะแนนหนึ่งตัวแล้วพิมพ์ออกมา เขียน cin << score; แทน cin >> score;

shift.cpp:7:9: error: no match for 'operator<<' (operand types are 'std::istream' {aka 'std::basic_istream<char>'} and 'int')
    7 |     cin << score;
      |     ~~~ ^~ ~~~~~
      |     |      |
      |     |      int
      |     std::istream {aka std::basic_istream<char>}

ต่อจากนี้ยังมี note อีกกว่า 450 บรรทัด ไล่ทุก << ที่ compiler รู้จัก ไม่ต้องอ่าน ข้อความ error ของ C++ ยาวแบบนี้บ่อย ให้อ่านแค่บรรทัดแรกที่มีคำว่า error ซึ่งบอกว่าไม่มี << ที่ใช้กับ std::istream (ชนิดของ cin) และ int ได้ แปลง่ายๆ ว่า cin ใช้ << ไม่ได้ เปลี่ยนเป็น >> ก็หาย ถ้าใช้ cout >> ก็ได้ error ยาวแบบเดียวกัน

แบบที่ 2 ลืมช่องว่างระหว่างตัวเลข

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int hours = 2, minutes = 15;
    cout << hours << minutes << '\n';
    cout << hours << " " << minutes << '\n';
    return 0;
}
215
2 15

บรรทัดแรกได้ 215 ซึ่งดูเหมือนตัวเลขตัวเดียว คอมไพล์ผ่านไม่มีคำเตือนเพราะ compiler ไม่รู้ว่าเราตั้งใจให้มีช่องว่าง ส่งแบบนี้ไป grader ตรวจว่าผิดทันที คั่นด้วย " " หรือ ' ' ได้ผลเหมือนกัน

แบบที่ 3 ใช้ setprecision โดยไม่มี fixed

#include <bits/stdc++.h>
using namespace std;

int main()
{
    double average = 245 / 3.0;
    double distance = 1234.5;
    cout << setprecision(2);
    cout << average << '\n';
    cout << distance << '\n';
    return 0;
}
82
1.2e+03

ตั้งใจจะได้ 81.67 กับ 1234.50 แต่ได้ 82 กับ 1.2e+03 เพราะถ้าไม่มี fixed ตัวเลขใน setprecision หมายถึงจำนวนหลักทั้งหมด ไม่ใช่จำนวนตำแหน่งหลังจุด 81.67 เหลือสองหลักจึงปัดเป็น 82 ส่วน 1234.5 เหลือสองหลักได้ 1200 ซึ่งพิมพ์เป็น 1.2e+03 ใช้ fixed คู่กับ setprecision เสมอ

สรุป

  • cout << a << ' ' << b << '\n'; พิมพ์ได้ทุกชนิด ไม่ต้องใช้ %d แต่ต้องใส่ช่องว่างเอง
  • cin >> a >> b; อ่านตามชนิดของตัวแปร ไม่ต้องมี & ชนิดยังต้องเลือกให้ใหญ่พอเอง
  • ลูกศรชี้ทางที่ข้อมูลไหล cin >> เข้าตัวแปร cout << ออกจอ
  • endl คือ '\n' บวก flush ใช้ '\n' เสมอ
  • while (cin >> x) อ่านจนหมดข้อมูล
  • ทศนิยม N ตำแหน่ง ใส่ cout << fixed << setprecision(N); ครั้งเดียวต้น main ห้ามลืม fixed