ข้ามไปที่เนื้อหา

Ch4: Operations on Data

TL;DR: Arithmetic and Shift Operators: C++ เตรียมตัวดำเนินการพื้นฐานทั้งคณิตศาสตร์, Compound Assignment, Increment, Decrement, และ Bitwise เพื่อใช้จัดการสถานะหน่วยความจำได้โดยตรง | ทุกประเภทข้อมูลตัวเลขพื้นฐานจะมีขอบเขตที่แน่นอนซึ่งกำหนดโดย Standard Library (std::numeric_limits) และจำนวน Bit ที่ใช้แทนค่าในระบบเลขฐานสอง — ชนิดข้อมูลแบบมีเครื่องหมาย (Signed Types) จะใช้ Two's Complement ส่วนชนิดข้อมูลแบบไม่มีเครื่องหมาย (Unsigned Types) จะมีการเวียนกลับครบรอบ (Wrap Around)

⚡ Quick Reference

#include <iostream>

int main() {
    int a = 10;
    int b = 3;

    // a + b, a - b, a * b, a / b, a % b - การดำเนินการทางคณิตศาสตร์พื้นฐาน
    int sum = a + b;
    int diff = a - b;
    int prod = a * b;
    int quot = a / b;
    int rem = a % b;

    // ++a, a++, --a, a-- - ตัวดำเนินการเพิ่มค่าและลดค่า
    int x1 = ++a; // Prefix: เพิ่มค่าก่อนแล้วค่อยคืนผลลัพธ์
    int x2 = a++; // Postfix: คืนผลลัพธ์ก่อนแล้วค่อยเพิ่มค่า

    // a += b, a -= b - ตัวดำเนินการกำหนดค่าแบบผสม
    a += b;

    // a < b, a > b, a == b - ตัวดำเนินการเปรียบเทียบค่า
    bool eq = (a == b);

    // a && b, a || b, !a - ตัวดำเนินการทางตรรกศาสตร์
    bool res = (a > 5 && b < 10);

    // std::ios::boolalpha, std::noboolalpha - แสดงค่าความจริงเป็นข้อความ true/false หรือตัวเลข 1/0
    std::cout << std::boolalpha << res << std::noboolalpha << std::endl;

    return 0;
}

ขีดจำกัดตัวเลขและการแทนค่าเลขฐานสอง (Numeric Limits & Binary Representation)

#include <iostream>
#include <limits>
#include <bitset>
#include <cstdint>

int main() {
    // std::numeric_limits<T> - ค้นหาค่า min/max/precision สำหรับชนิดข้อมูลตัวเลขใดๆ ในขั้นตอน Compile-time
    std::cout << "int min: "   << std::numeric_limits<int>::min() << '\n';
    std::cout << "int max: "   << std::numeric_limits<int>::max() << '\n';
    std::cout << "size_t max: " << std::numeric_limits<size_t>::max() << '\n';

    // std::numeric_limits<T>::digits - จำนวน Bit ที่ใช้สำหรับเก็บค่า (ไม่รวม Bit แสดงเครื่องหมาย)
    std::cout << "int digits: " << std::numeric_limits<int>::digits << '\n';

    // Fixed-width integer types (<cstdint>) - รับประกันขนาด Bit ที่แน่นอนในทุกแพลตฟอร์ม
    int32_t a{100};
    uint8_t b{255};

    // std::bitset<N> - แสดงข้อมูลดิบในรูปแบบเลขฐานสองของตัวแปร
    std::bitset<8> bits(b);
    std::cout << "255 as bits: " << bits << '\n';

    // Two's complement: ตัวเลขจำนวนเต็มลบแบบมีเครื่องหมายจะแสดงผลโดยการกลับบิตทั้งหมดแล้วบวกด้วย 1
    int8_t neg{-1};
    std::bitset<8> negBits(static_cast<uint8_t>(neg));
    std::cout << "-1 as bits: " << negBits << '\n'; // 11111111

    return 0;
}

