คู่มือการศึกษา: ฟังก์ชันในภาษา C++ (C++ Functions Study Guide)
คู่มือการศึกษา: ฟังก์ชันในภาษา C++ ฉบับสมบูรณ์
ฟังก์ชัน (Functions) เป็นกลไกหลักที่ใช้ขับเคลื่อนระบบการทำงานภายในโปรแกรม C++ การกำหนดฟังก์ชัน (Defining a function) คือการระบุว่างานนั้นๆ มีสเต็ปการทำงานอย่างไร ในขณะที่การเรียกใช้ (Calling a function) คือการสั่งให้โค้ดส่วนนั้นเริ่ม Execute เนื้อหาในบทความนี้จะครอบคลุมตั้งแต่เบสิกไปจนถึงเทคนิคที่ Developer ควรรู้
1. การประกาศและการนิยาม (Declarations and Definitions)
กฎเหล็กใน C++ คือ คอมไพเลอร์จะไม่ยอมให้เรียกใช้ฟังก์ชันเด็ดขาดถ้ายังไม่มีการ ประกาศ (Declaration) ล่วงหน้า
1.1 การประกาศฟังก์ชัน (Function Declaration)
หรือที่เรียกว่า Function Signature คือการบอกให้คอมไพเลอร์รู้จักหน้าตาของฟังก์ชันก่อน ประกอบด้วย:
- ชื่อฟังก์ชัน (Name)
- ค่าที่ Return (Return Type): ถ้าไม่มีการคืนค่ากลับให้ใช้
void - พารามิเตอร์ (Arguments): ระบุประเภทตัวแปรที่รับเข้ามาในวงเล็บ
ตัวอย่างการประกาศ:
double sqrt(double); // รับค่า double หนึ่งตัว และให้ผลลัพธ์กลับเป็น double
void exit(int); // รับค่า int แต่ไม่คืนค่า (void)
Elem* next_elem(); // ไม่รอรับพารามิเตอร์ คืนค่าเป็นพอยน์เตอร์ชี้ไปที่ Elem
1.2 การนิยามฟังก์ชัน (Function Definition)
ส่วนนี้คือการเขียน Body ของฟังก์ชันเพื่อให้มันทำงานได้จริง ในโปรเจกต์ระดับโปรดักชั่น เรานิยมแยกส่วนดึงการประกาศไปไว้ใน Header file (.h/.hpp) และเขียนนิยามใน Source file (.cpp) เพื่อลดเวลา Compile (Separate Compilation)
ตัวอย่างการนิยาม:
double square(double x) // ฟังก์ชันคำนวณค่ายกกำลังสอง
{
return x * x;
}
2. การส่งผ่านอาร์กิวเมนต์ (Argument Passing)
C++ มีวิถีการโยนค่าเข้าไปในฟังก์ชันหลายแบบ ซึ่งส่งผลโดยตรงต่อ Performance และความปลอดภัยของหน่วยความจำ
| รูปแบบ | เครื่องหมาย (Syntax) | คำอธิบาย |
|---|---|---|
| Pass-by-value | T x |
ก๊อปปี้ค่าต้นฉบับไปใช้เป็นตัวแปร Local ภายในฟังก์ชัน แก้ไขยังไงก็ไม่กระทบต้นฉบับข้างนอก |
| Pass-by-reference | T& x |
ชี้เป้าไปที่ตัวแปรต้นฉบับโดยตรง ไม่มีการก๊อปปี้ข้อมูล |
| Pass-by-const-reference | const T& x |
อ้างอิงตัวแปรต้นฉบับเพื่อไม่ต้องเสียเวลาก๊อปปี้ แต่ล็อกห้ามแก้ค่าเด็ดขาด (Read-only) เป็นท่ามาตรฐานของการส่ง Object ขนาดใหญ่ |
ข้อแนะนำสำหรับช่างโค้ด:
- ข้อมูลที่มีขนาดเล็ก (Small Data): พวก
int,doubleหรือพอยน์เตอร์ ให้โยนแบบ Pass-by-value ไปเลย - ข้อมูลขนาดใหญ่ (Large Object): เช่น
std::vectorหรือstd::stringให้รัดเข็มขัดโดยใช้ Pass-by-const-reference เพื่อหนีปัญหา Copy cost ที่ทำให้โปรแกรมอืด - เมื่อต้องการสลับหรือเปลี่ยนค่าตั้งต้น: ถึงจะยอมใช้ Pass-by-reference

