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

Ch11: Functions

TL;DR: One Definition Rule (ODR): ตัวแปรอิสระ (Freestanding variable) และฟังก์ชันทั่วไปจะมีคำนิยาม (Definition) ได้เพียงหนึ่งเดียวในโปรแกรม ไม่เช่นนั้นจะเกิด Linker Error ยกเว้น Class ที่นิยามซ้ำข้าม Object File ได้

⚡ Quick Reference

#include <iostream>

// การประกาศฟังก์ชัน (Function Prototype)
void process(int val);

// Pass by value: คัดลอกค่าข้ามไปทำงาน ไม่กระทบตัวแปรต้นทาง
void pass_value(int num) { num += 10; }

// Pass by reference: อ้างอิงตัวแปรจริง สามารถเปลี่ยนแปลงต้นทางได้
void pass_ref(int& num) { num += 10; }

// Pass by pointer: อ้างอิงผ่านแอดเดรสพอยน์เตอร์ ต้องระวังค่า null
void pass_ptr(int* p_num) {
    if (p_num) {
        *p_num += 10;
    }
}

int main() {
    int x = 10;
    pass_value(x);
    pass_ref(x);
    pass_ptr(&x);
    return 0;
}

🧠 Core Concepts

  • One Definition Rule (ODR): ตัวแปรอิสระ (Freestanding variable) และฟังก์ชันทั่วไปจะมีคำนิยาม (Definition) ได้เพียงหนึ่งเดียวในโปรแกรม ไม่เช่นนั้นจะเกิด Linker Error ยกเว้น Class ที่นิยามซ้ำข้าม Object File ได้
  • Function Parameter Passing: โดยปกติ อาร์กิวเมนต์จะถูกส่งผ่านค่าแบบคัดลอก (Pass by value) หากต้องการแก้ไขตัวแปรต้นทางในฟังก์ชัน จะต้องส่งค่าผ่านทาง Pointer (*) หรือการอ้างอิง Reference (&)
  • Compilation & Constraints: คำสั่ง #include จะคัดลอกข้อความในไฟล์มาวางเพื่อเตรียม Compile โดยมีคำสั่งจำกัดช่วง Compile-time เช่น constexpr (แนะนำให้ประมวลผลตอนสร้างโปรแกรม) และ consteval (บังคับให้ประมวลผลตอนสร้างโปรแกรมเท่านั้น)

⚠️ Pitfalls (Quick Scan)

ข้อผิดพลาด วิธีแก้
การนิยามตัวแปรอิสระหรือฟังก์ชันซ้ำกันในหลายไฟล์ ตรวจสอบให้มี Definition เพียงแห่งเดียวสำหรับแต่ละสัญลักษณ์ในไฟล์ .cpp ทั้งหมด
นิยามคลาสซ้ำกันภายในไฟล์ .cpp เดียวกัน ใช้ Include Guard หรือ #pragma once เพื่อป้องกันคลาสถูกโหลดซ้ำในไฟล์เดียวกัน
เขียนนิยาม Static Member หรือ out-of-line Method ในหลายออบเจกต์ไฟล์ ย้าย Definition ของเมธอดและตัวแปร static ไปนิยามไว้ในไฟล์ .cpp เพียงไฟล์เดียว
สร้างฟังก์ชันที่มีชื่อและพารามิเตอร์ประเภทเดียวกันสองตัว ตรวจสอบว่าแต่ละฟังก์ชันมีพารามิเตอร์ที่แตกต่างกันเพื่อแยกแยะลายเซ็นฟังก์ชัน
ประกาศโปรโตไทป์ฟังก์ชันแต่ไม่ได้เขียนเนื้อหาฟังก์ชัน (Definition) เขียนเนื้อหาฟังก์ชันที่ประกาศไว้ให้ครบถ้วนในไฟล์ .cpp ที่คอมไพล์
คาดหวังให้ตัวแปรภายนอกเปลี่ยนแปลงเมื่อส่งค่าแบบ Pass-by-value ใช้พารามิเตอร์ประเภท Pointer (*) หรือ Reference (&) ในการรับค่า
ปล่อยให้มีการแปลงค่าทศนิยม double ไปเป็น int อัตโนมัติในฟังก์ชัน แปลงประเภทข้อมูลให้ตรงกับพารามิเตอร์ของฟังก์ชันเพื่อเลี่ยง narrowing
ส่งค่าคนละชนิดอิมพลิซิตไปยังฟังก์ชันที่รับอาร์กิวเมนต์แบบ non-const reference สร้างตัวแปรให้ตรงประเภทก่อนส่งผ่าน Reference หรือปรับพารามิเตอร์เป็น const T&
พยายามแปลง std::string_view เป็น std::string โดยนัย เขียนการแปลงรูปแบบชัดเจนด้วย std::string(name_sv)
ใช้ตัวดำเนินการ a++ ในการบวกค่าเมื่อต้องการผลลัพธ์ใหม่ทันที ใช้ตัวดำเนินการเพิ่มค่าล่วงหน้าแบบ Prefix ++a
ใส่สัญลักษณ์เครื่องหมายดอกจัน (``) เดี่ยวบน Command Line เพื่อคูณตัวเลข แก้ปัญหาโดยการครอบด้วยฟันหนู หรือใช้ตัวอักษรอื่น เช่น x แทนการคูณ
ใช้ sizeof เพื่อตรวจสอบขนาด Array ในฟังก์ชัน ส่งขนาดของ Array แยกเป็นพารามิเตอร์อีกตัว หรือรับข้อมูลแบบ Array Reference T (&arr)[N]
กำหนดค่าเริ่มต้น constinit จากค่าธรรมดาที่ไม่ได้คำนวณเสร็จในระดับ Compile-time ใช้เฉพาะค่าคงที่ตัวเลขธรรมดา หรือชนิดข้อมูลประเภท constexpr ในการกำหนดค่า

