Skip to content

Bài 12: Tham số hàm (Function Parameters)

📖 Giới thiệu

Tham số hàm (Function Parameters) là cách chúng ta truyền dữ liệu vào hàm để hàm có thể xử lý. Đây là một trong những khái niệm quan trọng nhất khi làm việc với hàm.

Trong cuộc sống, khi bạn nhờ bạn tính toán, bạn sẽ đưa cho họ dữ liệu cần xử lý (như số tiền, tỷ lệ, v.v.). Tương tự, trong lập trình, chúng ta truyền tham số cho hàm để hàm biết cần làm gì với dữ liệu đó.

Có ba cách chính để truyền tham số:

  • Pass by Value: Truyền giá trị (copy dữ liệu)
  • Pass by Reference: Truyền tham chiếu (dùng chung dữ liệu)
  • Pass by Pointer: Truyền qua con trỏ (địa chỉ bộ nhớ)

🔧 Cú pháp

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

cpp
// Hàm nhận giá trị copy
void changeValue(int x) {
    x = 100;  // Chỉ thay đổi bản copy
    cout << "Trong hàm: " << x << endl;
}

int main() {
    int number = 50;
    changeValue(number);  // Truyền copy của number
    cout << "Sau khi gọi hàm: " << number << endl;  // Vẫn là 50
    return 0;
}

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

cpp
// Hàm nhận tham chiếu (alias)
void changeReference(int &x) {
    x = 100;  // Thay đổi biến gốc
    cout << "Trong hàm: " << x << endl;
}

int main() {
    int number = 50;
    changeReference(number);  // Truyền tham chiếu
    cout << "Sau khi gọi hàm: " << number << endl;  // Đã thành 100
    return 0;
}

Pass by Pointer (Truyền con trỏ)

cpp
// Hàm nhận con trỏ
void changePointer(int *x) {
    *x = 100;  // Thay đổi giá trị tại địa chỉ
    cout << "Trong hàm: " << *x << endl;
}

int main() {
    int number = 50;
    changePointer(&number);  // Truyền địa chỉ
    cout << "Sau khi gọi hàm: " << number << endl;  // Đã thành 100
    return 0;
}

🔬 Phân tích & Giải thích chi tiết

1. Pass by Value - An toàn nhưng chậm

Ưu điểm:

  • Hàm không thể làm hỏng dữ liệu gốc
  • An toàn, dễ debug
  • Phù hợp với dữ liệu nhỏ

Nhược điểm:

  • Tốn memory khi copy dữ liệu lớn
  • Chậm với struct/class lớn
  • Không thể trả về nhiều giá trị
cpp
#include <iostream>
#include <string>
using namespace std;

// Ví dụ về pass by value
void processStudent(string name, int age, double score) {
    // Chỉ làm việc với bản copy
    name = "Modified " + name;  // Không ảnh hưởng biến gốc
    age++;
    score += 10;
    
    cout << "Trong hàm:" << endl;
    cout << "Tên: " << name << ", Tuổi: " << age << ", Điểm: " << score << endl;
}

int main() {
    string studentName = "Nguyễn Văn A";
    int studentAge = 20;
    double studentScore = 85.5;
    
    cout << "Trước khi gọi hàm:" << endl;
    cout << "Tên: " << studentName << ", Tuổi: " << studentAge << ", Điểm: " << studentScore << endl;
    
    processStudent(studentName, studentAge, studentScore);
    
    cout << "Sau khi gọi hàm:" << endl;
    cout << "Tên: " << studentName << ", Tuổi: " << studentAge << ", Điểm: " << studentScore << endl;
    
    return 0;
}

2. Pass by Reference - Hiệu quả và linh hoạt

Ưu điểm:

  • Không tốn memory copy
  • Có thể sửa đổi dữ liệu gốc
  • Hiệu quả với dữ liệu lớn
  • Có thể trả về nhiều giá trị

Nhược điểm:

  • Có thể làm hỏng dữ liệu gốc
  • Cần cẩn thận khi sử dụng
cpp
#include <iostream>
#include <string>
using namespace std;

// Hàm hoán đổi hai số
void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

// Hàm tính chu vi và diện tích hình chữ nhật
void calculateRectangle(double length, double width, double &perimeter, double &area) {
    perimeter = 2 * (length + width);
    area = length * width;
}

// Hàm chuẩn hóa tên
void normalizeName(string &name) {
    // Chuyển thành chữ thường
    for (char &c : name) {
        c = tolower(c);
    }
    
    // Viết hoa chữ cái đầu
    if (!name.empty()) {
        name[0] = toupper(name[0]);
    }
    
    // Viết hoa sau dấu cách
    for (int i = 1; i < name.length(); i++) {
        if (name[i-1] == ' ' && name[i] != ' ') {
            name[i] = toupper(name[i]);
        }
    }
}

