Skip to content

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.33333

Ví 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: 4

Ví 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:

  1. 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
  2. Function Scope:

    • Khai báo trong function
    • Chỉ truy cập được trong function đó
    • Tồn tại khi function chạy
  3. 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
  4. 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.

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