📖 Full Details

Cause → Effect → Fix พร้อม timestamp (คลิกเพื่อดู) * **การนิยามตัวแปรอิสระหรือฟังก์ชันซ้ำกันในหลายไฟล์** -> **เกิด Linker error แจ้งเตือน multiple definition ทำให้โปรแกรมรันไม่ได้** -> **ตรวจสอบให้มี Definition เพียงแห่งเดียวสำหรับแต่ละสัญลักษณ์ในไฟล์ `.cpp` ทั้งหมด (01:17)** * **นิยามคลาสซ้ำกันภายในไฟล์ `.cpp` เดียวกัน** -> **เกิด Compile-time error แจ้งเตือน redefinition of class** -> **ใช้ Include Guard หรือ `#pragma once` เพื่อป้องกันคลาสถูกโหลดซ้ำในไฟล์เดียวกัน (03:42)** * **เขียนนิยาม Static Member หรือ out-of-line Method ในหลายออบเจกต์ไฟล์** -> **เกิด Linker error แจ้งเตือนว่ามีการนิยามฟังก์ชันเหล่านั้นซ้ำซ้อน** -> **ย้าย Definition ของเมธอดและตัวแปร static ไปนิยามไว้ในไฟล์ `.cpp` เพียงไฟล์เดียว (06:11)** * **สร้างฟังก์ชันที่มีชื่อและพารามิเตอร์ประเภทเดียวกันสองตัว** -> **เกิด Compile-time error แม้ว่าประเภทการส่งคืนหรือชื่อตัวแปรจะต่างกัน** -> **ตรวจสอบว่าแต่ละฟังก์ชันมีพารามิเตอร์ที่แตกต่างกันเพื่อแยกแยะลายเซ็นฟังก์ชัน (18:25)** * **ประกาศโปรโตไทป์ฟังก์ชันแต่ไม่ได้เขียนเนื้อหาฟังก์ชัน (Definition)** -> **เกิด Linker error แจ้ง undefined reference ใน GCC หรือ unresolved external symbol ใน MSVC** -> **เขียนเนื้อหาฟังก์ชันที่ประกาศไว้ให้ครบถ้วนในไฟล์ `.cpp` ที่คอมไพล์ (33:52)** * **คาดหวังให้ตัวแปรภายนอกเปลี่ยนแปลงเมื่อส่งค่าแบบ Pass-by-value** -> **การแก้ไขจะเกิดกับตัวแปรคัดลอกในฟังก์ชันเท่านั้น ตัวแปรจริงข้างนอกไม่มีการเปลี่ยนแปลง** -> **ใช้พารามิเตอร์ประเภท Pointer (`*`) หรือ Reference (`&`) ในการรับค่า (40:15)** * **ปล่อยให้มีการแปลงค่าทศนิยม double ไปเป็น int อัตโนมัติในฟังก์ชัน** -> **เลขทศนิยมจะถูกตัดเศษทิ้งเงียบๆ ทำให้สูญเสียความแม่นยำในการคำนวณ** -> **แปลงประเภทข้อมูลให้ตรงกับพารามิเตอร์ของฟังก์ชันเพื่อเลี่ยง narrowing (10:33)** * **ส่งค่าคนละชนิดอิมพลิซิตไปยังฟังก์ชันที่รับอาร์กิวเมนต์แบบ non-const reference** -> **Compiler สร้างตัวแปรชั่วคราวมารับค่าแทน ทำให้ตัวแปรหลักภายนอกไม่ถูกเปลี่ยนแปลงค่า** -> **สร้างตัวแปรให้ตรงประเภทก่อนส่งผ่าน Reference หรือปรับพารามิเตอร์เป็น `const T&` (41:00)** * **พยายามแปลง `std::string_view` เป็น `std::string` โดยนัย** -> **เกิด Compile-time error เนื่องจากตัวภาษา C++ บล็อกการแปลงชนิดนี้เพื่อความปลอดภัย** -> **เขียนการแปลงรูปแบบชัดเจนด้วย `std::string(name_sv)` (40:15)** * **ใช้ตัวดำเนินการ `a++` ในการบวกค่าเมื่อต้องการผลลัพธ์ใหม่ทันที** -> **ฟังก์ชันจะคืนค่าเก่าก่อนบวก ทำให้สูญเสียตรรกะคณิตศาสตร์ที่ถูกต้อง** -> **ใช้ตัวดำเนินการเพิ่มค่าล่วงหน้าแบบ Prefix `++a` (37:30)** * **ใส่สัญลักษณ์เครื่องหมายดอกจัน (`*`) เดี่ยวบน Command Line เพื่อคูณตัวเลข** -> **ระบบปฏิบัติการ (Shell) จะตีความเครื่องหมายนั้นเป็นการดึงรายชื่อไฟล์ในเครื่อง ส่งผลให้โปรแกรมรับค่าเพี้ยน** -> **แก้ปัญหาโดยการครอบด้วยฟันหนู หรือใช้ตัวอักษรอื่น เช่น `x` แทนการคูณ (49:30)** * **ใช้ `sizeof` เพื่อตรวจสอบขนาด Array ในฟังก์ชัน** -> **ข้อมูลอาร์เรย์จะลดรูปเป็นพอยน์เตอร์ธรรมดา (Decay) และได้ขนาดพอยน์เตอร์แทน** -> **ส่งขนาดของ Array แยกเป็นพารามิเตอร์อีกตัว หรือรับข้อมูลแบบ Array Reference `T (&arr)[N]` (39:15)** * **กำหนดค่าเริ่มต้น `constinit` จากค่าธรรมดาที่ไม่ได้คำนวณเสร็จในระดับ Compile-time** -> **เกิด Compile-time error เนื่องจากค่าตั้งต้นต้องรู้ผลลัพธ์ตั้งแต่ช่วงคอมไพล์** -> **ใช้เฉพาะค่าคงที่ตัวเลขธรรมดา หรือชนิดข้อมูลประเภท `constexpr` ในการกำหนดค่า (53:00)**

