Skip to content

Bài 16: Tham chiếu (References)

📖 Giới thiệu

Tham chiếu (Reference) là một alias (tên khác) cho một biến đã tồn tại. Nếu con trỏ là "địa chỉ nhà", thì tham chiếu là "tên gọi khác của ngôi nhà đó". Khi bạn thay đổi qua tham chiếu, biến gốc cũng thay đổi ngay lập tức.

So sánh dễ hiểu:

  • Biến thường: Như một chiếc hộp có tên
  • Con trỏ: Như tờ giấy ghi địa chỉ của hộp
  • Tham chiếu: Như gắn thêm một nhãn tên khác cho hộp đó

Tham chiếu có những đặc điểm:

  • Phải khởi tạo ngay: Không thể tạo reference rỗng
  • Không thể thay đổi: Một khi reference đã trỏ đến biến, không thể trỏ đến biến khác
  • Không có null reference: Luôn phải trỏ đến một biến hợp lệ
  • Automatic dereferencing: Không cần dấu * như con trỏ

🔧 Cú pháp

Khai báo và sử dụng Reference

cpp
// Cú pháp cơ bản
int x = 10;
int& ref = x;    // ref là tham chiếu đến x

// Sử dụng reference
cout << ref;     // In giá trị của x (10)
ref = 20;        // Thay đổi x thành 20
cout << x;       // In ra 20

// Ví dụ với string
string ten = "Nguyen Van An";
string& nickname = ten;
nickname = "An";
cout << ten;     // In ra "An"

Reference vs Con trỏ

cpp
int a = 5, b = 10;

// Con trỏ
int* ptr = &a;    // ptr trỏ đến a
cout << *ptr;     // Cần dấu * để lấy giá trị (5)
ptr = &b;         // Có thể trỏ đến biến khác
cout << *ptr;     // In ra 10

// Reference  
int& ref = a;     // ref tham chiếu đến a
cout << ref;      // Không cần dấu * (5)
// ref = b;       // LỖI! Đây là gán giá trị b cho a, không phải thay đổi reference
int& ref2 = b;    // Phải tạo reference mới

Reference như tham số hàm

cpp
// Pass by value (truyền giá trị)
void tangGiaTriValue(int x) {
    x++;  // Chỉ thay đổi bản copy
}

// Pass by reference (truyền tham chiếu)
void tangGiaTriRef(int& x) {
    x++;  // Thay đổi biến gốc
}

// Pass by const reference (không cho phép thay đổi)
void inGiaTri(const int& x) {
    cout << x;
    // x++;  // LỖI! Không thể thay đổi
}

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

1. Reference vs Copy

cpp
#include <iostream>
using namespace std;

void demoReferenceCopy() {
    int nguon = 100;
    
    // Copy (sao chép)
    int copy = nguon;
    copy = 200;
    cout << "Sau khi thay đổi copy:" << endl;
    cout << "nguon = " << nguon << endl;     // 100 (không đổi)
    cout << "copy = " << copy << endl;       // 200
    
    // Reference (tham chiếu)
    int& ref = nguon;
    ref = 300;
    cout << "\nSau khi thay đổi reference:" << endl;
    cout << "nguon = " << nguon << endl;     // 300 (đã đổi)
    cout << "ref = " << ref << endl;         // 300
}

2. Reference trong Functions

