Bài 13: Phạm vi biến (Variable Scope)
📖 Giới thiệu
Phạm vi biến (Variable Scope) là vùng trong chương trình nơi một biến có thể được truy cập và sử dụng. Hiểu về scope giúp chúng ta biết khi nào biến "sống", khi nào biến "chết", và tại sao đôi khi không thể truy cập được biến.
Giống như trong gia đình, mỗi phòng có những đồ vật riêng. Đồ trong phòng ngủ không thể dùng từ phòng khách, nhưng đồ ở phòng khách thì mọi người đều dùng được.
Trong C++, có các loại scope chính:
- Global Scope: Biến toàn cục - mọi nơi đều dùng được
- Local Scope: Biến cục bộ - chỉ dùng trong hàm/block
- Block Scope: Biến trong khối {} - chỉ dùng trong khối đó
- Function Parameter Scope: Tham số hàm - chỉ dùng trong hàm
🔧 Cú pháp
Global Scope (Phạm vi toàn cục)
cpp
#include <iostream>
using namespace std;
// Biến toàn cục - khai báo ngoài tất cả functions
int bienToanCuc = 100;
string tenUngDung = "Ứng dụng của tôi";
void hamA() {
cout << "Trong hàm A: " << bienToanCuc << endl; // OK
}
void hamB() {
bienToanCuc = 200; // OK - có thể thay đổi
cout << "Trong hàm B: " << bienToanCuc << endl;
}
int main() {
cout << "Trong main: " << bienToanCuc << endl; // OK
hamA();
hamB();
return 0;
}Local Scope (Phạm vi cục bộ)
cpp
void demSo() {
int count = 0; // Biến cục bộ - chỉ tồn tại trong hàm này
string thongBao = "Đếm số";
for (int i = 1; i <= 5; i++) {
count += i;
cout << "Lần " << i << ": " << count << endl;
}
// count và thongBao "chết" khi hàm kết thúc
}
int main() {
demSo();
// cout << count; // LỖI! count không tồn tại ở đây
return 0;
}Block Scope (Phạm vi khối)
cpp
int main() {
int x = 10;
if (x > 5) {
int y = 20; // y chỉ tồn tại trong if block
cout << x << " " << y << endl; // OK
}
// cout << y; // LỖI! y không tồn tại ngoài if block
for (int i = 0; i < 3; i++) {
int z = i * 2; // z chỉ tồn tại trong for loop
cout << z << " ";
}
// cout << i; // LỖI! i không tồn tại ngoài for loop
return 0;
}🔬 Phân tích & Giải thích
1. Hierarchy của Scope
cpp
#include <iostream>
using namespace std;
int bienToanCuc = 1; // Global scope
void hamDemo() {
int bienCucBo = 2; // Function scope
cout << "Trong hàm:" << endl;
cout << "- Biến toàn cục: " << bienToanCuc << endl;
cout << "- Biến cục bộ: " << bienCucBo << endl;
if (true) {
int bienBlock = 3; // Block scope
cout << "Trong if block:" << endl;
cout << "- Biến toàn cục: " << bienToanCuc << endl;
cout << "- Biến cục bộ: " << bienCucBo << endl;
cout << "- Biến block: " << bienBlock << endl;
}
// bienBlock không còn tồn tại ở đây
}
int main() {
cout << "Trong main:" << endl;
cout << "- Biến toàn cục: " << bienToanCuc << endl;
// cout << bienCucBo; // LỖI! Không thể truy cập
hamDemo();
return 0;
}2. Variable Shadowing (Che khuất biến)
cpp
#include <iostream>
using namespace std;
int x = 100; // Global x
void demoBienCheKhuat() {
int x = 200; // Local x che khuất global x
cout << "x trong hàm: " << x << endl; // In ra 200
cout << "x toàn cục: " << ::x << endl; // In ra 100 (dùng ::)
if (true) {
int x = 300; // Block x che khuất local x
cout << "x trong block: " << x << endl; // In ra 300
}
cout << "x sau block: " << x << endl; // In ra 200
}
int main() {
cout << "x trong main: " << x << endl; // In ra 100
demoBienCheKhuat();
return 0;
}3. Static Variables
cpp
#include <iostream>
using namespace std;
void demBienStatic() {
static int dem = 0; // Static variable - giữ giá trị giữa các lần gọi
int temp = 0; // Local variable - reset mỗi lần gọi
dem++;
temp++;
cout << "dem = " << dem << ", temp = " << temp << endl;
}
int main() {
cout << "Gọi hàm lần 1: ";
demBienStatic(); // dem = 1, temp = 1
cout << "Gọi hàm lần 2: ";
demBienStatic(); // dem = 2, temp = 1
cout << "Gọi hàm lần 3: ";
demBienStatic(); // dem = 3, temp = 1
return 0;
}4. Function Parameters Scope
cpp
void tinhToan(int a, int b) { // a, b có function scope
int tong = a + b; // tong có function scope
cout << "Tổng: " << tong << endl;
// Có thể thay đổi tham số
a = 999;
cout << "a thay đổi: " << a << endl;
}
int main() {
int x = 10, y = 20;
cout << "Trước khi gọi hàm: x = " << x << endl;
tinhToan(x, y);
cout << "Sau khi gọi hàm: x = " << x << endl; // x vẫn = 10 (pass by value)
// cout << a; // LỖI! a không tồn tại ở đây
return 0;
}💻 Ví dụ minh họa
Ví dụ 1: Quản lý điểm số học sinh
cpp
#include <iostream>
#include <string>
using namespace std;
// Biến toàn cục
int soLuongHocSinh = 0;
double tongDiemLop = 0.0;
// Hàm thêm học sinh
void themHocSinh(string ten, double diem) {
// Biến cục bộ
bool hocSinhHopLe = true;
// Kiểm tra tính hợp lệ
if (diem < 0 || diem > 10) {
hocSinhHopLe = false;
cout << "Điểm không hợp lệ cho " << ten << endl;
return;
}
if (hocSinhHopLe) {
soLuongHocSinh++; // Cập nhật biến toàn cục
tongDiemLop += diem; // Cập nhật biến toàn cục
cout << "Đã thêm học sinh: " << ten << " (Điểm: " << diem << ")" << endl;
}
}
// Hàm tính điểm trung bình lớp
double tinhDiemTrungBinhLop() {
if (soLuongHocSinh == 0) {
return 0.0;
}
return tongDiemLop / soLuongHocSinh;
}
// Hàm in thống kê
void inThongKe() {
cout << "\n=== THỐNG KÊ LỚP HỌC ===" << endl;
cout << "Số lượng học sinh: " << soLuongHocSinh << endl;
cout << "Tổng điểm lớp: " << tongDiemLop << endl;
cout << "Điểm trung bình: " << tinhDiemTrungBinhLop() << endl;
}
int main() {
cout << "CHƯƠNG TRÌNH QUẢN LÝ ĐIỂM SỐ" << endl;
// Thêm một số học sinh
themHocSinh("Nguyễn Văn An", 8.5);
themHocSinh("Trần Thị Bình", 9.0);
themHocSinh("Lê Văn Cường", 7.5);
themHocSinh("Phạm Thị Dung", 15.0); // Điểm không hợp lệ
inThongKe();
return 0;
}Kết quả:
CHƯƠNG TRÌNH QUẢN LÝ ĐIỂM SỐ
Đã thêm học sinh: Nguyễn Văn An (Điểm: 8.5)
Đã thêm học sinh: Trần Thị Bình (Điểm: 9)
Đã thêm học sinh: Lê Văn Cường (Điểm: 7.5)
Điểm không hợp lệ cho Phạm Thị Dung
=== THỐNG KÊ LỚP HỌC ===
Số lượng học sinh: 3
Tổng điểm lớp: 25
Điểm trung bình: 8.33333Ví dụ 2: Hệ thống đếm và thống kê
cpp
#include <iostream>
using namespace std;
// Biến toàn cục để theo dõi
int tongSoLanGoi = 0;
// Hàm đếm với static variable
int layMaSoTuDong() {
static int maSoHienTai = 1000; // Khởi tạo một lần duy nhất
maSoHienTai++;
tongSoLanGoi++;
return maSoHienTai;
}
// Hàm tạo ID người dùng
string taoIDNguoiDung(string ten) {
int maSo = layMaSoTuDong();
// Biến cục bộ để xử lý tên
string tenRutGon = "";
// Lấy 3 ký tự đầu của tên (nếu có)
for (int i = 0; i < 3 && i < ten.length(); i++) {
if (ten[i] != ' ') { // Bỏ qua dấu cách
tenRutGon += ten[i];
}
}
return tenRutGon + to_string(maSo);
}
// Hàm demo scope trong loop
void demoScopeLoop() {
cout << "\n=== DEMO SCOPE TRONG LOOP ===" << endl;
for (int i = 1; i <= 3; i++) {
int binhPhuong = i * i; // Biến block scope
static int tongBinhPhuong = 0; // Static - giữ giá trị
tongBinhPhuong += binhPhuong;
cout << "Lần " << i << ": " << i << "^2 = " << binhPhuong;
cout << ", Tổng = " << tongBinhPhuong << endl;
}
// binhPhuong không tồn tại ở đây
// i cũng không tồn tại ở đây
}
int main() {
cout << "HỆ THỐNG TẠO ID VÀ THỐNG KÊ" << endl;
// Tạo ID cho một số người dùng
cout << "\nTạo ID người dùng:" << endl;
cout << "Nguyễn Văn An -> " << taoIDNguoiDung("Nguyễn Văn An") << endl;
cout << "Trần Thị Bình -> " << taoIDNguoiDung("Trần Thị Bình") << endl;
cout << "Lê Văn Cường -> " << taoIDNguoiDung("Lê Văn Cường") << endl;
cout << "\nTổng số lần gọi hàm: " << tongSoLanGoi << endl;
// Demo scope trong loop
demoScopeLoop();
// Tạo thêm ID
cout << "\nTạo thêm ID:" << endl;
cout << "Phạm Thị Dung -> " << taoIDNguoiDung("Phạm Thị Dung") << endl;
cout << "Tổng số lần gọi hàm: " << tongSoLanGoi << endl;
return 0;
}Kết quả:
HỆ THỐNG TẠO ID VÀ THỐNG KÊ
Tạo ID người dùng:
Nguyễn Văn An -> Ngu1001
Trần Thị Bình -> Tra1002
Lê Văn Cường -> LeV1003
Tổng số lần gọi hàm: 3
=== DEMO SCOPE TRONG LOOP ===
Lần 1: 1^2 = 1, Tổng = 1
Lần 2: 2^2 = 4, Tổng = 5
Lần 3: 3^2 = 9, Tổng = 14
Tạo thêm ID:
Phạm Thị Dung -> Pha1004
Tổng số lần gọi hàm: 4Ví dụ 3: Game đơn giản với scope
cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
// Biến game toàn cục
int diemSo = 0;
int soLuotChoi = 0;
// Hàm tạo số ngẫu nhiên
int taoSoNgauNhien(int min, int max) {
static bool daKhoiTao = false;
if (!daKhoiTao) {
srand(time(0)); // Khởi tạo seed một lần
daKhoiTao = true;
}
return min + rand() % (max - min + 1);
}
// Hàm chơi một lượt
bool choi1Luot() {
soLuotChoi++;
// Biến cục bộ cho lượt chơi này
int soMay = taoSoNgauNhien(1, 10);
int soDoan;
int soLanDoan = 0;
const int maxLanDoan = 3;
cout << "\n--- LƯỢT " << soLuotChoi << " ---" << endl;
cout << "Đoán số từ 1 đến 10 (có " << maxLanDoan << " lần đoán)" << endl;
while (soLanDoan < maxLanDoan) {
soLanDoan++;
cout << "Lần đoán " << soLanDoan << ": ";
cin >> soDoan;
if (soDoan == soMay) {
int diemThuong = maxLanDoan - soLanDoan + 1; // Điểm block scope
diemSo += diemThuong;
cout << "🎉 ĐÚNG! Bạn được " << diemThuong << " điểm!" << endl;
return true;
} else if (soDoan < soMay) {
cout << "📈 Số cần đoán lớn hơn!" << endl;
} else {
cout << "📉 Số cần đoán nhỏ hơn!" << endl;
}
}
cout << "😢 Hết lượt! Số đúng là: " << soMay << endl;
return false;
}
// Hàm in thống kê game
void inThongKeGame() {
cout << "\n=== THỐNG KÊ GAME ===" << endl;
cout << "Số lượt đã chơi: " << soLuotChoi << endl;
cout << "Tổng điểm: " << diemSo << endl;
if (soLuotChoi > 0) {
double diemTrungBinh = (double)diemSo / soLuotChoi;
cout << "Điểm trung bình: " << diemTrungBinh << endl;
}
}
int main() {
cout << "🎮 GAME ĐOÁN SỐ 🎮" << endl;
char tiepTuc;
do {
choi1Luot();
inThongKeGame();
cout << "\nChơi tiếp? (y/n): ";
cin >> tiepTuc;
} while (tiepTuc == 'y' || tiepTuc == 'Y');
cout << "\nCảm ơn bạn đã chơi!" << endl;
return 0;
}🏋️ Thực hành
Bài tập 1: Phân tích scope
Xác định scope của từng biến trong đoạn code sau:
cpp
int x = 10; // Scope: ?
void ham1() {
int y = 20; // Scope: ?
for (int i = 0; i < 5; i++) {
int z = i * 2; // Scope: ?
static int count = 0; // Scope: ?
count++;
}
}
int main() {
int a = 5; // Scope: ?
if (a > 0) {
int b = 15; // Scope: ?
}
return 0;
}Bài tập 2: Xây dựng hệ thống đếm
Tạo một hệ thống đếm với các yêu cầu:
- Biến toàn cục đếm tổng số lần gọi hàm
- Hàm có static variable đếm riêng cho hàm đó
- In ra thống kê sau mỗi lần gọi
Bài tập 3: Game quản lý tài nguyên
Tạo game đơn giản với:
- Biến toàn cục: hp (máu), gold (vàng), level (cấp độ)
- Hàm đánh quái: giảm hp, tăng gold
- Hàm mua đồ: tiêu gold, tăng hp
- Hàm level up: tiêu gold, tăng level
📋 Tóm tắt
Các loại Scope chính:
Global Scope:
- Khai báo ngoài tất cả functions
- Truy cập được từ mọi nơi
- Tồn tại suốt chương trình
Function Scope:
- Khai báo trong function
- Chỉ truy cập được trong function đó
- Tồn tại khi function chạy
Block Scope:
- Khai báo trong khối {}
- Chỉ truy cập được trong khối đó
- Tồn tại khi khối thực thi
Parameter Scope:
- Tham số của function
- Như biến local của function
Quy tắc quan trọng:
- Variable Shadowing: Biến trong scope nhỏ che khuất biến cùng tên trong scope lớn
- Static Variables: Giữ giá trị giữa các lần gọi hàm
- Lifetime vs Scope: Thời gian sống khác với vùng truy cập
Tiếp theo:
Bài tiếp theo sẽ học về Đệ quy (Recursion) - kỹ thuật hàm tự gọi chính nó để giải quyết bài toán phức tạp.