int main() {
    // Demo hoán đổi
    int x = 10, y = 20;
    cout << "Trước hoán đổi: x=" << x << ", y=" << y << endl;
    swap(x, y);
    cout << "Sau hoán đổi: x=" << x << ", y=" << y << endl;
    
    // Demo tính toán hình chữ nhật
    double length = 5.0, width = 3.0;
    double perimeter, area;
    calculateRectangle(length, width, perimeter, area);
    cout << "Hình chữ nhật " << length << "x" << width << endl;
    cout << "Chu vi: " << perimeter << ", Diện tích: " << area << endl;
    
    // Demo chuẩn hóa tên
    string name = "ngUYen VaN aNh";
    cout << "Tên gốc: " << name << endl;
    normalizeName(name);
    cout << "Tên sau chuẩn hóa: " << name << endl;
    
    return 0;
}

3. Const Reference - An toàn và hiệu quả

cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;

// Hàm đọc dữ liệu (không sửa đổi)
void displayStudent(const string &name, const vector<double> &scores) {
    cout << "Học sinh: " << name << endl;
    cout << "Điểm các môn: ";
    for (double score : scores) {
        cout << score << " ";
    }
    cout << endl;
    
    // name = "Test";  // Lỗi: không thể sửa đổi const reference
}

// Hàm tính điểm trung bình
double calculateAverage(const vector<double> &scores) {
    if (scores.empty()) return 0;
    
    double sum = 0;
    for (double score : scores) {
        sum += score;
    }
    return sum / scores.size();
}

int main() {
    string studentName = "Trần Thị B";
    vector<double> scores = {8.5, 9.0, 7.5, 8.8, 9.2};
    
    displayStudent(studentName, scores);
    double average = calculateAverage(scores);
    cout << "Điểm trung bình: " << average << endl;
    
    return 0;
}

💻 Ví dụ minh họa

Chương trình quản lý tài khoản ngân hàng

cpp
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;

// Cấu trúc tài khoản
struct BankAccount {
    string accountNumber;
    string ownerName;
    double balance;
};

// Hiển thị thông tin tài khoản (const reference)
void displayAccount(const BankAccount &account) {
    cout << "===========================================" << endl;
    cout << "THÔNG TIN TÀI KHOẢN" << endl;
    cout << "===========================================" << endl;
    cout << "Số tài khoản: " << account.accountNumber << endl;
    cout << "Chủ tài khoản: " << account.ownerName << endl;
    cout << "Số dư: " << fixed << setprecision(2) << account.balance << " VND" << endl;
    cout << "===========================================" << endl;
}

// Nạp tiền (reference để thay đổi số dư)
bool deposit(BankAccount &account, double amount) {
    if (amount <= 0) {
        cout << "❌ Lỗi: Số tiền nạp phải lớn hơn 0!" << endl;
        return false;
    }
    
    if (amount > 50000000) {  // Giới hạn 50 triệu
        cout << "❌ Lỗi: Số tiền nạp vượt quá giới hạn (50 triệu VND)!" << endl;
        return false;
    }
    
    account.balance += amount;
    cout << "✅ Nạp tiền thành công!" << endl;
    cout << "Số tiền nạp: " << fixed << setprecision(2) << amount << " VND" << endl;
    cout << "Số dư mới: " << account.balance << " VND" << endl;
    return true;
}

// Rút tiền (reference để thay đổi số dư)
bool withdraw(BankAccount &account, double amount) {
    if (amount <= 0) {
        cout << "❌ Lỗi: Số tiền rút phải lớn hơn 0!" << endl;
        return false;
    }
    
    if (amount > account.balance) {
        cout << "❌ Lỗi: Số dư không đủ!" << endl;
        cout << "Số dư hiện tại: " << fixed << setprecision(2) << account.balance << " VND" << endl;
        return false;
    }
    
    if (amount > 20000000) {  // Giới hạn rút 20 triệu
        cout << "❌ Lỗi: Số tiền rút vượt quá giới hạn (20 triệu VND)!" << endl;
        return false;
    }
    
    account.balance -= amount;
    cout << "✅ Rút tiền thành công!" << endl;
    cout << "Số tiền rút: " << fixed << setprecision(2) << amount << " VND" << endl;
    cout << "Số dư còn lại: " << account.balance << " VND" << endl;
    return true;
}

