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.
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ớpWallet, 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ềtruenế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
- Các thành viên private:
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
Walletbằng các giá trị đầu vào - In
"Combined wealth: $<amount>"bằngcombinedBalance() - So sánh hai ví và in
"<name> has more money"cho chủ ví có số dư cao hơn (dùnghasMoreThan()để xác định) - Chuyển
100.0từ ví thứ nhất sang ví thứ hai bằngtransferTo() - 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)
- Tạo hai đối tượng
Đ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;
}
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
1Nền tảng về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online