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.cpp16.Functions/16.3FunctionDeclarationsAndDefinitions/main.cpp16.Functions/16.4MultipleFiles_CompilationModelRevisited/main.cpp16.Functions/16.5PassByValue/main.cpp16.Functions/16.6PassByConstValue/main.cpp16.Functions/16.7PassByPointer/main.cpp16.Functions/16.8PassByPointerToConst/main.cpp16.Functions/16.9PassByConstPointerToConst/main.cpp16.Functions/16.10PassByReference/main.cpp16.Functions/16.11PassByConstReference/main.cpp16.Functions/16.13ArrayFunctionParameters/main.cpp16.Functions/16.15SizedArraysByReference/main.cpp16.Functions/16.16MultiDimensionalArrayFunctionParameters/main.cpp16.Functions/16.17DefaultFunctionParameters/main.cpp16.Functions/16.18ImplicitConversions/main.cpp16.Functions/16.19ImplicitConversionsWithReferences/main.cpp16.Functions/16.20ImplicitConversionsWithPointers/main.cpp16.Functions/16.21String_viewParameters/main.cpp16.Functions/16.22ImplicitConversionsFromStringViewToString/main.cpp16.Functions/16.23constexprFunctions/main.cpp16.Functions/16.24constevalFunctions/main.cpp17.EnumsAndTypeAliases/17.2EnumClasses/main.cpp17.EnumsAndTypeAliases/17.3UsingEnum/main.cpp17.EnumsAndTypeAliases/17.4OldEnums/main.cpp17.EnumsAndTypeAliases/17.5TypeAliases/main.cpp18.ArgumentsToTheMainFunction/18.2GrabAndUseTheArguments/main.cpp18.ArgumentsToTheMainFunction/18.3CalculatorV1/main.cpp18.ArgumentsToTheMainFunction/18.4CalculatorV2/main.cpp