Menu
CoddyTech

Tìm hiểu sâu về phạm vi truy cập

Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 35 trên 104.

Giờ bạn đã hiểu ba từ chỉ định phạm vi truy cập, hãy cùng tìm hiểu cách chúng hoạt động khi có nhiều đối tượng thuộc cùng một lớp. Nhiều người thường hiểu nhầm rằng private có nghĩa là “chỉ đối tượng này”, nhưng thực ra nó có nghĩa là “chỉ lớp này”.

Các đối tượng thuộc cùng một lớp có thể truy cập các thành viên riêng tư của nhau:

class Person {
    std::string secret;
    
public:
    Person(std::string s) : secret(s) {}
    
    void readSecret(const Person& other) {
        // Truy cập thành viên riêng tư của đối tượng khác: hợp lệ!
        std::cout << other.secret;
    }
};

Điều này hoạt động vì quyền truy cập được kiểm soát ở cấp lớp (class), chứ không phải cấp đối tượng (object). Phương thức (method) readSecret thuộc lớp Person, nên phương thức này có thể truy cập các thành viên riêng tư của bất kỳ đối tượng Person nào. Thiết kế này cho phép các thao tác như hàm khởi tạo sao chép và toán tử so sánh hoạt động với trạng thái nội bộ của các đối tượng khác.

Bạn cũng có thể chuyển đổi giữa các chỉ định phạm vi truy cập nhiều lần trong một lớp:

class Mixed {
public:
    void publicMethod1();
    
private:
    int privateData;
    
public:
    void publicMethod2();    // Trở lại public
    
private:
    void privateHelper();    // Lại là private
};

Dù đây là C++ hợp lệ, bạn nên nhóm các thành viên theo quyền truy cập để mã dễ đọc hơn. Hầu hết hướng dẫn về phong cách viết mã đều khuyên liệt kê các thành viên public trước vì chúng tạo thành giao diện của lớp, tiếp theo là protected rồi đến private.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống so sánh ví để minh họa cách các đối tượng thuộc cùng một lớp có thể truy cập các thành viên private của nhau. Đây là điểm mấu chốt về kiểm soát quyền truy cập trong C++: quyền truy cập được kiểm soát ở cấp lớp, không phải cấp đối tượng.

Bạn sẽ tạo hai tệp để sắp xếp code:

  • Wallet.h: Định nghĩa lớp Wallet, lớp này lưu dữ liệu tài chính private nhưng cho phép so sánh giữa các ví. Lớp của bạn cần có:
    • Các thành viên private: ownerName (chuỗi) và balance (số thực)
    • Một hàm khởi tạo nhận tên chủ ví và số dư ban đầu
    • Một getter public getOwnerName() trả về tên chủ ví
    • Một phương thức hasMoreThan(const Wallet& other) so sánh số dư của ví này với số dư private của ví khác, trả về true nếu ví này có nhiều tiền hơn
    • Một phương thức combinedBalance(const Wallet& other) trả về tổng số dư private của cả hai ví
    • Một phương thức transferTo(Wallet& other, double amount) chuyển tiền từ ví này sang ví khác. Phương thức này cần trực tiếp thay đổi số dư private của cả hai ví, chỉ khi số tiền dương và không vượt quá số dư của ví này
  • main.cpp: Minh họa cách truy cập thành viên private giữa các đối tượng. Đọc tên của hai chủ ví và số dư của hai ví từ đầu vào (bốn dòng: name1, balance1, name2, balance2), sau đó:
    • Tạo hai đối tượng Wallet bằng các giá trị đầu vào
    • In "Combined wealth: $<amount>" bằng combinedBalance()
    • So sánh hai ví và in "<name> has more money" cho chủ ví có số dư cao hơn (dùng hasMoreThan() để xác định)
    • Chuyển 100.0 từ ví thứ nhất sang ví thứ hai bằng transferTo()
    • In "After transfer:"
    • In lại "Combined wealth: $<amount>" (giá trị này không thay đổi)
    • So sánh lại và in "<name> has more money" (kết quả có thể đã thay đổi)

Điểm mấu chốt ở đây là các phương thức như hasMoreThan() và transferTo() có thể trực tiếp truy cập thành viên private balance của một đối tượng Wallet khác, không cần getter. Cách này hoạt động vì quyền truy cập trong C++ được kiểm soát theo lớp: mọi code bên trong lớp Wallet đều có thể truy cập các thành viên private của bất kỳ đối tượng Wallet nào.

Định dạng tất cả giá trị tiền tệ với hai chữ số thập phân bằng std::fixed và std::setprecision(2) từ <iomanip>. Chuyển đổi đầu vào số dư bằng std::stod().

Tự làm thử

#include <iostream>
#include <string>
#include <iomanip>
#include "Wallet.h"

using namespace std;

int main() {
    // Đọc đầu vào: name1, balance1, name2, balance2
    string name1, name2;
    string balanceStr1, balanceStr2;
    
    getline(cin, name1);
    getline(cin, balanceStr1);
    getline(cin, name2);
    getline(cin, balanceStr2);
    
    double balance1 = stod(balanceStr1);
    double balance2 = stod(balanceStr2);
    
    // Định dạng đầu ra cho các giá trị tiền tệ
    cout << fixed << setprecision(2);
    
    // TODO: Tạo hai đối tượng Wallet bằng các giá trị đầu vào
    
    // TODO: In ra tổng số tiền bằng combinedBalance()
    // Định dạng: "Combined wealth: $<amount>"
    
    // TODO: So sánh hai ví bằng hasMoreThan() và in ra ai có nhiều tiền hơn
    // Định dạng: "<name> has more money"
    
    // TODO: Chuyển 100.0 từ ví thứ nhất sang ví thứ hai bằng transferTo()
    
    // TODO: In ra "After transfer:"
    
    // TODO: In lại tổng số tiền
    
    // TODO: So sánh lại và in ra ai có nhiều tiền hơn
    
    return 0;
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C++ online