3. การคืนค่าจากฟังก์ชัน (Return Values)
ฟังก์ชันจะโยนผลลัพธ์กลับไปให้จุดที่เรียกใช้งานผ่านคำสั่ง return
- Return-by-value: คืนค่าแบบก๊อปปี้ เหมาะกับพวก Type ธรรมดาตัวเล็กๆ
- Move Semantics: ฟีเจอร์ตัวตึงของ Modern C++ สำหรับ Object ใหญ่ๆ (อย่าง Matrix หรือ Vector ตัวบึ้มๆ) คอมไพเลอร์จะใช้ “Move Constructor” สับเปลี่ยน Resource ออกมาไวๆ แทนที่จะเหนื่อยเปล่าไปกับการก๊อปปี้
- Return-type Deduction: ขี้เกียจระบุ Type ก็ยัด
autoเข้าไปให้คอมไพเลอร์เดาให้ แต่เตือนไว้ก่อนว่าใช้เยอะไปเพื่อนร่วมทีมอาจจะอ่าน Interface ไม่ออก - Structured Binding: ท่าสับสำหรับ Return ค่าหลายตัวพร้อมกันโดยจับมัดเข้า struct หรือ
std::tupleฝั่งคนรับสามารถแกะค่าแตกใส่ตัวแปร Local รวดเดียวจบ
4. การโอเวอร์โหลดฟังก์ชัน (Function Overloading)
ฟีเจอร์นี้ยอมให้เราตั้งชื่อฟังก์ชันซ้ำกันได้เป๊ะๆ ขอแค่ประเภทของ Argument หรือจำนวนที่รับเข้าไปต่างกัน ตอนรันคอมไพเลอร์มันจะฉลาดพอที่จะแมปให้ตรงตัว
กฎข้อสำคัญ: ต่อให้ Overload ต่าง Type กัน แต่เนื้องานหรือบริบทตรรกะ (Semantics) ของมันควรเหมือนกันนะ เช่น:
void print(int); // สั่งปริ้นเลขจำนวนเต็ม
void print(double); // สั่งปริ้นเลขทศนิยม
void print(std::string); // สั่งปริ้นสตริง
ข้อควรระวัง: ถ้ามั่วจัดจนคอมไพเลอร์สับสนเลือกไม่ถูกว่าต้องดึงตัวไหนมาใช้ มันจะตอกหน้ากลับมาด้วย Error “Ambiguous Call” ทันที
5. ประเภทพิเศษของฟังก์ชัน (Special Function Types)
5.1 ฟังก์ชัน constexpr
หากแปะป้าย constexpr คลุมฟังก์ชัน คอมไพเลอร์จะจัดการรันการคำนวณนั้นให้เสร็จตั้งแต่ตอน Compile-time เลย (ถ้าค่าที่โยนเข้ามาตอนนั้นเป็นค่าคงที่) ช่วยบูสต์ความเร็ว Runtime ได้มหาศาล
- ข้อแม้คือฟังก์ชันนั้นๆ ต้องคลีน ไม่มีผลข้างเคียงให้ข้อมูลอื่นเปลี่ยน (Side effects)
- ถ้าดันเอาตัวแปรที่ไม่ใช่ค่าคงที่โยนเข้าไป มันก็จะลดร่างกลับไปเป็นฟังก์ชัน Runtime ธรรมดาให้อัตโนมัติ
5.2 ฟังก์ชันสมาชิกของคลาส (Member Functions)
คลาสและสตรัคเจอร์สามารถอมฟังก์ชันไว้ใช้งานภายในตัวเองได้
- Constructors (คอนสตรัคเตอร์): ฟังก์ชันชื่อเดียวกับคลาสคอยเซ็ตอัปค่าสเตตัสเริ่มต้นเสมอยามถูกสร้าง Object
constMember Functions: สังเกตจากconstแปะท้ายวงเล็บพารามิเตอร์ เป็นการสาบานว่า “ฟังก์ชันฉันแค่ขออ่าน ไม่มั่วซั่วแก้ข้อมูลภายใน Object เด็ดขาด”
5.3 ฟังก์ชันเสมือน (Virtual Functions)
เสาหลักของเรื่อง Polymorphism และการทำ OOP (Object-Oriented Programming) หากประกาศว่าเป็น virtual ไว้ในคลาสแม่ คลาสลูกที่สืบทอดไปจะสามารถนำฟังก์ชันนั้นๆ ไป Override เขียนพฤติกรรมเฉพาะตัวใหม่ทับได้
6. ข้อพึงระวังและ Best Practices
- สั้นเข้าไว้ (Keep it short): 1 ฟังก์ชันควรรับผิดชอบแค่งานเดียว ไม่ใช่อัดตรรกะเข้าไปเป็นก้อนใหญ่
- ชื่อสื่อความหมาย: ห้ามตั้งประหลาดๆ ชนิดต้องเปิดพจนานุกรมถึงจะรู้ว่าทำอะไร ควรอ่านปุ๊บรู้ปั๊บ
- เลี่ยง Global Variables แบบสุดชีวิต: ส่งข้อมูลเข้าออกด้วย Parameter กับ Return เถอะ การพึ่งพาและผูกลอจิกกับ Global Variables คือนรกตอนทำ Unit Test และ Debug
- เรียกหา
nullptr: โปรแกรมเมอร์สายพริ้วเขาเลิกใช้0หรือNULLชี้เป้าพอยน์เตอร์ว่างกันแล้ว เขาใช้nullptrกันหมด - เช็ก Null Pointer เสมอ: ก่อนเอาค่าไปไข (Dereferencing) ต้องเช็กเสมอว่าค่าพอยน์เตอร์ในพารามิเตอร์ไม่เท่ากับ
nullptrใช่ไหม เพื่อป้องกัน Crash ยอดฮิต - Static Assertion: ตรวจจับ Error ได้ตั้งแต่ Compile-time ด้วย
static_assertเพื่อดักโครงสร้างให้เป๊ะก่อนเอาไปรันจริง