5.1 Thiết lập giới hạn
Tại sao cần hạn chế truy cập?
- Ngăn chặn lập trình viên người dùng thao tác sai các thành viên nội bộ.
- Hỗ trợ nhà thiết kế thư viện thay đổi triển khai nội bộ mà không ảnh hưởng đến mã nguồn bên ngoài.
Tại sao nên dùng class thay vì struct?
classcó các thành viên mặc định làprivate, trong khistructcó các thành viên mặc định làpublic.classcó lợi hơn trong việc ẩn giấu chi tiết triển khai và giới hạn giao diện.
5.2 Quyền truy cập
Ẩn giấu triển khai
Quyền truy cập thường được gọi là ẩn giấu triển khai (implementation hiding). Quyền truy cập thiết lập ranh giới bên trong kiểu dữ liệu trừu tượng vì hai lý do:
- Để tách biệt giao diện (interface) khỏi triển khai (implementation).
- Để làm rõ những gì lập trình viên khách hàng có thể và không thể sử dụng.
Giải thích từ khóa
public: Mọi người đều có thể truy cập.private: Chỉ các hàm thành viên của lớp và chính lớp đó mới có thể truy cập.protected: Tương tự như private, sự khác biệt duy nhất là: lớp con (lớp kế thừa) có thể truy cập các thành viênprotected(nhưng không thể truy cập các thành viênprivate), có nghĩa là lớp đó và các lớp con của nó có thể truy cập.
Ví dụ về quyền truy cập:
// PrivateExample.cpp
// Thiết lập ranh giới
class DoiTuong
{
private:
char kyTu;
float soThuc;
public:
int giaTriCong;
void hamChucNang();
};
void DoiTuong::hamChucNang()
{
giaTriCong = 0;
kyTu = '0';
soThuc = 0.0;
};
int main()
{
DoiTuong doiTuong;
doiTuong.giaTriCong = 1; // OK, public
// doiTuong.kyTu = '1'; // Lỗi, private
// doiTuong.soThuc = 1.0; // Lỗi, private
}
Cấu trúc của lớp
Có hai cách định nghĩa cấu trúc của lớp:
class X
{
public:
void hamGiaoDien();
private:
void hamRiengTu();
int duLieuNgoai;
int mX;
};
class X
{
void hamRiengTu();
int duLieuNgoai;
int mX;
public:
void hamGiaoDien();
};
Lưu ý: Mặc định, các thành viên của class là private.
5.3 Bạn bè (Friends)
Friend là gì?
Hàm friend là một hàm có đặc quyền. Nó cho phép hàm bên ngoài (hàm friend) hoặc lớp (lớp friend) truy cập các thành viên riêng tư của lớp.
Một hàm thành viên public thông thường có ba đặc tính logic khác nhau:
- Hàm này có thể truy cập phần riêng tư của đối tượng lớp.
- Hàm này thuộc phạm vi của lớp (được định nghĩa trong "không gian tên" của lớp).
- Hàm này phải được gọi qua đối tượng (có một con trỏ
this).
Một hàm thành viên public static chỉ có hai đặc tính đầu tiên.
Một hàm friend chỉ có đặc tính đầu tiên.
Tác dụng của friend
- Friend có thể cải thiện hiệu suất.
- Friend không thể được kế thừa (dù là kế thừa riêng tư hay công khai).
- Chúng ta có thể khai báo một hàm toàn cục (global function) là friend; cũng có thể khai báo một hàm thành viên của lớp khác, thậm chí toàn bộ lớp là friend.
Ví dụ 1: Một hàm toàn cục là bạn bè của một lớp
// Example1
#include <iostream>
using namespace std;
class ThoiGian
{
int gio, phut;
public:
void thietLap(int nGio, int nPhut)
{
gio = nGio;
phut = nPhut;
}
friend void hienThi(ThoiGian& tg); // Hàm friend
};
void hienThi(ThoiGian& tg) // Không có tiền tố "friend"
{
cout << tg.gio << ":" << tg.phut << endl;
}
int main()
{
ThoiGian tg;
tg.thietLap(20, 30);
hienThi(tg); // Không phải là thành viên của "tg"
}
Kết quả:
20:30
Ví dụ 2: Một hàm toàn cục là bạn bè của nhiều lớp
// Example2
#include <iostream>
using namespace std;
class Thuyen; // Sẽ được định nghĩa sau
class XeHoi
{
public:
void thietLap(int i){khoiLuong = i;}
friend int tongKhoiLuong(XeHoi& x, Thuyen& t);
private:
int khoiLuong;
};
class Thuyen
{
public:
void thietLap(int i){khoiLuong = i;}
friend int tongKhoiLuong(XeHoi& x, Thuyen& t);
private:
int khoiLuong;
};
int tongKhoiLuong(XeHoi& x, Thuyen& t){
return x.khoiLuong + t.khoiLuong;
}
int main()
{
XeHoi x;
x.thietLap(10);
Thuyen t;
t.thietLap(8);
cout << "Tổng khối lượng là " << tongKhoiLuong(x, t) << endl;
}
Lưu ý: Tại sao phải khai báo trước Thuyen? Vì nguyên tắc của C++ là phải khai báo trước khi sử dụng. Nếu không khai báo trước, thì trong XeHoi, việc sử dụng Thuyen& t làm tham số sẽ gây lỗi.
Kết quả:
Tổng khối lượng là 18
Ví dụ 3: Một hàm thành viên là bạn bè
// Example3
#include <iostream>
#include <string>
using namespace std;
class BeGai; // Sẽ được định nghĩa sau
class BeTrai
{
public:
void khoiTao(string n){ten = n;}
void hienThiThongTin(BeGai& x); // Hàm thành viên
private:
string ten;
};
class BeGai
{
public:
void khoiTao(string n){ten = n;}
friend void BeTrai::hienThiThongTin(BeGai& x); // Friend
private:
string ten;
};
void BeTrai::hienThiThongTin(BeGai& x)
{
cout << "Tên của bé trai là " << ten << endl << "Tên của bé gái là " << x.ten << endl;
}
int main()
{
BeTrai bt;
bt.khoiTao("Bob");
BeGai bg;
bg.khoiTao("Kitty");
bt.hienThiThongTin(bg);
}
Kết quả:
Tên của bé trai là Bob
Tên của bé gái là Kitty
Lưu ý: Ví dụ này hoạt động vì BeTrai::hienThiThongTin(BeGai& x) được BeGai khai báo là friend, do đó nó mới có thể thực thi x.ten.
Ví dụ 4: Một lớp là bạn bè
// Example4
#include <iostream>
using namespace std;
class LopX
{
public:
void thietLap(int i){giaTriX = i;}
void hienThi()
{
cout << "x=" << giaTriX << "," << "y=" << giaTriY << endl;
}
private:
int giaTriX;
static int giaTriY;
friend class LopY; // Không cần khai báo trước lớp friend
};
int LopX::giaTriY = 1; // Khởi tạo biến thành viên tĩnh bên ngoài lớp
class LopY
{
public:
void thietLap(int i, int j);
void hienThi();
private:
LopX doiTuong; // Đối tượng thành viên
};
void LopY::thietLap(int i, int j)
{
doiTuong.giaTriX = i; // Sử dụng thành viên private của doiTuong
LopX::giaTriY = j; // a::y? a.y?
}
void LopY::hienThi() // Sử dụng thành viên private của doiTuong
{
cout << "x=" << doiTuong.giaTriX << ",";
cout << "y=" << LopX::giaTriY << endl;
}
int main()
{
LopX b;
b.thietLap(5);
b.hienThi();
LopY c;
c.thietLap(6, 9);
c.hienThi();
b.hienThi();
}
Kết quả:
x=5,y=1
x=6,y=9
x=5,y=9
Trong **C++**, câu lệnh **friend class** **không cần phải khai báo trước** lớp đó, trình biên dịch sẽ **tự động chấp nhận** Y như một tên lớp, ngay cả khi nó chưa được định nghĩa. Trình biên dịch C++ khi xử lý friend là **khoan dung, nó cho phép bạn khai báo một lớp là bạn bè, **dù lớp đó chưa được khai báo hoặc định nghĩa**, vì trình biên dịch biết rằng định nghĩa của Y có thể xuất hiện sau này, nó sẽ **kiểm tra muộn** xem lớp đó có tồn tại không.
Từ khóa `static`
Trong C++, thành viên `static` (biến hoặc hàm) có nghĩa là nó "thuộc về lớp chứ không phải đối tượng". Điều này có nghĩa là:
- Mọi đối tượng chia sẻ một bản sao của biến `static`:
- Biến thành viên `static` được khai báo trong lớp và định nghĩa bên ngoài lớp
- Vòng đời của nó kéo dài trong suốt quá trình chạy của chương trình
- Dù có bao nhiêu đối tượng, biến thành viên `static` chỉ có một bản sao bộ nhớ.
- Biến thành viên `static` được định nghĩa và khởi tạo chỉ một lần duy nhất bên ngoài lớp:
Dòng mã này khởi tạo biến thành viên tĩnh `giaTriY`, chỉ có thể viết một lần và phải bên ngoài lớp.int LopX::giaTriY = 1; - Cách truy cập:
- Truy cập qua tên lớp: `LopX::giaTriY`.
- Hoặc truy cập qua đối tượng: `doiTuong.giaTriY` (Mặc dù có thể truy cập, nhưng nên dùng tên lớp để nhấn mạnh đây là dữ liệu được chia sẻ bởi lớp).
Vì vậy, `doiTuong.giaTriY` là hợp lệ, nhưng `doiTuong::giaTriY` thì không, vì `::` đòi hỏi bên trái phải là tên kiểu hoặc không gian tên, không phải đối tượng.LopX::giaTriY = j; // a::y? a.y?
5.4 Lớp (The class)
Cấu trúc cơ bản của một lớp:
class Ngay
{
public:
void thietLapNgay(int y, int m, int d);
int laNamNhuan();
void inRa();
private:
int nam, thang, ngay;
};
// Định nghĩa hàm thành viên
Định nghĩa hàm thành viên:
Sử dụng toán tử phân giải phạm vi `::`.
class Ngay
{
public:
void thietLapNgay(int y, int m, int d);
int laNamNhuan();
void inRa();
private:
int nam, thang, ngay;
};
void Ngay::thietLapNgay(int y, int m, int d)
{
nam = y;
thang = m;
ngay = d;
}
Ví dụ: Lớp Handle ẩn giấu chi tiết triển khai (phương pháp Pimpl)
QuanLy.h
// QuanLy.h
// Lớp QuanLy
#ifndef QUANLY_H
#define QUANLY_H
class QuanLy
{
struct NhanVat; // Chỉ khai báo
NhanVat* nhanVatNho;
public:
void khoiTao();
void veSinh();
int doc();
void thayDoi(int);
};
#endif
QuanLy.cpp
// QuanLy.cpp
// Triển khai lớp QuanLy
#include "QuanLy.h"
#include <cassert>
struct QuanLy::NhanVat
{
int i;
};
// ... (triển khai các hàm thành viên)
UseQuanLy.cpp
// UseQuanLy.cpp
#include "QuanLy.h"
int main()
{
QuanLy u;
u.khoiTao();
u.doc();
u.thayDoi(1);
u.veSinh();
}