Skip to content

C06: Hàm trong C++ — Tái sử dụng code

Bạn sẽ học được: Định nghĩa hàm, overload, template, đệ quy
Yêu cầu: Đã học C05 (String)
Thời gian: 45 phút


Tại sao cần hàm?

Analogies: Hàm = Công thức nấu ăn

flowchart LR
    A["Hàm"] -->|"Như công thức"| B["Input = Nguyên liệu"]
    B --> C["Process = Các bước"]
    C --> D["Output = Món ăn"]
Khái niệm Analogies Ví dụ
Hàm Công thức nấu ăn int tong(int a, int b)
Tham số Nguyên liệu a, b
Giá trị trả về Món ăn return a + b
Gọi hàm Nấu theo công thức int kq = tong(3, 5)

Tại sao dùng hàm?

  • Tái sử dụng code: Viết 1 lần, dùng nhiều lần
  • Dễ đọc: Chia code thành các phần nhỏ
  • Dễ debug: Chỉ cần sửa 1 chỗ

Định nghĩa hàm

Cú pháp cơ bản

1
2
3
4
kiểu_trả_về tên_hàm(tham_số) {
    // Code
    return giá_trị;
}

Ví dụ 1: Hàm tính tổng

1
2
3
4
5
6
7
8
9
int tong(int a, int b) {
    return a + b;
}

int main() {
    int kq = tong(3, 5);
    cout << kq << endl;  // 8
    return 0;
}

Ví dụ 2: Hàm không trả về giá trị

1
2
3
4
5
6
7
8
void inHello(string name) {
    cout << "Hello " << name << "!" << endl;
}

int main() {
    inHello("Nam");  // Hello Nam!
    return 0;
}

Ví dụ 3: Hàm không có tham số

1
2
3
4
5
6
7
8
int random100() {
    return rand() % 100;
}

int main() {
    cout << random100() << endl;
    return 0;
}

Truyền tham số

Truyền giá trị (Pass by Value)

void tangMot(int x) {
    x++;  // Chỉ thay đổi bản sao
}

int main() {
    int a = 5;
    tangMot(a);
    cout << a << endl;  // Vẫn là 5!
    return 0;
}

Truyền tham chiếu (Pass by Reference)

void tangMot(int &x) {
    x++;  // Thay đổi biến gốc
}

int main() {
    int a = 5;
    tangMot(a);
    cout << a << endl;  // Là 6!
    return 0;
}

Khi nào dùng &?

  • Không cần &: Chỉ đọc giá trị (không thay đổi)
  • Cần &: Muốn thay đổi giá trị
  • Cần &: Truyền mảng, string lớn (tránh copy)

Overload — Nhiều hàm cùng tên

// Hàm tính tổng 2 số
int tong(int a, int b) {
    return a + b;
}

// Hàm tính tổng 3 số (cùng tên, khác tham số)
int tong(int a, int b, int c) {
    return a + b + c;
}

// Hàm tính tổng 2 số thực
double tong(double a, double b) {
    return a + b;
}

int main() {
    cout << tong(3, 5) << endl;      // 8 (gọi hàm 2 int)
    cout << tong(3, 5, 7) << endl;   // 15 (gọi hàm 3 int)
    cout << tong(3.5, 5.5) << endl;  // 9.0 (gọi hàm 2 double)
    return 0;
}

Hàm với mảng/vector

Truyền mảng

int timMax(int a[], int n) {
    int maxVal = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] > maxVal) maxVal = a[i];
    }
    return maxVal;
}

int main() {
    int a[] = {3, 1, 4, 1, 5, 9};
    cout << timMax(a, 6) << endl;  // 9
    return 0;
}

Truyền vector

int timMax(const vector<int>& a) {
    int maxVal = a[0];
    for (int x : a) {
        if (x > maxVal) maxVal = x;
    }
    return maxVal;
}

int main() {
    vector<int> a = {3, 1, 4, 1, 5, 9};
    cout << timMax(a) << endl;  // 9
    return 0;
}

Đệ quy (Recursion)

Analogies: Đệ quy = Gương chiếu gương

flowchart TD
    A["Hàm f(n)"] --> B{"n == 0?"}
    B -->|"Có"| C["return 0"]
    B -->|"Không"| D["return f(n-1) + 1"]
    D --> A

Ví dụ 1: Tính giai thừa

1
2
3
4
5
6
7
8
9
int giaiThua(int n) {
    if (n <= 1) return 1;      // Base case
    return n * giaiThua(n - 1); // Recursive case
}

