Skip to content

🔧 Functions — Hàm trong C++

Functions là building blocks của mọi chương trình. Hiểu cách truyền tham số quyết định performance và tính đúng đắn của code.

Declaration vs Definition

Declaration (Prototype) — Chỉ khai báo

cpp
// Declaration: Nói "hàm này tồn tại"
// Thường đặt trong header file (.h, .hpp)
int add(int a, int b);                    // OK
double calculateArea(double radius);       // OK
void greet(std::string name);              // OK - tên param là optional
void process(int, int);                    // OK - không cần tên param

Definition — Cài đặt đầy đủ

cpp
// Definition: Cài đặt logic của hàm
// Thường đặt trong source file (.cpp)
int add(int a, int b) {
    return a + b;
}

double calculateArea(double radius) {
    return 3.14159 * radius * radius;
}

Tại sao tách Declaration và Definition?

┌─────────────────────────────────────────────────────────────────┐
│  math.h (Header)                 math.cpp (Source)              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  // Declaration only             #include "math.h"              │
│  int add(int a, int b);                                         │
│  int multiply(int a, int b);     // Definitions                 │
│                                  int add(int a, int b) {        │
│                                      return a + b;              │
│                                  }                              │
│                                                                 │
│                                  int multiply(int a, int b) {   │
│                                      return a * b;              │
│                                  }                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘


          main.cpp chỉ cần #include "math.h"
          → Compile nhanh hơn (không cần recompile math.cpp nếu main thay đổi)

Pass by Value — Truyền bản sao

cpp
#include <iostream>

void doubleValue(int x) {
    x = x * 2;  // Chỉ modify bản sao local
    std::cout << "Inside function: x = " << x << std::endl;
}

int main() {
    int num = 10;
    std::cout << "Before: num = " << num << std::endl;
    
    doubleValue(num);  // Truyền bản sao của num
    
    std::cout << "After: num = " << num << std::endl;
    
    return 0;
}

Output:

Before: num = 10
Inside function: x = 20
After: num = 10        ← num không thay đổi!

Ưu/Nhược điểm Pass by Value

✅ Ưu điểm❌ Nhược điểm
An toàn — không thay đổi originalTốn memory cho copy
Dễ hiểu, predictableChậm với objects lớn
Thread-safe (mỗi thread có bản sao riêng)

Pass by Reference — Truyền tham chiếu

cpp
#include <iostream>

void doubleValue(int& x) {  // Note: int& (reference)
    x = x * 2;  // Modify trực tiếp biến gốc!
}

int main() {
    int num = 10;
    std::cout << "Before: num = " << num << std::endl;
    
    doubleValue(num);  // Truyền reference
    
    std::cout << "After: num = " << num << std::endl;
    
    return 0;
}

Output:

Before: num = 10
After: num = 20        ← num đã thay đổi!

Pass by Const Reference — Read-only, không copy

cpp
#include <iostream>
#include <string>
#include <vector>

// ❌ BAD: Copy toàn bộ vector (có thể hàng triệu elements!)
void printBad(std::vector<int> v) {
    for (const auto& x : v) {
        std::cout << x << " ";
    }
}

// ✅ GOOD: Không copy, chỉ reference, không modify
void printGood(const std::vector<int>& v) {
    for (const auto& x : v) {
        std::cout << x << " ";
    }
    // v.push_back(100);  // ❌ Error: v is const
}

// ✅ GOOD: Với string
void greet(const std::string& name) {
    std::cout << "Hello, " << name << "!" << std::endl;
}

📌 HPN Standard: Khi nào dùng gì?

SituationSyntaxReason
Modify originalT&Thay đổi biến gốc
Read large objectconst T&Tránh copy, không modify
Small primitivesT (by value)Copy rẻ, đơn giản
Optional/nullableT* (pointer)Có thể nullptr

Golden Rule: Mặc định dùng const T& cho objects. Chỉ dùng by-value cho primitives (int, double, char, bool).


Pass by Pointer vs Reference

cpp
#include <iostream>