🧠 Core Concepts

  • Arithmetic and Shift Operators: C++ เตรียมตัวดำเนินการพื้นฐานทั้งคณิตศาสตร์, Compound Assignment, Increment, Decrement, และ Bitwise เพื่อใช้จัดการสถานะหน่วยความจำได้โดยตรง
  • Constant Correctness (const, constexpr, constinit): การกำหนดค่าคงที่เพื่อความปลอดภัย ประกอบด้วย const (เป็น Read-only ตอน Runtime), constexpr (ประมวลผลค่าคงที่ตอน Compile), และ constinit (การันตีการเซ็ตค่าเริ่มต้นตอน Compile แต่ตัวแปรยังสามารถเปลี่ยนค่าได้ตอน Runtime)
  • Integral Promotion & Type Conversion: ในการคำนวณทางคณิตศาสตร์ ชนิดข้อมูลขนาดเล็กอย่าง short และ char จะถูกโปรโมตเป็น int โดยอัตโนมัติ และควรใช้ static_cast ในการทำ Type Conversion เพื่อเลี่ยงการเกิด Warning จากการลดรูปข้อมูล
  • std::numeric_limits<T>: คลาสคุณลักษณะ (Trait Class) ที่สามารถตรวจสอบได้ในขั้นตอน Compile-time เพื่อดึงค่าต่ำสุด (min), ค่าสูงสุด (max), ความแม่นยำ (precision) และคุณสมบัติอื่นๆ ของชนิดข้อมูลตัวเลขใดๆ — ซึ่งปลอดภัยกว่าการเขียนตัวเลขแบบเจาะจง (Hardcoding Magic Numbers) เช่น 2147483647
  • Two's Complement: รูปแบบเลขฐานสองมาตรฐานสำหรับเก็บข้อมูลจำนวนเต็มแบบมีเครื่องหมาย (Signed Integers) โดยบิตที่มีนัยสำคัญสูงสุด (Most Significant Bit) จะทำหน้าที่เป็นบิตแสดงเครื่องหมาย (Sign Bit) ส่วนค่าติดลบจะถูกจัดเก็บด้วยการกลับบิตทั้งหมด (Invert Bits) ของค่าบวกนั้นๆ แล้วบวกด้วย 1
  • Fixed-Width Types: ไลบรารี <cstdint> มีการเตรียมชนิดข้อมูล เช่น int32_t, uint8_t, int64_t ที่รับประกันขนาดความกว้างบิตที่แน่นอนโดยไม่ขึ้นกับแพลตฟอร์ม แตกต่างจาก int หรือ long ที่ขนาดอาจเปลี่ยนแปลงได้ตามแต่ตัวคอมไพเลอร์หรือสถาปัตยกรรมของเครื่อง

⚠️ Pitfalls (Quick Scan)

