I. Khái niệm sơ bộ về lập trình hướng thủ tục và hướng đối tượng
Lập trình hướng thủ tục, như trong ngôn ngữ C, tập trung vào các bước xử lý. Để giải một bài toán, ta phân tích quy trình thực hiện và dùng các hàm để lần lượt hoàn thành từng phần.
Trong khi đó, C++ là ngôn ngữ hướng đối tượng, nghĩa là chia vấn đề thành các đối tượng riêng biệt. Mỗi đối tượng có dữ liệu và hành vi riêng, và chúng phối hợp với nhau để hoàn thành nhiệm vụ.
II. Giới thiệu về lớp (class)
Trong C, cấu trúc (struct) chỉ chứa dữ liệu. Nhưng ở C++, struct được mở rộng để có thể chứa cả hàm. Điều này khiến cấu trúc gần giống với một lớp đơn giản.
Ví dụ: triển khai ngăn xếp (stack) bằng C++:
struct Stack {
void KhoiTao(int dungLuong = 4) {
phanTu = (int*)malloc(dungLuong * sizeof(int));
if (!phanTu) {
perror("Cap phat that bai");
return;
}
dinh = 0;
this->dungLuong = dungLuong;
}
int* phanTu;
int dinh;
int dungLuong;
};
Ta có thể xem struct trong C++ như một phiên bản nâng cấp của cấu trúc C, và thường dùng từ khóa class thay vì struct để định nghĩa lớp.
III. Cách khai báo lớp
Cú pháp cơ bản để định nghĩa một lớp:
class TenLop {
// Các thành viên: biến và hàm
};
class: từ khóa khai báo lớp.TenLop: tên của lớp.- Phần bên trong
{}gọi là thân lớp, chứa các thành viên. - Kết thúc định nghĩa lớp phải có dấu
;.
Các biến bên trong gọi là biến thành viên (thuộc tính), còn các hàm là hàm thành viên (phương thức).
3.1 Hai cách định nghĩa hàm thành viên
Cách 1: Viết trực tiếp hàm bên trong lớp.
class Stack {
public:
void KhoiTao(int dungLuong = 4) {
phanTu = (int*)malloc(dungLuong * sizeof(int));
dinh = 0;
this->dungLuong = dungLuong;
}
private:
int* phanTu;
int dinh;
int dungLuong;
};
→ Hàm định nghĩa trong lớp có thể được biên dịch thành hàm nội tuyến (inline).
Cách 2 (khuyến nghị): Khai báo trong file .h, định nghĩa trong file .cpp.
// Stack.h
class Stack {
public:
void KhoiTao(int dungLuong = 4);
private:
int* phanTu;
int dinh;
int dungLuong;
};
// Stack.cpp
void Stack::KhoiTao(int dungLuong) {
phanTu = (int*)malloc(dungLuong * sizeof(int));
dinh = 0;
this->dungLuong = dungLuong;
}
Dấu :: là toán tử phạm vi, giúp liên kết hàm với lớp.
3.2 Quy ước đặt tên biến thành viên
Nên thêm tiền tố hoặc hậu tố để phân biệt với tham số hàm, ví dụ: _tenBien hoặc m_tenBien.
IV. Phạm vi truy cập và tính đóng gói
4.1 Các mức truy cập
C++ hỗ trợ ba mức truy cập:
- public: thành viên có thể truy cập từ bên ngoài lớp.
- private: chỉ truy cập được bên trong lớp.
- protected: dùng trong kế thừa, tương tự private ở giai đoạn đầu.
Một số nguyên tắc:
- Phạm vi bắt đầu từ vị trí xuất hiện đến khi gặp mức truy cập tiếp theo hoặc kết thúc lớp.
- Lớp dùng
classcó mức mặc định là private. - Lớp dùng
structcó mức mặc định là public (để tương thích với C).
Câu hỏi phỏng vấn: Sự khác biệt giữastructvàclasstrong C++?
→ Khác biệt chính nằm ở mức truy cập mặc định:structlà public,classlà private.
4.2 Tính đóng gói (Encapsulation)
Đóng gói là việc kết hợp dữ liệu và phương thức xử lý dữ liệu vào cùng một lớp, đồng thời che giấu chi tiết cài đặt, chỉ công bố giao diện sử dụng.
Ví dụ: máy tính để bàn cung cấp nút nguồn, bàn phím, cổng USB… Người dùng không cần biết CPU hay bo mạch chủ hoạt động thế nào. Nhà sản xuất "đóng hộp" mọi thứ lại, chỉ để lộ những gì cần thiết — đó chính là đóng gói.
Trong C++, ta dùng lớp và các mức truy cập để thực hiện đóng gói, giúp quản lý mã tốt hơn và tăng tính bảo mật.
V. Phạm vi lớp (Class Scope)
Lớp tạo ra một phạm vi mới. Mọi thành viên đều thuộc phạm vi này. Khi định nghĩa hàm bên ngoài, phải dùng toán tử :: để chỉ rõ hàm thuộc lớp nào.
class NhanSu {
public:
void HienThi();
private:
char ten[20];
char gioiTinh[3];
int tuoi;
};
void NhanSu::HienThi() {
cout << ten << " " << gioiTinh << " " << tuoi << endl;
}
VI. Khởi tạo đối tượng (Instance)
Việc tạo đối tượng từ lớp gọi là khởi tạo.
- Lớp là bản mẫu, chưa chiếm bộ nhớ.
- Đối tượng là thể hiện cụ thể, chiếm bộ nhớ thực tế.
Ví dụ:
class NgayThang {
public:
void DatNgay(int nam, int thang, int ngay);
private:
int _nam, _thang, _ngay;
};
int main() {
NgayThang ngay1; // Khởi tạo đối tượng
return 0;
}
VII. Kích thước của đối tượng lớp
Làm sao để tính kích thước một đối tượng?
7.1 Giả định về cách lưu trữ
Có ba khả năng:
- Mỗi đối tượng lưu toàn bộ dữ liệu và mã hàm → lãng phí.
- Lưu địa chỉ hàm → phức tạp, không hiệu quả.
- Chỉ lưu biến thành viên, còn hàm được chia sẻ chung → hợp lý nhất.
7.2 Kiểm chứng qua thực nghiệm
class A1 {
void Ham1() {}
int bien;
}; // sizeof(A1) = 4 (chỉ tính biến)
class A2 {
void Ham2() {}
}; // sizeof(A2) = 1
class A3 {}; // sizeof(A3) = 1
Kết luận: Kích thước lớp = tổng kích thước các biến thành viên + căn chỉnh bộ nhớ.
Lớp rỗng có kích thước 1 byte để đảm bảo mỗi đối tượng có địa chỉ riêng biệt.
VIII. Con trỏ this trong hàm thành viên
8.1 Giới thiệu con trỏ this
Xét lớp ngày tháng sau:
class NgayThang {
public:
void DatNgay(int nam, int thang, int ngay) {
_nam = nam;
_thang = thang;
_ngay = ngay;
}
void In() {
cout << _nam << "-" << _thang << "-" << _ngay << endl;
}
private:
int _nam, _thang, _ngay;
};
Khi d1.DatNgay(2023, 12, 3), làm sao hàm biết đang thao tác trên d1 chứ không phải d2?
→ Biên dịch C++ tự động thêm một tham số ẩn là con trỏ this, trỏ đến đối tượng gọi hàm. Mọi truy cập biến thành viên thực chất là thông qua this->.
8.2 Đặc điểm của con trỏ this
- Loại:
TenLop* const— không thể gán lại. - Chỉ xuất hiện trong hàm thành viên.
- Không lưu trong đối tượng — là tham số ẩn.
- Thường được truyền qua thanh ghi (VD:
ecxtrong Visual Studio).
Câu hỏi phỏng vấn:
1. Con trỏthisđược lưu ở đâu?
→ Trên ngăn xếp (stack), hoặc trong thanh ghi.
2.thiscó thể lànullkhông?
Xét ví dụ:
class A {
public:
void In() {
cout << "In()" << endl;
}
private:
int _a;
};
int main() {
A* p = nullptr;
p->In(); // Vẫn chạy bình thường!
return 0;
}
→ Không bị lỗi vì hàm In() không truy cập biến thành viên, nên không cần giải tham chiếu this.
class A {
public:
void InA() {
cout << _a << endl; // Truy cập biến thành viên
}
private:
int _a;
};
int main() {
A* p = nullptr;
p->InA(); // Crash! Vì this == null, không thể truy cập _a
return 0;
}
Kết luận: this có thể là null, nhưng chỉ gây lỗi khi có thao tác truy cập dữ liệu thành viên.