// Pass by pointer — Có thể null
void processPtr(int* ptr) {
    if (ptr != nullptr) {
        *ptr = 100;
    }
}

// Pass by reference — Guaranteed non-null
void processRef(int& ref) {
    ref = 100;
}

int main() {
    int x = 42;
    
    processPtr(&x);  // Phải dùng &
    std::cout << "After ptr: x = " << x << std::endl;
    
    processRef(x);   // Không cần &, tự động bind
    std::cout << "After ref: x = " << x << std::endl;
    
    // Pointer có thể null
    processPtr(nullptr);  // OK, hàm handle được
    
    // Reference KHÔNG thể null
    // processRef(nullptr);  // ❌ Compiler error
    
    return 0;
}
AspectPointer (T*)Reference (T&)
Nullable?✅ Có thể nullptr❌ Không thể
Rebindable?ptr = &other;❌ Một lần mãi mãi
Syntax*ptr để accessTrực tiếp ref
Use caseOptional paramsRequired params

Default Arguments

cpp
#include <iostream>
#include <string>

// Default arguments: Từ phải sang trái!
void greet(const std::string& name, const std::string& greeting = "Hello") {
    std::cout << greeting << ", " << name << "!" << std::endl;
}

// ❌ Invalid: default argument không ở cuối
// void bad(int a = 10, int b) { ... }

int main() {
    greet("Alice");              // Hello, Alice!
    greet("Bob", "Xin chào");    // Xin chào, Bob!
    
    return 0;
}

Function Overloading

cpp
#include <iostream>
#include <string>

// Cùng tên, khác parameters
int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

std::string add(const std::string& a, const std::string& b) {
    return a + b;
}

int main() {
    std::cout << add(1, 2) << std::endl;           // 3 (int version)
    std::cout << add(1.5, 2.5) << std::endl;       // 4.0 (double version)
    std::cout << add("Hello, ", "World!") << std::endl;  // Hello, World!
    
    return 0;
}

⚠️ Overloading không dựa trên return type

cpp
int getValue();
double getValue();  // ❌ Error: chỉ khác return type

// Compiler không biết gọi cái nào khi:
// auto x = getValue();

Return by Value, Reference, and Pointer

cpp
#include <iostream>
#include <vector>
#include <string>

// Return by value — OK cho small objects
int getAnswer() {
    return 42;
}

// Return by reference — DANGEROUS nếu return local!
// ❌ BAD: Dangling reference
std::string& badGetName() {
    std::string name = "Alice";
    return name;  // name bị destroy sau khi function ends!
}

// ✅ GOOD: Return reference đến parameter hoặc member
std::string& getLonger(std::string& a, std::string& b) {
    return (a.size() > b.size()) ? a : b;
}

// ✅ GOOD: Return by value (Move semantics xử lý efficiency)
std::vector<int> createVector() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    return v;  // Move, không copy (C++11)
}

🐛 Bug Hunt Challenge

🐛 Bug Hunt

Đoạn code sau có bug gì?

cpp
#include <iostream>

void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(x, y);
    std::cout << "x = " << x << ", y = " << y << std::endl;
    return 0;
}
💡 Gợi ý

Hàm swap nhận parameters như thế nào?

🔍 Giải thích & Fix

Output: x = 10, y = 20 — Không swap!

Bug: Parameters ab được pass by value → chỉ swap bản copy.

Fix:

cpp
void swap(int& a, int& b) {  // Pass by reference
    int temp = a;
    a = b;
    b = temp;
}

Hoặc dùng std::swap:

cpp
#include <utility>
std::swap(x, y);

📚 Tổng kết

ConceptSyntaxUse Case
Pass by valuefunc(T x)Small types, cần copy
Pass by referencefunc(T& x)Modify original
Pass by const reffunc(const T& x)Read-only, avoid copy
Pass by pointerfunc(T* x)Nullable, optional
Default argumentsfunc(int x = 10)Optional params
OverloadingSame name, diff paramsPolymorphism

➡️ Tiếp theo

Tiếp theo: Scope & Lifetime — Local, Global, và Static variables.