cpp
// Hoán đổi giá trị bằng reference
void hoanDoi(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// Tính toán và trả về nhiều giá trị
void tinhToanPhanSo(int tuSo, int mauSo, int& thuong, int& du) {
    thuong = tuSo / mauSo;
    du = tuSo % mauSo;
}

// Tăng giá trị và báo cáo
bool tangGiaTri(int& gia_tri, int buoc) {
    gia_tri += buoc;
    return gia_tri <= 100;  // Trả về true nếu chưa vượt giới hạn
}

3. Const Reference

cpp
// Hiệu quả cho objects lớn
void xuLyChuoi(const string& str) {  // Không copy string, chỉ tham chiếu
    cout << "Độ dài: " << str.length() << endl;
    cout << "Ký tự đầu: " << str[0] << endl;
    // str[0] = 'X';  // LỖI! const reference không cho phép thay đổi
}

// So sánh hiệu suất
void hamChamValue(string str) {      // Copy toàn bộ string (chậm)
    cout << str.length() << endl;
}

void hamNhanhRef(const string& str) { // Chỉ tham chiếu (nhanh)
    cout << str.length() << endl;
}

4. Reference trong Class/Struct

cpp
struct HocSinh {
    string ten;
    double diem;
    
    // Constructor với reference parameters
    HocSinh(const string& t, double d) : ten(t), diem(d) {}
    
    // Method trả về reference
    string& layTen() {
        return ten;  // Cho phép thay đổi tên từ bên ngoài
    }
    
    // Method trả về const reference
    const double& layDiem() const {
        return diem;  // Chỉ đọc, không thay đổi
    }
};

💻 Ví dụ minh họa

Ví dụ 1: Hệ thống quản lý điểm số

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

// Struct học sinh
struct HocSinh {
    string ten;
    double diemToan;
    double diemVan;
    double diemAnh;
};

// Tính điểm trung bình thông qua reference
void tinhDiemTrungBinh(const HocSinh& hs, double& diemTB) {
    diemTB = (hs.diemToan + hs.diemVan + hs.diemAnh) / 3.0;
}

// Cập nhật điểm học sinh
void capNhatDiem(HocSinh& hs, const string& mon, double diemMoi) {
    if (mon == "Toan") {
        hs.diemToan = diemMoi;
    } else if (mon == "Van") {
        hs.diemVan = diemMoi;
    } else if (mon == "Anh") {
        hs.diemAnh = diemMoi;
    }
    cout << "Đã cập nhật điểm " << mon << " của " << hs.ten << " thành " << diemMoi << endl;
}

// Tìm học sinh có điểm cao nhất
const HocSinh& timHocSinhXuatSac(const vector<HocSinh>& danhSach) {
    if (danhSach.empty()) {
        static HocSinh empty = {"", 0, 0, 0};
        return empty;
    }
    
    const HocSinh* xuatSacNhat = &danhSach[0];
    double diemCaoNhat = 0;
    
    for (const HocSinh& hs : danhSach) {
        double diemTB;
        tinhDiemTrungBinh(hs, diemTB);
        
        if (diemTB > diemCaoNhat) {
            diemCaoNhat = diemTB;
            xuatSacNhat = &hs;
        }
    }
    
    return *xuatSacNhat;
}

// In thông tin học sinh
void inThongTin(const HocSinh& hs) {
    double diemTB;
    tinhDiemTrungBinh(hs, diemTB);
    
    cout << "\n--- THÔNG TIN HỌC SINH ---" << endl;
    cout << "Tên: " << hs.ten << endl;
    cout << "Điểm Toán: " << hs.diemToan << endl;
    cout << "Điểm Văn: " << hs.diemVan << endl;
    cout << "Điểm Anh: " << hs.diemAnh << endl;
    cout << "Điểm TB: " << diemTB << endl;
}

int main() {
    // Tạo danh sách học sinh
    vector<HocSinh> lop = {
        {"Nguyen Van An", 8.5, 7.0, 9.0},
        {"Tran Thi Binh", 9.0, 8.5, 8.0},
        {"Le Van Cuong", 7.5, 6.0, 7.0}
    };
    
    cout << "DANH SÁCH HỌC SINH BAN ĐẦU:" << endl;
    for (const HocSinh& hs : lop) {
        inThongTin(hs);
    }
    
    // Cập nhật điểm cho một học sinh
    cout << "\n=== CẬP NHẬT ĐIỂM ===" << endl;
    capNhatDiem(lop[0], "Toan", 9.5);  // Truyền reference để thay đổi
    
    // In lại thông tin sau khi cập nhật
    cout << "\nTHÔNG TIN SAU KHI CẬP NHẬT:" << endl;
    inThongTin(lop[0]);
    
    // Tìm học sinh xuất sắc nhất
    cout << "\n=== HỌC SINH XUẤT SẮC NHẤT ===" << endl;
    const HocSinh& xuatSac = timHocSinhXuatSac(lop);
    inThongTin(xuatSac);
    
    return 0;
}

Kết quả:

DANH SÁCH HỌC SINH BAN ĐẦU:

--- THÔNG TIN HỌC SINH ---
Tên: Nguyen Van An
Điểm Toán: 8.5
Điểm Văn: 7
Điểm Anh: 9
Điểm TB: 8.16667

=== CẬP NHẬT ĐIỂM ===
Đã cập nhật điểm Toan của Nguyen Van An thành 9.5

THÔNG TIN SAU KHI CẬP NHẬT:

--- THÔNG TIN HỌC SINH ---
Tên: Nguyen Van An  
Điểm Toán: 9.5
Điểm Văn: 7
Điểm Anh: 9
Điểm TB: 8.5

Ví dụ 2: Hệ thống banking đơn giản

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

// Struct tài khoản ngân hàng
struct TaiKhoan {
    string soTK;
    string tenChuTK;
    double soDu;
    bool trangThai; // true = hoạt động, false = khóa
};

// Tìm tài khoản theo số TK
TaiKhoan* timTaiKhoan(vector<TaiKhoan>& danhSach, const string& soTK) {
    for (TaiKhoan& tk : danhSach) {
        if (tk.soTK == soTK) {
            return &tk;  // Trả về pointer để có thể thay đổi
        }
    }
    return nullptr;  // Không tìm thấy
}

// Nạp tiền vào tài khoản
bool napTien(TaiKhoan& tk, double soTien) {
    if (!tk.trangThai) {
        cout << "Tài khoản đã bị khóa!" << endl;
        return false;
    }
    
    if (soTien <= 0) {
        cout << "Số tiền phải lớn hơn 0!" << endl;
        return false;
    }
    
    tk.soDu += soTien;
    cout << "Nạp tiền thành công! Số dư mới: " << tk.soDu << " VND" << endl;
    return true;
}

// Rút tiền từ tài khoản
bool rutTien(TaiKhoan& tk, double soTien) {
    if (!tk.trangThai) {
        cout << "Tài khoản đã bị khóa!" << endl;
        return false;
    }
    
    if (soTien <= 0) {
        cout << "Số tiền phải lớn hơn 0!" << endl;
        return false;
    }
    
    if (tk.soDu < soTien) {
        cout << "Số dư không đủ! Số dư hiện tại: " << tk.soDu << " VND" << endl;
        return false;
    }
    
    tk.soDu -= soTien;
    cout << "Rút tiền thành công! Số dư còn lại: " << tk.soDu << " VND" << endl;
    return true;
}

// Chuyển tiền giữa hai tài khoản
bool chuyenTien(TaiKhoan& tkNguon, TaiKhoan& tkDich, double soTien) {
    cout << "\n--- CHUYỂN TIỀN ---" << endl;
    cout << "Từ TK: " << tkNguon.soTK << " -> Đến TK: " << tkDich.soTK << endl;
    cout << "Số tiền: " << soTien << " VND" << endl;
    
    // Kiểm tra tài khoản nguồn
    if (!rutTien(tkNguon, soTien)) {
        cout << "Chuyển tiền thất bại!" << endl;
        return false;
    }
    
    // Nạp tiền vào tài khoản đích
    if (!napTien(tkDich, soTien)) {
        // Hoàn lại tiền nếu nạp thất bại
        tkNguon.soDu += soTien;
        cout << "Chuyển tiền thất bại! Đã hoàn lại tiền." << endl;
        return false;
    }
    
    cout << "Chuyển tiền thành công!" << endl;
    return true;
}

// In thông tin tài khoản
void inThongTinTK(const TaiKhoan& tk) {
    cout << "\n--- THÔNG TIN TÀI KHOẢN ---" << endl;
    cout << "Số TK: " << tk.soTK << endl;
    cout << "Chủ TK: " << tk.tenChuTK << endl;
    cout << "Số dư: " << tk.soDu << " VND" << endl;
    cout << "Trạng thái: " << (tk.trangThai ? "Hoạt động" : "Khóa") << endl;
}

int main() {
    // Tạo danh sách tài khoản
    vector<TaiKhoan> nganHang = {
        {"001", "Nguyen Van An", 1000000, true},
        {"002", "Tran Thi Binh", 500000, true},
        {"003", "Le Van Cuong", 200000, false}  // Tài khoản bị khóa
    };
    
    cout << "HỆ THỐNG NGÂN HÀNG" << endl;
    
    // Hiển thị thông tin ban đầu
    cout << "\nTHÔNG TIN BAN ĐẦU:" << endl;
    for (const TaiKhoan& tk : nganHang) {
        inThongTinTK(tk);
    }
    
    // Thực hiện các giao dịch
    cout << "\n=== THỰC HIỆN GIAO DỊCH ===" << endl;
    
    // Tìm tài khoản và thực hiện giao dịch
    TaiKhoan* tk1 = timTaiKhoan(nganHang, "001");
    TaiKhoan* tk2 = timTaiKhoan(nganHang, "002");
    
    if (tk1 && tk2) {
        // Rút tiền từ TK 001
        cout << "\n1. Rút tiền từ TK 001:" << endl;
        rutTien(*tk1, 100000);
        
        // Nạp tiền vào TK 002
        cout << "\n2. Nạp tiền vào TK 002:" << endl;
        napTien(*tk2, 50000);
        
        // Chuyển tiền từ TK 001 sang TK 002
        cout << "\n3. Chuyển tiền 001 -> 002:" << endl;
        chuyenTien(*tk1, *tk2, 200000);
    }
    
    // Hiển thị thông tin sau giao dịch
    cout << "\n=== THÔNG TIN SAU GIAO DỊCH ===" << endl;
    for (const TaiKhoan& tk : nganHang) {
        inThongTinTK(tk);
    }
    
    return 0;
}

Ví dụ 3: Game RPG đơn giản

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

// Struct nhân vật
struct NhanVat {
    string ten;
    int hp;
    int maxHp;
    int attack;
    int defense;
    int level;
    int exp;
};

// Khởi tạo nhân vật
void khoiTaoNhanVat(NhanVat& nv, const string& ten) {
    nv.ten = ten;
    nv.level = 1;
    nv.maxHp = 100;
    nv.hp = nv.maxHp;
    nv.attack = 20;
    nv.defense = 10;
    nv.exp = 0;
}

// Hồi máu cho nhân vật
void hoiMau(NhanVat& nv, int soLuong) {
    nv.hp += soLuong;
    if (nv.hp > nv.maxHp) {
        nv.hp = nv.maxHp;
    }
    cout << nv.ten << " hồi " << soLuong << " HP. HP hiện tại: " << nv.hp << "/" << nv.maxHp << endl;
}

// Nhận sát thương
void nhanSatThuong(NhanVat& nv, int damage) {
    int satThuongThucTe = damage - nv.defense;
    if (satThuongThucTe < 0) satThuongThucTe = 0;
    
    nv.hp -= satThuongThucTe;
    if (nv.hp < 0) nv.hp = 0;
    
    cout << nv.ten << " nhận " << satThuongThucTe << " damage. HP còn lại: " << nv.hp << "/" << nv.maxHp << endl;
}

// Tăng level
void tangLevel(NhanVat& nv) {
    nv.level++;
    nv.maxHp += 20;
    nv.hp = nv.maxHp;  // Full HP khi level up
    nv.attack += 5;
    nv.defense += 3;
    nv.exp = 0;
    
    cout << "🎉 " << nv.ten << " lên level " << nv.level << "!" << endl;
    cout << "Tăng: +20 HP, +5 Attack, +3 Defense" << endl;
}

// Nhận kinh nghiệm
void nhanExp(NhanVat& nv, int expGain) {
    nv.exp += expGain;
    cout << nv.ten << " nhận " << expGain << " EXP" << endl;
    
    // Kiểm tra level up (cần 100 EXP cho mỗi level)
    while (nv.exp >= 100) {
        nv.exp -= 100;
        tangLevel(nv);
    }
}

// Đánh nhau giữa hai nhân vật
void danhNhau(NhanVat& nv1, NhanVat& nv2) {
    cout << "\n⚔️ " << nv1.ten << " vs " << nv2.ten << " ⚔️" << endl;
    
    int luot = 1;
    while (nv1.hp > 0 && nv2.hp > 0) {
        cout << "\n--- Lượt " << luot << " ---" << endl;
        
        // NV1 tấn công NV2
        cout << nv1.ten << " tấn công " << nv2.ten << "!" << endl;
        nhanSatThuong(nv2, nv1.attack);
        
        if (nv2.hp <= 0) {
            cout << "🏆 " << nv1.ten << " thắng!" << endl;
            nhanExp(nv1, 50);
            break;
        }
        
        // NV2 tấn công NV1
        cout << nv2.ten << " tấn công " << nv1.ten << "!" << endl;
        nhanSatThuong(nv1, nv2.attack);
        
        if (nv1.hp <= 0) {
            cout << "🏆 " << nv2.ten << " thắng!" << endl;
            nhanExp(nv2, 50);
            break;
        }
        
        luot++;
    }
}

// In thông tin nhân vật
void inThongTin(const NhanVat& nv) {
    cout << "\n=== " << nv.ten << " ===" << endl;
    cout << "Level: " << nv.level << endl;
    cout << "HP: " << nv.hp << "/" << nv.maxHp << endl;
    cout << "Attack: " << nv.attack << endl;
    cout << "Defense: " << nv.defense << endl;
    cout << "EXP: " << nv.exp << "/100" << endl;
}

int main() {
    cout << "🎮 GAME RPG ĐƠN GIẢN 🎮" << endl;
    
    // Tạo hai nhân vật
    NhanVat anh_hung, quai_vat;
    
    khoiTaoNhanVat(anh_hung, "Anh Hung");
    khoiTaoNhanVat(quai_vat, "Quai Vat");
    
    // Tăng stats cho quái vật
    quai_vat.attack = 25;
    quai_vat.defense = 5;
    
    // Hiển thị thông tin ban đầu
    cout << "\nTHÔNG TIN NHÂN VẬT:" << endl;
    inThongTin(anh_hung);
    inThongTin(quai_vat);
    
    // Bắt đầu đánh nhau
    danhNhau(anh_hung, quai_vat);
    
    // Hồi máu cho người thắng
    if (anh_hung.hp > 0) {
        cout << "\n" << anh_hung.ten << " sử dụng potion hồi máu!" << endl;
        hoiMau(anh_hung, 30);
    }
    
    // Hiển thị thông tin cuối game
    cout << "\n=== KẾT QUÁ CUỐI GAME ===" << endl;
    inThongTin(anh_hung);
    inThongTin(quai_vat);
    
    return 0;
}

🏋️ Thực hành

Bài tập 1: Hoán đổi và tính toán

cpp
// Viết các hàm sử dụng reference:
void hoanDoi(int& a, int& b);
void tinhToanHinhTron(double banKinh, double& chuVi, double& dienTich);
void timMinMax(const vector<int>& arr, int& min, int& max);

Bài tập 2: Quản lý sinh viên

Tạo hệ thống quản lý sinh viên với:

  • Struct SinhVien
  • Hàm cập nhật điểm (dùng reference)
  • Hàm tìm sinh viên xuất sắc (return const reference)
  • Hàm sắp xếp danh sách (dùng reference parameter)

Bài tập 3: Game quản lý tài nguyên

Tạo game với:

  • Struct Character có hp, mana, gold
  • Hàm chiến đấu (thay đổi stats qua reference)
  • Hàm mua bán (reference parameters)
  • Hàm level up (reference để cập nhật)

📋 Tóm tắt

Reference vs Con trỏ:

Đặc điểmReferenceCon trỏ
Khởi tạoBắt buộc ngayCó thể null
Thay đổi đíchKhông thểCó thể
Cú phápĐơn giảnCần dấu *
An toànCao hơnDễ lỗi hơn
OverheadKhôngCó thể có

Khi nào dùng Reference:

  1. Function parameters: Tránh copy, cho phép thay đổi
  2. Return values: Trả về object lớn hiệu quả
  3. Const reference: Truy cập read-only hiệu quả
  4. Alias: Tạo tên khác cho biến phức tạp

Best Practices:

  • Dùng const reference cho parameters chỉ đọc
  • Dùng reference cho parameters cần thay đổi
  • Tránh return reference cho local variables
  • Ưu tiên reference hơn con trỏ khi có thể

Tiếp theo:

Bài tiếp theo sẽ học về Cấp phát bộ nhớ động (Dynamic Memory) - quản lý memory với new/delete và smart pointers.

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