// Chuyển tiền giữa hai tài khoản
bool transfer(BankAccount &fromAccount, BankAccount &toAccount, double amount) {
    cout << "\n🔄 CHUYỂN TIỀN" << endl;
    cout << "Từ: " << fromAccount.ownerName << " (" << fromAccount.accountNumber << ")" << endl;
    cout << "Đến: " << toAccount.ownerName << " (" << toAccount.accountNumber << ")" << endl;
    cout << "Số tiền: " << fixed << setprecision(2) << amount << " VND" << endl;
    
    if (amount <= 0) {
        cout << "❌ Lỗi: Số tiền chuyển phải lớn hơn 0!" << endl;
        return false;
    }
    
    if (amount > fromAccount.balance) {
        cout << "❌ Lỗi: Số dư tài khoản nguồn không đủ!" << endl;
        return false;
    }
    
    // Thực hiện chuyển tiền
    fromAccount.balance -= amount;
    toAccount.balance += amount;
    
    cout << "✅ Chuyển tiền thành công!" << endl;
    cout << "Số dư mới của " << fromAccount.ownerName << ": " << fromAccount.balance << " VND" << endl;
    cout << "Số dư mới của " << toAccount.ownerName << ": " << toAccount.balance << " VND" << endl;
    return true;
}

// Tính lãi suất (value parameters)
double calculateInterest(double principal, double rate, int months) {
    return principal * (rate / 100) * (months / 12.0);
}

// Menu chính
void showMenu() {
    cout << "\n📱 HỆ THỐNG NGÂN HÀNG" << endl;
    cout << "1. Xem thông tin tài khoản" << endl;
    cout << "2. Nạp tiền" << endl;
    cout << "3. Rút tiền" << endl;
    cout << "4. Chuyển tiền" << endl;
    cout << "5. Tính lãi suất" << endl;
    cout << "0. Thoát" << endl;
    cout << "Chọn chức năng (0-5): ";
}

int main() {
    // Khởi tạo hai tài khoản mẫu
    BankAccount account1 = {
        "123456789",
        "Nguyễn Văn An",
        5000000.0
    };
    
    BankAccount account2 = {
        "987654321", 
        "Trần Thị Bình",
        3000000.0
    };
    
    int choice;
    double amount;
    
    do {
        showMenu();
        cin >> choice;
        
        switch (choice) {
            case 1:
                cout << "\nTÀI KHOẢN 1:" << endl;
                displayAccount(account1);
                cout << "\nTÀI KHOẢN 2:" << endl;
                displayAccount(account2);
                break;
                
            case 2:
                cout << "\nNhập số tiền muốn nạp: ";
                cin >> amount;
                deposit(account1, amount);
                break;
                
            case 3:
                cout << "\nNhập số tiền muốn rút: ";
                cin >> amount;
                withdraw(account1, amount);
                break;
                
            case 4:
                cout << "\nNhập số tiền muốn chuyển: ";
                cin >> amount;
                transfer(account1, account2, amount);
                break;
                
            case 5: {
                double principal, rate;
                int months;
                cout << "\nNhập số tiền gốc: ";
                cin >> principal;
                cout << "Nhập lãi suất (%/năm): ";
                cin >> rate;
                cout << "Nhập số tháng: ";
                cin >> months;
                
                double interest = calculateInterest(principal, rate, months);
                cout << "💰 Số tiền lãi: " << fixed << setprecision(2) << interest << " VND" << endl;
                break;
            }
                
            case 0:
                cout << "Cảm ơn bạn đã sử dụng dịch vụ! 👋" << endl;
                break;
                
            default:
                cout << "❌ Lựa chọn không hợp lệ!" << endl;
        }
        
    } while (choice != 0);
    
    return 0;
}

🏋️ Thực hành

Bài tập 1: Hoán đổi và sắp xếp

Viết chương trình với các hàm:

  • swap(int &a, int &b): Hoán đổi hai số
  • sortThree(int &a, int &b, int &c): Sắp xếp 3 số tăng dần
cpp
#include <iostream>
using namespace std;

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

void sortThree(int &a, int &b, int &c) {
    if (a > b) swap(a, b);
    if (b > c) swap(b, c);
    if (a > b) swap(a, b);
}

int main() {
    int x, y, z;
    cout << "Nhập 3 số: ";
    cin >> x >> y >> z;
    
    cout << "Trước sắp xếp: " << x << " " << y << " " << z << endl;
    sortThree(x, y, z);
    cout << "Sau sắp xếp: " << x << " " << y << " " << z << endl;
    
    return 0;
}

Bài tập 2: Tính toán phương trình bậc 2

Viết hàm solveQuadratic(double a, double b, double c, double &x1, double &x2) trả về số nghiệm.

cpp
#include <iostream>
#include <cmath>
using namespace std;

