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

Ch10: Char Manipulation / Strings

TL;DR: Low-Level C-Strings: C-string คือ Array ของตัวอักษรที่ลงท้ายด้วย Null Terminator (\0) ซึ่งต้องการการดูแลพื้นที่หน่วยความจำเองและต้องระมัดระวังขอบเขตการเขียนข้อมูลเพื่อไม่ให้เกิด Buffer Overflow

⚡ Quick Reference

#include <iostream>
#include <string>
#include <string_view>
#include <cctype>
#include <cstring>

int main() {
    // std::string - คลาสเก็บข้อมูลสายอักขระแบบไดนามิกของ C++
    std::string str = "Hello C++";

    // std::string_view - มุมมองส่องข้อความแบบอ่านอย่างเดียวไม่โคลนข้อมูล (C++17)
    std::string_view view = str;

    // std::isalnum, std::isalpha, std::isdigit, std::isspace - ตรวจสอบคุณสมบัติตัวอักษร
    bool is_letter = std::isalpha('A');

    // std::toupper, std::tolower - แปลงประเภทตัวอักษรเป็นพิมพ์ใหญ่หรือพิมพ์เล็ก
    char upper = std::toupper('a');

    char buffer[20] = "Hello";
    // std::strlen - คำนวณหาความยาวของ C-style string
    size_t len = std::strlen(buffer);

    // std::strcmp - เปรียบเทียบข้อความ C-style string ทางพจนานุกรม
    int cmp = std::strcmp(buffer, "Hello");

    // std::strcat - ต่อสายอักขระ C-style string
    std::strcat(buffer, " World");

    // std::strcpy - คัดลอกข้อความ C-style string ไปยังปลายทาง
    char dest[20];
    std::strcpy(dest, buffer);

    return 0;
}

🧠 Core Concepts

  • Low-Level C-Strings: C-string คือ Array ของตัวอักษรที่ลงท้ายด้วย Null Terminator (\0) ซึ่งต้องการการดูแลพื้นที่หน่วยความจำเองและต้องระมัดระวังขอบเขตการเขียนข้อมูลเพื่อไม่ให้เกิด Buffer Overflow
  • Standard String Helper Library: Library <cctype> และ <cstring> จัดเตรียมฟังก์ชันพื้นฐานสำหรับคัดกรองข้อมูลตัวอักษร (เช่น isalpha, isdigit) และฟังก์ชันจัดการ Buffer (เช่น strlen, strcpy)
  • High-Level std::string & std::string_view: C++ ครอบการจัดการหน่วยความจำด้วย std::string (ช่วยขยายขนาดเมมโมรี่อัตโนมัติ) และเพิ่ม std::string_view เพื่อใช้อ่านข้อมูลโดยไม่มีการคัดลอก (Non-owning, read-only)

⚠️ Pitfalls (Quick Scan)

