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ớiReference 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.5Ví 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ểm | Reference | Con trỏ |
|---|---|---|
| Khởi tạo | Bắt buộc ngay | Có thể null |
| Thay đổi đích | Không thể | Có thể |
| Cú pháp | Đơn giản | Cần dấu * |
| An toàn | Cao hơn | Dễ lỗi hơn |
| Overhead | Không | Có thể có |
Khi nào dùng Reference:
- Function parameters: Tránh copy, cho phép thay đổi
- Return values: Trả về object lớn hiệu quả
- Const reference: Truy cập read-only hiệu quả
- Alias: Tạo tên khác cho biến phức tạp
Best Practices:
- Dùng
const referencecho 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.