int main() {
    cout << giaiThua(5) << endl;  // 120 (5! = 5*4*3*2*1)
    return 0;
}

Ví dụ 2: Fibonacci

1
2
3
4
5
6
7
8
9
int fib(int n) {
    if (n <= 1) return n;           // Base case
    return fib(n - 1) + fib(n - 2); // Recursive case
}

int main() {
    cout << fib(10) << endl;  // 55
    return 0;
}

Đệ quy có thể chậm

// ❌ Chậm: Tính lại nhiều lần
int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);  // O(2^n)
}

// ✅ Nhanh: Dùng memoization
int memo[100];
int fib(int n) {
    if (n <= 1) return n;
    if (memo[n] != -1) return memo[n];
    return memo[n] = fib(n-1) + fib(n-2);  // O(n)
}

Hàm Lambda (C++11)

Cú pháp cơ bản

1
2
3
4
auto ten_hàm = [](tham_số) -> kiểu_trả_về {
    // Code
    return giá_trị;
};

Ví dụ

// Lambda tính tổng
auto tong = [](int a, int b) -> int {
    return a + b;
};

cout << tong(3, 5) << endl;  // 8

// Lambda không có tham số
auto inHello = []() {
    cout << "Hello!" << endl;
};

inHello();  // Hello!

Lambda trong sort

vector<int> a = {5, 2, 8, 1, 9};

// Sắp xếp tăng dần
sort(a.begin(), a.end(), [](int x, int y) {
    return x < y;
});

// Sắp xếp giảm dần
sort(a.begin(), a.end(), [](int x, int y) {
    return x > y;
});

Common Mistakes — Lỗi thường gặp

Lỗi 1: Quên return

1
2
3
4
5
6
7
8
9
// ❌ SAI: Không có return
int tong(int a, int b) {
    int c = a + b;
}  // Lỗi compile!

// ✅ ĐÚNG
int tong(int a, int b) {
    return a + b;
}

Lỗi 2: Truyền mảng không đúng

1
2
3
4
5
6
7
8
9
// ❌ SAI: Không biết kích thước mảng
void inMang(int a[]) {
    // Không biết a có bao nhiêu phần tử!
}

// ✅ ĐÚNG: Truyền thêm kích thước
void inMang(int a[], int n) {
    for (int i = 0; i < n; i++) cout << a[i] << " ";
}

Lỗi 3: Đệ quy không có base case

// ❌ SAI: Không có base case → đệ quy vô hạn
int f(int n) {
    return f(n - 1);  // Stack overflow!
}

// ✅ ĐÚNG
int f(int n) {
    if (n <= 0) return 0;  // Base case
    return f(n - 1);
}

Bài tập thực hành

Bài 1: Hàm kiểm tra số nguyên tố

Viết hàm isPrime(int n) trả về true nếu n là số nguyên tố.

Lời giải
#include <bits/stdc++.h>
using namespace std;

bool isPrime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) return false;
    }
    return true;
}

int main() {
    int n;
    cin >> n;
    if (isPrime(n)) cout << "Nguyen to";
    else cout << "Khong phai";
    return 0;
}

Bài 2: Hàm tính GCD

Viết hàm gcd(int a, int b) trả về ước chung lớn nhất.

Lời giải
#include <bits/stdc++.h>
using namespace std;

int gcd(int a, int b) {
    while (b != 0) {
        int r = a % b;
        a = b;
        b = r;
    }
    return a;
}

int main() {
    int a, b;
    cin >> a >> b;
    cout << gcd(a, b) << endl;
    return 0;
}

Bài 3: Hàm đảo ngược mảng

Viết hàm daoMang(vector<int> &a) đảo ngược mảng tại chỗ.

Lời giải
#include <bits/stdc++.h>
using namespace std;

void daoMang(vector<int> &a) {
    int n = a.size();
    for (int i = 0; i < n / 2; i++) {
        swap(a[i], a[n - 1 - i]);
    }
}

int main() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];
    daoMang(a);
    for (int x : a) cout << x << " ";
    return 0;
}

Tóm tắt bài học

Nội dung Chi tiết
Định nghĩa kiểu_trả_về tên_hàm(tham_số) { ... }
Truyền giá trị void f(int x) — không thay đổi biến gốc
Truyền tham chiếu void f(int &x) — thay đổi biến gốc
Overload Nhiều hàm cùng tên, khác tham số
Đệ quy Hàm gọi chính mình
Lambda auto f = [](int x) { return x * 2; };

Bài viết liên quan


Bài tiếp theo: C07: Template & Fast I/O →


💬 Bình luận