ข้อผิดพลาด วิธีแก้
การตรวจสอบผลลัพธ์ของ std::isalnum กับเลข 1 โดยตรง ประเมินผลลัพธ์ในเงื่อนไขแบบ boolean หรือใช้ static_cast<bool>
ส่งผ่านตัวแปร char ที่มีค่าติดลบไปยังฟังก์ชันกลุ่ม <cctype> | ทำการ Cast ชนิดข้อมูลเป็นunsigned char` ก่อนส่งผ่านฟังก์ชันเสมอ
ใช้คำสั่ง sizeof หาขนาดของ Char Array ที่ลดรูปเป็น Pointer แล้ว ใช้ std::strlen ในการนับจำนวนตัวอักษรที่ไม่รวม null terminator
สั่งเลื่อน Pointer ค้นหาของข้อความต้นทางในลูปค้นหา std::strchr ใช้ Pointer ตัวแยกมารับแอดเดรสผลลัพธ์ที่ได้จากการค้นหา และกำหนดให้สายอักขระหลักเป็น read-only
ใช้ฟังก์ชัน std::strcat หรือ std::strcpy โดยเผื่อขนาดปลายทางไว้ไม่พอ มั่นใจว่าขนาด Array ปลายทางใหญ่พอหรือหันไปใช้ std::string แทน
พยายามป้อน const char[] เป็นตัวแปรปลายทางในการต่อหรือคัดลอกข้อความ C-string ป้อนเฉพาะ Array ตัวอักษรที่ไม่ได้เป็นค่าคงที่ (non-const) เท่านั้น
ใช้ตัวดำเนินการ += เชื่อมข้อมูลตัวเลขจำนวนเต็มเข้ากับ std::string ใช้ std::to_string เพื่อแปลงตัวเลขให้เป็นข้อความก่อนนำไปเชื่อมต่อ
ลืมป้อน Null Terminator ด้วยตนเองหลังใช้ฟังก์ชัน std::strncpy ใส่ Null Terminator (\0) ปิดท้ายสายอักขระที่ตำแหน่งสุดท้ายด้วยตนเอง
พิมพ์ข้อมูลปลายทางที่ได้จากการคัดลอกผ่าน std::string::copy ทำ Zero-init ตัวแปรปลายทางก่อน หรือเขียน Null Terminator ทับตำแหน่งสุดท้าย
นำ String Literal สองตัวมาบวกกันตรงๆ ด้วยเครื่องหมาย + มั่นใจว่ามีตัวแปรตัวใดตัวหนึ่งเป็น std::string หรือวางสองข้อความติดกันโดยไม่มีตัวเชื่อม
เปรียบเทียบค่าระหว่าง std::string กับ Char Array ที่ไม่ได้ลงท้ายด้วย Null Terminator ตรวจสอบให้มั่นใจเสมอว่า Array ตัวอักษรลงท้ายด้วย \0 ทุกครั้งก่อนเปรียบเทียบค่า
ปล่อยให้ std::string_view ทำงานต่อไปหลังจากที่ตัวแปร String หลักหมดอายุแล้ว ห้ามใช้ std::string_view หากสายอักขระที่เป็นเจ้าของข้อมูลหลุดพ้นจากขอบเขตสโคปไปแล้ว
เรียกใช้ std::strlen บนพอยน์เตอร์ข้อมูลของ std::string_view ที่ถูกแก้ไขขนาดแล้ว ใช้คำสั่ง sv.length() หรือ sv.size() แทนที่จะไปดึงความยาวดิบจากพอยน์เตอร์ตรงๆ

📖 Full Details

Cause → Effect → Fix พร้อม timestamp (คลิกเพื่อดู) * **การตรวจสอบผลลัพธ์ของ `std::isalnum` กับเลข `1` โดยตรง** -> **เงื่อนไขอาจเป็นเท็จบนบาง Compiler เนื่องจากฟังก์ชันกลุ่มตรวจคัดกรองจะคืนค่าใดๆ ที่ไม่ใช่ศูนย์เมื่อเป็นจริง (05:54)** -> **ประเมินผลลัพธ์ในเงื่อนไขแบบ boolean หรือใช้ `static_cast` (05:54)** * **ส่งผ่านตัวแปร `char` ที่มีค่าติดลบไปยังฟังก์ชันกลุ่ม `** -> **เกิด Undefined behavior หรือเกิดความเสียหายในการเข้าถึงหน่วยความจำ (06:47)** -> **ทำการ Cast ชนิดข้อมูลเป็น `unsigned char` ก่อนส่งผ่านฟังก์ชันเสมอ (06:47)** * **ใช้คำสั่ง `sizeof` หาขนาดของ Char Array ที่ลดรูปเป็น Pointer แล้ว** -> **ได้ขนาดของ Pointer (8 Byte บนสถาปัตยกรรม 64-bit) แทนที่จะได้ความยาวสายอักขระจริง (15:53)** -> **ใช้ `std::strlen` ในการนับจำนวนตัวอักษรที่ไม่รวม null terminator (15:53)** * **สั่งเลื่อน Pointer ค้นหาของข้อความต้นทางในลูปค้นหา `std::strchr`** -> **ทำให้ประสิทธิภาพแย่ลงและได้จำนวนรอบการวนซ้ำที่ไม่ถูกต้อง (23:24)** -> **ใช้ Pointer ตัวแยกมารับแอดเดรสผลลัพธ์ที่ได้จากการค้นหา และกำหนดให้สายอักขระหลักเป็น read-only (23:24)** * **ใช้ฟังก์ชัน `std::strcat` หรือ `std::strcpy` โดยเผื่อขนาดปลายทางไว้ไม่พอ** -> **เกิด Buffer Overflow ทำลายข้อมูลในหน่วยความจำและโปรแกรมแครช (29:56)** -> **มั่นใจว่าขนาด Array ปลายทางใหญ่พอหรือหันไปใช้ `std::string` แทน (29:56)** * **พยายามป้อน `const char[]` เป็นตัวแปรปลายทางในการต่อหรือคัดลอกข้อความ C-string** -> **เกิด Compile-time error เนื่องจากค่าคงที่ห้ามเปลี่ยนแปลงข้อมูล (29:27)** -> **ป้อนเฉพาะ Array ตัวอักษรที่ไม่ได้เป็นค่าคงที่ (non-const) เท่านั้น (29:27)** * **ใช้ตัวดำเนินการ `+=` เชื่อมข้อมูลตัวเลขจำนวนเต็มเข้ากับ `std::string`** -> **ตัวแปลงข้อมูลจะแปลงเลขนั้นเป็น ASCII Code แทนการพิมพ์ตัวเลข (เช่น พิมพ์ตัวอักษร 'C' แทนเลข 67) (42:14)** -> **ใช้ `std::to_string` เพื่อแปลงตัวเลขให้เป็นข้อความก่อนนำไปเชื่อมต่อ (42:14)** * **ลืมป้อน Null Terminator ด้วยตนเองหลังใช้ฟังก์ชัน `std::strncpy`** -> **การพิมพ์หรือแสดงผลตัวแปรจะแสดงตัวอักษรขยะหรือแครชหากข้อความมีความยาวเกินกำหนด (28:13)** -> **ใส่ Null Terminator (`\0`) ปิดท้ายสายอักขระที่ตำแหน่งสุดท้ายด้วยตนเอง (28:13)** * **พิมพ์ข้อมูลปลายทางที่ได้จากการคัดลอกผ่าน `std::string::copy`** -> **เกิดการพิมพ์อักษรขยะเนื่องจากฟังก์ชัน copy ไม่ได้เติม Null Terminator ให้ (48:50)** -> **ทำ Zero-init ตัวแปรปลายทางก่อน หรือเขียน Null Terminator ทับตำแหน่งสุดท้าย (48:50)** * **นำ String Literal สองตัวมาบวกกันตรงๆ ด้วยเครื่องหมาย `+`** -> **เกิด Compile-time error เนื่องจากตัวภาษา C++ ไม่รองรับการนำ Pointer ของตัวอักษรสองตัวมาบวกกัน (40:26)** -> **มั่นใจว่ามีตัวแปรตัวใดตัวหนึ่งเป็น `std::string` หรือวางสองข้อความติดกันโดยไม่มีตัวเชื่อม (40:26)** * **เปรียบเทียบค่าระหว่าง `std::string` กับ Char Array ที่ไม่ได้ลงท้ายด้วย Null Terminator** -> **โปรแกรมจะคำนวณและอ่านข้อมูลข้ามขอบเขตไปเรื่อยๆ จนกว่าจะเจอ Null Byte ในหน่วยความจำ (50:41)** -> **ตรวจสอบให้มั่นใจเสมอว่า Array ตัวอักษรลงท้ายด้วย `\0` ทุกครั้งก่อนเปรียบเทียบค่า (50:41)** * **ปล่อยให้ `std::string_view` ทำงานต่อไปหลังจากที่ตัวแปร String หลักหมดอายุแล้ว** -> **เกิดตัวอ้างอิงค้าง (Dangling Reference) ซึ่งจะอ่านแอดเดรสที่ไม่มีอยู่จริงและทำให้เกิดแครช (58:13)** -> **ห้ามใช้ `std::string_view` หากสายอักขระที่เป็นเจ้าของข้อมูลหลุดพ้นจากขอบเขตสโคปไปแล้ว (58:13)** * **เรียกใช้ `std::strlen` บนพอยน์เตอร์ข้อมูลของ `std::string_view` ที่ถูกแก้ไขขนาดแล้ว** -> **จะอ่านค่าความยาวเกินขอบเขตจริงเนื่องจากความกว้างของข้อความที่ถูกตัดจะเก็บเฉพาะในตัวแปรระดับ view เท่านั้น (58:38)** -> **ใช้คำสั่ง `sv.length()` หรือ `sv.size()` แทนที่จะไปดึงความยาวดิบจากพอยน์เตอร์ตรงๆ (58:38)**

📎 Repo Files

  • 15.2CharacterManipulation/main.cpp
  • 15.3CStringManipulation/main.cpp
  • 15.4CStringConcatenationAndCopy/main.cpp
  • 15.6DeclaringAndUsingStdString/main.cpp
  • 15.7ConcatenatingStdStrings/main.cpp
  • 15.8AccessingCharactersInStdString/main.cpp
  • 15.9StdStringSizeAndCapacity/main.cpp
  • 15.10ModifyingStdStrings/main.cpp
  • 15.11ComparingStdStrings/main.cpp
  • 15.12StdStringCompare/main.cpp
  • 15.13StdStringReplacingCopyingResizingSwapping/main.cpp
  • 15.14SearchingStdString/main.cpp
  • 15.15TransformingStdStringFromToNumbers/main.cpp
  • 15.16EscapeSequences/main.cpp
  • 15.17RawStringLiterals/main.cpp
  • 15.18CopiedStrings/main.cpp
  • 15.19StringView/main.cpp