int solveQuadratic(double a, double b, double c, double &x1, double &x2) {
    if (a == 0) {
        if (b == 0) {
            return (c == 0) ? -1 : 0;  // Vô số nghiệm hoặc vô nghiệm
        }
        x1 = x2 = -c / b;
        return 1;  // Phương trình bậc 1
    }
    
    double delta = b * b - 4 * a * c;
    
    if (delta < 0) {
        return 0;  // Vô nghiệm
    } else if (delta == 0) {
        x1 = x2 = -b / (2 * a);
        return 1;  // Nghiệm kép
    } else {
        x1 = (-b + sqrt(delta)) / (2 * a);
        x2 = (-b - sqrt(delta)) / (2 * a);
        return 2;  // Hai nghiệm phân biệt
    }
}

int main() {
    double a, b, c, x1, x2;
    cout << "Nhập a, b, c: ";
    cin >> a >> b >> c;
    
    int numSolutions = solveQuadratic(a, b, c, x1, x2);
    
    cout << "Phương trình: " << a << "x² + " << b << "x + " << c << " = 0" << endl;
    
    if (numSolutions == 0) {
        cout << "Phương trình vô nghiệm" << endl;
    } else if (numSolutions == 1) {
        cout << "Nghiệm: x = " << x1 << endl;
    } else if (numSolutions == 2) {
        cout << "Nghiệm 1: x₁ = " << x1 << endl;
        cout << "Nghiệm 2: x₂ = " << x2 << endl;
    } else {
        cout << "Phương trình có vô số nghiệm" << endl;
    }
    
    return 0;
}

Bài tập 3: Quản lý điểm sinh viên

Viết chương trình quản lý điểm với struct Student.

cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Student {
    string name;
    vector<double> scores;
    double average;
    char grade;
};

void inputStudent(Student &student) {
    cout << "Nhập tên sinh viên: ";
    cin.ignore();
    getline(cin, student.name);
    
    int numSubjects;
    cout << "Nhập số môn học: ";
    cin >> numSubjects;
    
    student.scores.clear();
    for (int i = 0; i < numSubjects; i++) {
        double score;
        cout << "Điểm môn " << (i + 1) << ": ";
        cin >> score;
        student.scores.push_back(score);
    }
}

void calculateGrade(Student &student) {
    if (student.scores.empty()) {
        student.average = 0;
        student.grade = 'F';
        return;
    }
    
    double sum = 0;
    for (double score : student.scores) {
        sum += score;
    }
    student.average = sum / student.scores.size();
    
    if (student.average >= 9.0) {
        student.grade = 'A';
    } else if (student.average >= 8.0) {
        student.grade = 'B';
    } else if (student.average >= 7.0) {
        student.grade = 'C';
    } else if (student.average >= 5.0) {
        student.grade = 'D';
    } else {
        student.grade = 'F';
    }
}

void displayStudent(const Student &student) {
    cout << "\n=== THÔNG TIN SINH VIÊN ===" << endl;
    cout << "Tên: " << student.name << endl;
    cout << "Điểm các môn: ";
    for (double score : student.scores) {
        cout << score << " ";
    }
    cout << endl;
    cout << "Điểm trung bình: " << student.average << endl;
    cout << "Xếp loại: " << student.grade << endl;
}

int main() {
    Student student;
    
    inputStudent(student);
    calculateGrade(student);
    displayStudent(student);
    
    return 0;
}

📋 Tóm tắt

Kiến thức đã học:

  1. Pass by Value:

    • Truyền bản copy của dữ liệu
    • An toàn nhưng tốn memory và chậm
    • Phù hợp với dữ liệu nhỏ
  2. Pass by Reference:

    • Truyền tham chiếu đến dữ liệu gốc
    • Hiệu quả và có thể thay đổi dữ liệu
    • Sử dụng ký hiệu &
  3. Const Reference:

    • Hiệu quả như reference nhưng không thể sửa đổi
    • An toàn và hiệu quả cho việc đọc dữ liệu
    • Phù hợp với dữ liệu lớn chỉ cần đọc
  4. Pass by Pointer:

    • Truyền địa chỉ bộ nhớ
    • Linh hoạt nhưng dễ gây lỗi
    • Chuẩn bị cho Dynamic Memory

Nguyên tắc sử dụng:

  • Value: Dữ liệu nhỏ, cần an toàn
  • Const Reference: Dữ liệu lớn, chỉ đọc
  • Reference: Cần thay đổi dữ liệu gốc
  • Pointer: Dynamic memory, advanced operations

Chuẩn bị cho bài tiếp theo:

  • Function overloading với parameters khác nhau
  • Default parameters
  • Variable-length argument lists
  • Lambda functions với captures

"Tham số giống như nguyên liệu nấu ăn - chọn đúng cách truyền (value/reference) để có 'món ăn' (kết quả) hoàn hảo!"


Chuyển sang: Function Overloading để học cách tạo nhiều hàm cùng tên với tham số khác nhau!

Khóa học C++ miễn phí