Hàm và lớp bạn
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 32 trên 104.
Đôi khi, một hàm hoặc lớp bên ngoài lớp của bạn cần truy cập các thành viên riêng tư của lớp. Thay vì đặt các thành viên đó ở chế độ công khai, C++ cung cấp từ khóa friend để cấp quyền truy cập có chọn lọc mà vẫn duy trì tính đóng gói.
Hàm bạn được khai báo bên trong một lớp nhưng không phải là thành viên của lớp đó. Hàm này có thể truy cập các thành viên riêng tư và được bảo vệ của lớp:
class Box {
double width;
public:
Box(double w) : width(w) {}
// Khai báo printWidth là hàm bạn
friend void printWidth(const Box& b);
};
// Định nghĩa hàm bạn (friend function), KHÔNG phải hàm thành viên
void printWidth(const Box& b) {
std::cout << b.width; // Có thể truy cập thành viên riêng tư
}Lưu ý rằng printWidth được định nghĩa bên ngoài lớp mà không có toán tử phân giải phạm vi. Đây là một hàm thông thường nhưng có quyền truy cập đặc biệt.
Bạn cũng có thể cho phép toàn bộ một lớp làm bạn, để mọi hàm thành viên của lớp đó truy cập các thành viên riêng tư:
class Engine {
int horsepower;
friend class Mechanic; // Mechanic có thể truy cập các thành viên riêng tư
public:
Engine(int hp) : horsepower(hp) {}
};
class Mechanic {
public:
void diagnose(const Engine& e) {
std::cout << e.horsepower; // Hợp lệ vì Mechanic là lớp bạn
}
};Tình bạn không có tính hai chiều. Nếu Engine khai báo Mechanic là bạn, Mechanic có thể truy cập các thành viên riêng tư của Engine, nhưng điều ngược lại thì không. Hãy dùng quan hệ bạn bè một cách hạn chế, vì chúng tạo ra sự liên kết chặt chẽ giữa các lớp. Chúng hữu ích nhất khi nạp chồng toán tử và viết các hàm trợ giúp có liên quan chặt chẽ.
Thử thách
DễHãy xây dựng một hệ thống nhắn tin an toàn, trong đó lớp MessageReader cần quyền truy cập đặc biệt để đọc các tin nhắn đã mã hóa được lưu trong lớp SecureMessage. Đây là tình huống lý tưởng để dùng hàm bạn và lớp bạn.
Bạn sẽ tạo hai tệp để sắp xếp code:
SecureMessage.h: Định nghĩa lớpSecureMessagelưu dữ liệu tin nhắn ở chế độ riêng tư và cấp quyền truy cập có chọn lọc cho các thực thể đáng tin cậy. Lớp của bạn cần có:- Các thành viên riêng tư:
content(chuỗi) vàsecretKey(số nguyên) - Một hàm khởi tạo nhận nội dung và khóa bí mật
- Khai báo
MessageReaderlà lớp bạn để lớp này có thể truy cập các thành viên riêng tư - Khai báo hàm bạn
decryptMessage(const SecureMessage& msg, int key), hàm này trả về nội dung chỉ khi khóa được cung cấp khớp với khóa bí mật, nếu không thì trả về"Access Denied"
Sau đó, định nghĩa lớp
MessageReadertrong cùng tệp tiêu đề:- Một phương thức
readContent(const SecureMessage& msg)truy cập trực tiếp và trả vềcontentriêng tư của tin nhắn - Một phương thức
verifyKey(const SecureMessage& msg, int key)kiểm tra xem khóa được cung cấp có khớp vớisecretKeycủa tin nhắn hay không, rồi trả về"Valid"hoặc"Invalid"
Cuối cùng, định nghĩa hàm bạn
decryptMessagesau các lớp.- Các thành viên riêng tư:
main.cpp: Minh họa cách dùng hàm bạn và lớp bạn. Đọc nội dung tin nhắn, khóa bí mật và khóa kiểm tra từ đầu vào, mỗi giá trị trên một dòng riêng, sau đó:- Tạo một đối tượng
SecureMessagevới nội dung và khóa bí mật - Tạo một đối tượng
MessageReader - In ra
"Reader access: <content>"bằng phương thứcreadContent()của đối tượng đọc - In ra
"Key verification: <result>"bằng cách gọiverifyKey()với khóa kiểm tra - In ra
"Decrypt with test key: <result>"bằng cách gọi hàm bạn với khóa kiểm tra - In ra
"Decrypt with correct key: <result>"bằng cách gọi hàm bạn với khóa bí mật ban đầu
- Tạo một đối tượng
Lưu ý rằng hàm bạn được định nghĩa bên ngoài lớp, không có toán tử phân giải phạm vi: chúng là các hàm thông thường có quyền truy cập đặc biệt. Lớp MessageReader, với tư cách là lớp bạn, có thể truy cập trực tiếp các thành viên riêng tư của SecureMessage trong tất cả phương thức của nó.
Chuyển các đầu vào khóa từ chuỗi sang số nguyên bằng std::stoi(). Thêm tệp tiêu đề vào main.cpp bằng #include "SecureMessage.h".
Tự làm thử
#include <iostream>
#include <string>
#include "SecureMessage.h"
using namespace std;
int main() {
// Đọc đầu vào
string messageContent;
string secretKeyStr;
string testKeyStr;
getline(cin, messageContent);
getline(cin, secretKeyStr);
getline(cin, testKeyStr);
int secretKey = stoi(secretKeyStr);
int testKey = stoi(testKeyStr);
// TODO: Tạo SecureMessage với nội dung và khóa bí mật
// TODO: Tạo đối tượng MessageReader
// TODO: In ra "Reader access: <content>" bằng readContent()
// TODO: In ra "Key verification: <result>" bằng verifyKey() với khóa kiểm tra
// TODO: In ra "Decrypt with test key: <result>" bằng decryptMessage với khóa kiểm tra
// TODO: In ra "Decrypt with correct key: <result>" bằng decryptMessage với khóa bí mật
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