📎 Repo Files

  • 16.Functions/16.2FirstHandOnCppFunctions/main.cpp
  • 16.Functions/16.3FunctionDeclarationsAndDefinitions/main.cpp
  • 16.Functions/16.4MultipleFiles_CompilationModelRevisited/main.cpp
  • 16.Functions/16.5PassByValue/main.cpp
  • 16.Functions/16.6PassByConstValue/main.cpp
  • 16.Functions/16.7PassByPointer/main.cpp
  • 16.Functions/16.8PassByPointerToConst/main.cpp
  • 16.Functions/16.9PassByConstPointerToConst/main.cpp
  • 16.Functions/16.10PassByReference/main.cpp
  • 16.Functions/16.11PassByConstReference/main.cpp
  • 16.Functions/16.13ArrayFunctionParameters/main.cpp
  • 16.Functions/16.15SizedArraysByReference/main.cpp
  • 16.Functions/16.16MultiDimensionalArrayFunctionParameters/main.cpp
  • 16.Functions/16.17DefaultFunctionParameters/main.cpp
  • 16.Functions/16.18ImplicitConversions/main.cpp
  • 16.Functions/16.19ImplicitConversionsWithReferences/main.cpp
  • 16.Functions/16.20ImplicitConversionsWithPointers/main.cpp
  • 16.Functions/16.21String_viewParameters/main.cpp
  • 16.Functions/16.22ImplicitConversionsFromStringViewToString/main.cpp
  • 16.Functions/16.23constexprFunctions/main.cpp
  • 16.Functions/16.24constevalFunctions/main.cpp
  • 17.EnumsAndTypeAliases/17.2EnumClasses/main.cpp
  • 17.EnumsAndTypeAliases/17.3UsingEnum/main.cpp
  • 17.EnumsAndTypeAliases/17.4OldEnums/main.cpp
  • 17.EnumsAndTypeAliases/17.5TypeAliases/main.cpp
  • 18.ArgumentsToTheMainFunction/18.2GrabAndUseTheArguments/main.cpp
  • 18.ArgumentsToTheMainFunction/18.3CalculatorV1/main.cpp
  • 18.ArgumentsToTheMainFunction/18.4CalculatorV2/main.cpp