ข้อผิดพลาด วิธีแก้
ลืมใส่ Semicolon ปิดท้ายคำสั่ง ใส่เครื่องหมาย ; ปิดท้ายคำสั่งเสมอ (Ch 4.3, 5.2)
การใช้ Block Comment ซ้อนกัน (/ / ... / /) ห้ามคอมเมนต์แบบบล็อกซ้อนกัน ให้ใช้คอมเมนต์บรรทัดเดียว (//) แทน (Ch 4.2)
การหารด้วยศูนย์ (Division by zero) ตรวจสอบเสมอว่าตัวหารไม่ใช่ศูนย์ก่อนทำการหาร (Ch 4.3)
ลืมเขียน Directive #include ที่จำเป็น ตรวจเช็คและทำการ #include Header ที่จำเป็นไว้ด้านบนของไฟล์เสมอ (Ch 4.1)
การกำหนดค่าเริ่มต้นแบบ Functional ด้วยชนิดข้อมูลที่แคบกว่า (Narrowing) ใช้ Braced Initialization {} เพื่อดักจับปัญหาการลดรูปข้อมูลตอน Compile (6.2, 7.2)
การใช้ตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้น กำหนดค่าเริ่มต้นให้ตัวแปรเสมอ หรือใช้สัญลักษณ์ {} เพื่อทำ Zero-init (5.2, 9.2)
การใช้ std::cin >> รับค่าข้อมูลที่มีช่องว่าง ใช้ std::getline(cin, var) เมื่อต้องการรับข้อมูล String ที่มีช่องว่าง (7.2)
การใช้เลข 0 นำหน้าเลขฐานสิบ ห้ามใส่เลข 0 นำหน้าจำนวนเต็มเลขฐานสิบโดยเด็ดขาด (6.2)
เข้าใจผิดว่า std::setw จะจัดระเบียบทุกค่าที่ตามมา ต้องเรียกใช้ std::setw หน้าตัวแปรทุกครั้งที่จะแสดงผลตาราง (5.8)
การลดรูปข้อมูลโดยนัย (Implicit Narrowing) ในการกำหนดค่า ใช้ static_cast<int>() เพื่อแสดงเจตนาแปลงข้อมูลชัดเจน หรือใช้ Braced initialization (7.2)
ปัญหาค่าเกินขีดจำกัด (Overflow) ใน Unsigned type ตรวจสอบขอบเขตด้วย std::numeric_limits และใช้ประเภทข้อมูลที่ใหญ่พอ (7.4)
ปัญหาค่าต่ำกว่าขีดจำกัด (Underflow) ใน Unsigned type ตรวจสอบก่อนเสมอว่าตัวแปรมีค่ามากกว่าศูนย์ก่อนทำการลดค่า (7.4)
การนำข้อมูลประเภท short หรือ char มาคำนวณทางคณิตศาสตร์ ทำความเข้าใจเรื่องการโปรโมตข้อมูล และ Cast ผลลัพธ์กลับหากต้องการชนิดข้อมูลเดิม (5.11)
การกำหนดค่าคงที่ constinit จากตัวแปรธรรมดาที่รู้ผลตอน Runtime ตรวจสอบให้แน่ใจว่าค่าที่ป้อนให้ constinit เป็นชนิด constexpr (6.5)
การประกาศใช้ constexpr คู่กับ constinit ในตัวแปรเดียวกัน เลือกตัวใดตัวหนึ่ง: constexpr สำหรับค่าคงที่เปลี่ยนไม่ได้ หรือ constinit สำหรับตัวแปรที่เริ่มตอน Compile แต่แก้ไขได้ (6.5)
การใช้เงื่อนไขตรวจสอบที่ไม่ใช่ค่าคงที่ใน static_assert ระบุเฉพาะเงื่อนไขและตัวแปรที่สามารถคำนวณเสร็จสิ้นได้ตั้งแต่ช่วงคอมไพล์เท่านั้น (6.4)
เข้าใจผิดว่าการเปรียบเทียบตรรกะจะพิมพ์ข้อความ true/false เสมอ ใส่คำสั่ง std::cout << std::boolalpha เพื่อปรับแต่งการแสดงผล (5.6)
เข้าใจผิดว่าเลขฐานสิบหกจะพิมพ์ตัวอักษรพิมพ์ใหญ่เสมอ ใช้ std::uppercase ร่วมด้วยเพื่อบังคับแสดงตัวพิมพ์ใหญ่ (5.8)
ตั้งค่าความละเอียดทศนิยมเกินขีดจำกัดประเภทข้อมูล ห้ามตั้งค่าทศนิยมใน setprecision เกิน 7 หลักสำหรับ float และ 15 หลักสำหรับ double (5.8)
กำหนดค่า min/max แบบเจาะจงลงไปตรงๆ (เช่น 2147483647) ใช้ std::numeric_limits<T>::max() แทน
ทึกทักว่า int จะมีขนาด 4 Bytes เสมอในทุกแพลตฟอร์ม ใช้ชนิดข้อมูลแบบระบุความกว้างคงที่ (int32_t) เมื่อต้องการขนาดที่แน่นอน
เปรียบเทียบข้อมูลแบบมีเครื่องหมายและไม่มีเครื่องหมายโดยตรง แปลงชนิดข้อมูลอย่างชัดเจน (Explicit Cast) หรือใช้ชนิดข้อมูลประเภทเดียวกันเพื่อเลี่ยงการแปลงค่าโดยอัตโนมัติ
คิดว่าเลขติดลบแค่สลับบิตเครื่องหมาย (Sign Bit) เท่านั้น ทำความเข้าใจระบบ Two's Complement ว่าเป็นการกลับบิตทั้งหมดและบวก 1
ใช้ int8_t/uint8_t ใน std::cout โดยคาดหวังว่าจะแสดงตัวเลข ชนิดข้อมูลเหล่านี้มักเป็นนามแฝง (Alias) ของ char ต้องแปลงเป็น int ก่อน ไม่เช่นนั้นจะแสดงผลเป็นตัวอักษร

📖 Full Details

Cause → Effect → Fix พร้อม timestamp (คลิกเพื่อดู) * **ลืมใส่ Semicolon ปิดท้ายคำสั่ง** -> **เกิด Compile-time error แจ้งเตือนเรื่องลืมใส่ Semicolon ก่อนสั่ง Return** -> **ใส่เครื่องหมาย `;` ปิดท้ายคำสั่งเสมอ (Ch 4.3, 5.2)** * **การใช้ Block Comment ซ้อนกัน (`/* /* ... */ */`)** -> **เกิด Compile error เนื่องจากหาจุดปิดคอมเมนต์ไม่เจอ** -> **ห้ามคอมเมนต์แบบบล็อกซ้อนกัน ให้ใช้คอมเมนต์บรรทัดเดียว (`//`) แทน (Ch 4.2)** * **การหารด้วยศูนย์ (Division by zero)** -> **Compiler แจ้งเตือนและโปรแกรมจะแครชในจังหวะ Runtime เมื่อดึงผลลัพธ์ไปใช้** -> **ตรวจสอบเสมอว่าตัวหารไม่ใช่ศูนย์ก่อนทำการหาร (Ch 4.3)** * **ลืมเขียน Directive `#include` ที่จำเป็น** -> **เกิด Compile error แจ้งว่า `namespace std has no member`** -> **ตรวจเช็คและทำการ `#include` Header ที่จำเป็นไว้ด้านบนของไฟล์เสมอ (Ch 4.1)** * **การกำหนดค่าเริ่มต้นแบบ Functional ด้วยชนิดข้อมูลที่แคบกว่า (Narrowing)** -> **ทศนิยมจะถูกปัดทิ้งอย่างเงียบๆ โดยไม่มี Warning แจ้งเตือน** -> **ใช้ Braced Initialization `{}` เพื่อดักจับปัญหาการลดรูปข้อมูลตอน Compile (6.2, 7.2)** * **การใช้ตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้น** -> **ตัวแปรจะเก็บค่าขยะจากหน่วยความจำ ทำให้เกิด Undefined behavior** -> **กำหนดค่าเริ่มต้นให้ตัวแปรเสมอ หรือใช้สัญลักษณ์ `{}` เพื่อทำ Zero-init (5.2, 9.2)** * **การใช้ `std::cin >>` รับค่าข้อมูลที่มีช่องว่าง** -> **ข้อมูลสายอักขระจะถูกตัดตอนเจอช่องว่างแรก และข้อมูลส่วนที่เหลือจะค้างอยู่ใน Buffer** -> **ใช้ `std::getline(cin, var)` เมื่อต้องการรับข้อมูล String ที่มีช่องว่าง (7.2)** * **การใช้เลข 0 นำหน้าเลขฐานสิบ** -> **Compiler จะตีความเลขดังกล่าวเป็นเลขฐานแปด (Octal) ซึ่งให้ผลลัพธ์ที่ไม่ถูกต้อง** -> **ห้ามใส่เลข 0 นำหน้าจำนวนเต็มเลขฐานสิบโดยเด็ดขาด (6.2)** * **เข้าใจผิดว่า `std::setw` จะจัดระเบียบทุกค่าที่ตามมา** -> **ช่องว่างจัดเรียงจะส่งผลกับข้อมูลตัวถัดไปเพียงตัวเดียวเท่านั้น** -> **ต้องเรียกใช้ `std::setw` หน้าตัวแปรทุกครั้งที่จะแสดงผลตาราง (5.8)** * **การลดรูปข้อมูลโดยนัย (Implicit Narrowing) ในการกำหนดค่า** -> **ตัวเลขทศนิยมจะถูกตัดเศษทิ้งกลายเป็นจำนวนเต็ม ทำให้สูญเสียข้อมูล** -> **ใช้ `static_cast()` เพื่อแสดงเจตนาแปลงข้อมูลชัดเจน หรือใช้ Braced initialization (7.2)** * **ปัญหาค่าเกินขีดจำกัด (Overflow) ใน Unsigned type** -> **ค่าจะวนกลับไปที่ 0 อย่างเงียบๆ ทำให้ตรรกะโปรแกรมทำงานผิดพลาด** -> **ตรวจสอบขอบเขตด้วย `std::numeric_limits` และใช้ประเภทข้อมูลที่ใหญ่พอ (7.4)** * **ปัญหาค่าต่ำกว่าขีดจำกัด (Underflow) ใน Unsigned type** -> **ค่าจะวนกลับไปเป็นค่าสูงสุดของประเภทข้อมูลนั้นๆ** -> **ตรวจสอบก่อนเสมอว่าตัวแปรมีค่ามากกว่าศูนย์ก่อนทำการลดค่า (7.4)** * **การนำข้อมูลประเภท `short` หรือ `char` มาคำนวณทางคณิตศาสตร์** -> **ตัวแปรขนาดเล็กจะถูกโปรโมตเป็น `int` ทำให้ผลลัพธ์เป็น `int` ไม่ใช่ประเภทเดิม** -> **ทำความเข้าใจเรื่องการโปรโมตข้อมูล และ Cast ผลลัพธ์กลับหากต้องการชนิดข้อมูลเดิม (5.11)** * **การกำหนดค่าคงที่ `constinit` จากตัวแปรธรรมดาที่รู้ผลตอน Runtime** -> **เกิด Compile-time error เนื่องจากตัวหารหรือค่าเริ่มต้นต้องพร้อมตั้งแต่ตอน Compile** -> **ตรวจสอบให้แน่ใจว่าค่าที่ป้อนให้ `constinit` เป็นชนิด `constexpr` (6.5)** * **การประกาศใช้ `constexpr` คู่กับ `constinit` ในตัวแปรเดียวกัน** -> **เกิด Compile error เนื่องจากข้อกำหนดทั้งสองขัดแย้งกัน** -> **เลือกตัวใดตัวหนึ่ง: `constexpr` สำหรับค่าคงที่เปลี่ยนไม่ได้ หรือ `constinit` สำหรับตัวแปรที่เริ่มตอน Compile แต่แก้ไขได้ (6.5)** * **การใช้เงื่อนไขตรวจสอบที่ไม่ใช่ค่าคงที่ใน `static_assert`** -> **เกิด Compile error เนื่องจาก `static_assert` ทำงานเฉพาะระดับ Compile-time** -> **ระบุเฉพาะเงื่อนไขและตัวแปรที่สามารถคำนวณเสร็จสิ้นได้ตั้งแต่ช่วงคอมไพล์เท่านั้น (6.4)** * **เข้าใจผิดว่าการเปรียบเทียบตรรกะจะพิมพ์ข้อความ true/false เสมอ** -> **ผลลัพธ์จะพิมพ์เป็นตัวเลข 1 หรือ 0 ทำให้การตรวจสอบยากขึ้น** -> **ใส่คำสั่ง `std::cout << std::boolalpha` เพื่อปรับแต่งการแสดงผล (5.6)** * **เข้าใจผิดว่าเลขฐานสิบหกจะพิมพ์ตัวอักษรพิมพ์ใหญ่เสมอ** -> **ตัวอักษรฐานสิบหกจะแสดงผลเป็นตัวพิมพ์เล็กโดยเริ่มต้น** -> **ใช้ `std::uppercase` ร่วมด้วยเพื่อบังคับแสดงตัวพิมพ์ใหญ่ (5.8)** * **ตั้งค่าความละเอียดทศนิยมเกินขีดจำกัดประเภทข้อมูล** -> **ตัวเลขหลังขีดจำกัดความละเอียดจะกลายเป็นเลขขยะที่ไม่มีความหมาย** -> **ห้ามตั้งค่าทศนิยมใน `setprecision` เกิน 7 หลักสำหรับ `float` และ 15 หลักสำหรับ `double` (5.8)** * **กำหนดค่า min/max แบบเจาะจงลงไปตรงๆ** -> **โค้ดอาจทำงานผิดพลาดอย่างเงียบๆ หากคอมไพล์บนแพลตฟอร์มที่ขนาดชนิดข้อมูลต่างกัน** -> **ใช้ `std::numeric_limits::max()`/`::min()` เพื่อให้ขอบเขตปรับตามประเภทข้อมูลจริง** * **ทึกทักว่า `int` จะมีขนาด 4 Bytes เสมอ** -> **เกิดบั๊กด้านการพอร์ตโปรแกรม (Portability) บนระบบสมองกลฝังตัว (Embedded) หรือแพลตฟอร์มรุ่นเก่าที่ `int` อาจมีขนาดแค่ 2 Bytes** -> **ใช้ชนิดข้อมูลแบบระบุความกว้างคงที่จาก `` (`int32_t`, `uint16_t` ฯลฯ) เมื่อต้องการขนาดที่แน่นอน** * **เปรียบเทียบข้อมูลแบบมีเครื่องหมายและไม่มีเครื่องหมายโดยตรง (เช่น `int` เทียบกับ `size_t`)** -> **คอมไพเลอร์จะแปลงค่าแบบมีเครื่องหมายเป็นไม่มีเครื่องหมายโดยอัตโนมัติ ส่งผลให้เกิดข้อผิดพลาดกับเลขติดลบ** -> **ทำการแปลงชนิดข้อมูลอย่างชัดเจน (Explicit Cast) หรือปรับให้ตัวถูกดำเนินการทั้งคู่มีเครื่องหมายประเภทเดียวกัน** * **ไม่เข้าใจระบบ Two's Complement** -> **เกิดความสับสนเมื่อต้องดีบั๊กการดำเนินการทางบิต (Bitwise Operations) หรือพฤติกรรมค่าล้น (Overflow)** -> **จำไว้ว่า: ค่าติดลบ = กลับบิตทั้งหมดของค่าบวกนั้น แล้วบวกด้วย 1** * **พิมพ์ค่า `int8_t`/`uint8_t` ผ่าน `std::cout`** -> **ผลลัพธ์แสดงเป็นตัวอักษรแทนที่จะเป็นตัวเลข เนื่องจากข้อมูลนี้เป็นนามแฝง (Alias) ของกลุ่ม `char`** -> **แปลงเป็น `int` ก่อนพิมพ์ออกทางหน้าจอ: `std::cout << static_cast(b)`**

📎 Repo Files

  • 05.OperationsOnData/5.2.BasicOperations/main.cpp
  • 05.OperationsOnData/5.3.PrecedenceAndAssociativity/main.cpp
  • 05.OperationsOnData/5.4.PrefixPostfixIncrementDecrement/main.cpp
  • 05.OperationsOnData/5.5.CompoundAssignmentOperators/main.cpp
  • 05.OperationsOnData/5.6.RelationalOperators/main.cpp
  • 05.OperationsOnData/5.7.LogicalOperators/main.cpp
  • 05.OperationsOnData/5.8.OutputFormatting/main.cpp
  • 05.OperationsOnData/5.9.NumericLimits/main.cpp
  • 05.OperationsOnData/5.10.MathFunctions/main.cpp
  • 05.OperationsOnData/5.11.WeirdIntegralTypes/main.cpp
  • 06.LiteralsAndConstants/6.2Literals/main.cpp
  • 06.LiteralsAndConstants/6.3Constants/main.cpp
  • 06.LiteralsAndConstants/6.4ConstantExpressions/main.cpp
  • 06.LiteralsAndConstants/6.5constinit/main.cpp
  • 07.ConversionsOverflowAndUnderflow/7.2ImplicitDataConversions/main.cpp
  • 07.ConversionsOverflowAndUnderflow/7.3ExplicitDataConversions/main.cpp
  • 07.ConversionsOverflowAndUnderflow/7.4OverflowAndUnderflow/main.cpp
  • 08.BitwiseOperators/8.2PrintingIntegersInBinary/main.cpp
  • 08.BitwiseOperators/8.3ShiftOperators/main.cpp
  • 08.BitwiseOperators/8.4LogicalBitwiseOperators/main.cpp
  • 08.BitwiseOperators/8.5CompoundBitwiseOperators/main.cpp
  • 08.BitwiseOperators/8.6Masks/main.cpp
  • 08.BitwiseOperators/8.7MasksExample/main.cpp
  • 08.BitwiseOperators/8.8PackingColorInformation/main.cpp
  • 09.VariableLifetimeAndScope/9.2VariableScope/main.cpp
  • 07.ConversionsOverflowAndUnderflow/main.cpp
  • 08.BitwiseOperators/main.cpp