- Khái niệm
Ở những chương trình code bình thường, code của chúng ta như một vector vậy, chúng chỉ là một dãy các dòng lệnh sẽ được chạy tuần tự từ đầu đến cuối. Ngoại lệ là các hàm và dữ liệu sẽ được tách khỏi vector đó và có thể gọi chúng giữa vector để tạo nên kết quả.
Vậy OOP – Lập trình hướng đối tượng là gì? Nếu nhìn vào cuộc sống thực tế, tất cả thứ bạn thấy xung quanh đều là các đối tượng (Object) , object sẽ luôn có 2 thành phần chính
- Các đặc tính của chúng. (VD: cân nặng, chiều cao, kích cỡ, hình dáng, màu sắc, …)
- Các “hành động” chúng có thể bị tương tác hoặc có thể tương tác (Quyển sách bị “mở”, người “đi”, …) (Hàm)
Lập trình hướng đối tượng cho chúng ta khả năng tạo ra các đối tượng liên kết chặt chẽ với nhau và hoạt động như những khối hộp có thể sử dụng lại
VD: Bạn cần tạo một con game thế giới mở, người chơi có thể đi lại trong thế giới.
VD bạn cần tạo ra một ngôi nhà, ngôi nhà được cấu thành từ : bức tường, vật gia dụng, sơn, …. Bạn không thể mỗi nơi lại gọi hết các bức tường, vật gia dụng, sơn, … khác nhau ra, mà chúng ta sẽ có một Lớp – Ngôi nhà chứa tất cả đặc tính chúng ta cần như màu sắc, kích cỡ, và các thuộc tính này sẽ khác nhau mỗi nơi, để có thể sử dụng lại
- Class và Object
Class là một lớp chứa các thuộc tính, chức năng. và Object – đối tượng là một vật thể được tạo ra từ một lớp Class đó.
struct NgayThang { int nam {}; int thang {}; int ngay {}; };
Đây là một lớp ngày tháng có 3 thuộc tính : ngày tháng năm, các lớp bây giờ sẽ là những thời gian khác nhau trong năm
NgayThang tet = 1/1/2022
NgayThang valentine = 4/2/2022
Con trỏ this?
This là một con trỏ cho phép chúng ta truy cập các thành phần, đặc tính của class
Ví dụ ta đang xây dựng một mạng xã hội với Class, và có một lớp NguoiDung
struct NguoiDung { int taikhoan {}; int matkhau {};};
Ta không thể cho phép người sử dụng khác in ra mật khẩu, tài khoản của một người dùng bất kì
cout << NguoiDung.matkhau;
=> Không cho phép truy cập các phần tử bí mật.
Nhưng ta vẫn cần cho phép người dùng có thể lấy ra mật khẩu khi đăng nhập, đăng xuất, quên mật khẩu, … nhưng mà lại không thể truy cập các phần tử bí mật.
NguoiDung endcontest;
cout << endcontest.matkhau; -> Không được
endcontest.dangnhap();
// nhưng
void NguoiDung::dangnhap() const {
string matkhau;
cin >> matkhau;
if (matkhau == this.matkhau) cout << “dang nhap”;
}
Ta đã ứng dụng con trỏ this để gọi ra mật khẩu, trong khi không thể truy cập trực tiếp.
- Các đặc tính của OOP
Tính đóng gói (Encapsulation):
(Điều này giống ví dụ về this bên trên)
Là cách để che dấu những tính chất xử lý bên trong của đối tượng, những đối tượng khác không thể tác động trực tiếp làm thay đổi trạng thái chỉ có thể tác động thông qua các method public của đối tượng đó.
xem cách thể hiện bằng code dưới đây : class hinhchunhat
- height và width ở đây chính là các tính chất (properties) của đối tượng class hinhchunhat
- tinhdientich() là method được public nhằm mục đích tương tác với các đối tượng khác. Tạo một class Program với method static để run, xem cách tương tác và thay đổi tính chất của đối tượng thông qua các method public như nào:
Ta thấy ta sẽ không thể truy cập các thuộc tính height, width. Nhưng vẫn có thể sử dụng các hàm/method để truy cập chúng.
Tính kế thừa (Inheritance):
Là kỹ thuật cho phép kế thừa lại những tính năng mà một đối tượng khác đã có, giúp tránh việc code lặp dư thừa mà chỉ xử lý công việc tương tự.
- Kế thừa một cấp (Single level Inheritance): Với một class cha và một class con
Ví dụ ta có một class đồ điện tử, chúng có các thuộc tính như: số vi mạch, lượng điện sử dụng, hiệu điện thế, cường độ dòng điện ,…. Các thiết bị như điện thoại, tai nghe, … vẫn dùng chúng, nhưng lại có thêm một số thuộc tính riêng khác, nên thay vì phải tạo lại tất cả thuộc tính đó cho từng class điện thoại, tai nghe mới. Ta sẽ gọi Điện thoại là lớp kế thừa của đồ điện tử, và thừa hưởng mọi thuộc tính, chức năng của đồ điện tử.
Tính đa hình (Polymorphism):
Là một đối tượng thuộc các lớp khác nhau có thể hiểu cùng một thông điệp theo cách khác nhau.
Ví dụ đa hình trong thực tế: Mình có 2 con vật: chó, mèo hai con vật này khi nhận được mệnh lệnh là “hãy kêu” thì chó kêu “gâu gâu”, mèo kêu “meo meo”.
Ví dụ trên cả 2 con vật đều hiểu chung một thông điệp “hãy kêu” và thực hiện theo cách riêng của chúng.
Tính trừu tượng (Abstraction):
Abstraction (Tính trừu tượng) là một khả năng mà chương trình có thể bỏ qua sự phức tạp bằng cách tập trung vào cốt lõi của thông tin cần xử lý.
Điều đó có nghĩa, bạn có thể xử lý một đối tượng bằng cách gọi tên một phương thức và thu về kết quả xử lý, mà không cần biết làm cách nào đối tượng đó được các thao tác trong class.
Ví dụ: bạn có thể nấu cơm bằng nồi cơm điện bằng cách rất đơn giản là ấn công tắc nấu, mà không cần biết là bên trong cái nồi cơm điện đó đã làm thế nào mà gạo có thể nấu thành cơm.
- Nhãn phạm vi
Nhãn public
- Từ khóa public được dùng để tạo các thành phần “public – công khai” (Cho dữ liệu hoặc hàm)
- Các thành phần được tạo public có thể truy cập từ bất cứ chỗ nào trong phần mềm
Nhãn private
- Từ khóa public được dùng để tạo các thành phần “private – bí mật” (Cho dữ liệu hoặc hàm)
- Các phần tử private chỉ có thể truy cập trong class hoặc class friend
Nhãn protected
- Từ khóa public được dùng để tạo các thành phần “protected – bảo mật” (Cho dữ liệu hoặc hàm)
- Các thành phần protected chỉ có thể truy cập trong class và các class kế thừa của nó.
Tổng quát
Kiểu | Trong chính class | Class kế thừa | Bên ngoài |
public | Yes | Yes | Yes |
private | Yes | No | No |
protected | Yes | Yes | No |
- Hàm khởi tạo và hàm hủy
Hàm khởi tạo là một hàm đặc biệt trong lớp. Hàm này được gọi tự động khi một đối tượng được tạo ra. Hàm khởi tạo sẽ khởi tạo giá trị cho các thuộc tính của đối tượng. Trong C++, một hàm khởi tạo có đặc điểm sau:
− Tên hàm khởi tạo trùng với tên của lớp.
− Hàm khởi tạo không có kiểu dữ liệu trả về (kể cả void).
− Hàm khởi tạo có thể là public, private hoặc protected. Hàm private có thể truy cập từ lớp friend, protected có thể gọi từ lớp kế thừa.
− Hàm khởi tạo có thể có đối số hoặc không có đối số.
− Trong một lớp có thể có nhiều hàm khởi tạo (cùng tên nhưng khác đối số).
Hàm khởi tạo mặc định (default constructor)
Hàm khởi tạo mà không có đối số nào thì được gọi là hàm khởi tạo mặc định. Hàm này thường dùng gán giá trị mặc định cho các thuộc tính trong lớp.
Trong ví dụ bên dưới, hàm Circle() là một hàm khởi tạo mặc định và tự đặt r = 1 nếu chưa cài r.
Lưu ý: Nếu lớp không có hàm khởi tạo nào, trình biên dịch sẽ cung cấp một hàm khởi tạo mặc định không đối số và không code trong nó.
Nếu trong lớp đã có ít nhất một hàm khởi tạo thì hàm khởi tạo mặc định do trình biên dịch cung cấp sẽ không còn nữa. Khi đó, bạn phải sử dụng các hàm khởi tạo được định nghĩa trong lớp nếu không sẽ gây ra lỗi.
Hàm huỷ sẽ tự động được gọi trước khi giải phóng một đối tượng để giải phóng vùng nhớ trước khi đối tượng được hủy bỏ. Hàm hủy có các đặc điểm sau:
− Mỗi lớp chỉ có một hàm hủy.
− Hàm hủy không có kiểu, không có giá trị trả về và không có đối số.
− Tên hàm huỷ cùng tên với tên lớp và có một dấu ngã (~) ngay trước tên.
- Getter và Setter
Là các hàm dùng để set (đặt) hoặc get (lấy) dữ liệu
- Static
Static trong C++ là một toán tử có tác dụng chỉ định một biến hoặc hàm thành viên trong class tồn tại ở dạng tĩnh.
Dạng tĩnh ở đây có ý nghĩa, vùng bộ nhớ dùng để lưu trữ các dữ liệu này sẽ là bất biến, không thay đổi.
Bằng việc sử dụng static trong class C++, chúng ta sẽ có được biến static và hàm static, là các thành viên được lưu tại các vùng bộ nhớ bất biến. Vì ở dạng tĩnh nên chúng sẽ tồn tại duy nhất trong class. Chúng có thể sử dụng kể cả không có đối tượng trong class
6.1. Biến static
Để tạo biến static trong class C++, chúng ta thêm toán tử static vào đằng trước tên biến khi khai báo biến trong class như sau. Lưu ý là access modifier của biến static phải ở dạng public để cho phép truy cập nó từ ngoài phạm vi class.
Ưu điểm lớn nhất của biến static đó chính là chúng ta có thể sử dụng trực tiếp nó mà không cần phải tạo ra đối tượng (instance) từ class. Tuy nhiên do không tạo ra instance dẫn đến việc giá trị ban đầu của biến static cũng không được khởi tạo, nên khi dùng biến static mà không tạo ra đối tượng, chúng ta cần phải khởi tạo giá trị cho nó ở bên ngoài phạm vi class.
Ví dụ:
Giống như ví dụ trên, do sNum là biến static nên chúng ta có thể trực tiếp sử dụng giá trị của nó mà không cần phải tạo ra đối tượng từ class. Tuy nhiên biến num thì chỉ là biến thành viên bình thường nên chúng ta không thể sử dụng trực tiếp nó vì lỗi sẽ xảy ra.
6.2. Hàm static
Để tạo hàm static trong class C++, chúng ta thêm toán tử static vào đằng trước tên hàm khi khai báo hàm trong class như sau. Lưu ý là access modifier của hàm static phải ở dạng public để cho phép truy cập nó từ ngoài phạm vi class.
Lưu ý: đối với các hàm static thì các biến sử dụng bên trong phải là biến static
Ví dụ:
- Kỹ thuật chia tách file trong C++
Headers file
Khi các chương trình phát triển ngày càng lớn (và sử dụng nhiều file hơn), Việc khai báo các hàm bạn muốn sử dụng được định nghĩa trong cùng một file sẽ trở nên ngày càng tẻ nhạt và làm cho file đó trở lên nhiều dòng code, khó kiểm soát. Sẽ tốt hơn nếu bạn có thể đặt tất cả các khai báo của mình ở một nơi và sau đó định nghĩa chúng khi bạn cần nó.
Viết một file header
Ví dụ chúng ta có hai tệp, add.cpp và main.cpp, trông giống như thế này:
Trong ví dụ này, chúng ta đã sử dụng một khai báo để trình biên dịch sẽ biết định danh add là gì khi biên dịch main.cpp. Chúng ta có thể thấy việc thêm thủ công cho mọi hàm bạn muốn sử dụng trong một file khác khá nhiều và phức tạp.
Hãy viết một file header để giảm bớt gánh nặng này. Viết một file header rất dễ dàng, vì các file header chỉ bao gồm hai phần:
Các tiền chỉ thị trong file header.
Nội dung thực tế của file header, phải là khai báo cho tất cả các định danh mà chúng ta muốn các file khác có thể nhìn thấy.
Sử dụng hậu tố .h khi đặt tên file header
Các file header thường được ghép nối với các file code, với file header cung cấp các khai báo cho file code tương ứng. Vì file header của chúng ta sẽ chứa một khai báo cho các hàm được định nghĩa trong add.cpp, nên chúng ta sẽ gọi file header mới là add.h.
Nếu một file header được ghép nối với một file code (ví dụ: add.h với add.cpp), cả hai sẽ có cùng tên (add).
File add.h
Để sử dụng file header này trong main.cpp, chúng ta phải #include nó (sử dụng dấu ngoặc kép, không phải dấu ngoặc nhọn).
main.cpp
Quy tắc đặt tên biến trong OOP C++
Luôn sử dụng tiếng Anh để đặt tên hàm và biến.
Ví dụ:
/* Bad */
const hoTen = “Trạng Tí”
const banBe = [“Sửu Ẹo”, “Dần Béo”, “Cả Mẹo”]
/* Good */
const fullName = “Trạng Tí”
const friends = [“Sửu Ẹo”, “Dần Béo”, “Cả Mẹo”]
Quy ước đặt tên
Nếu team của bạn chọn quy ước đặt tên là camelCase, hãy sử dụng camelCase cho toàn bộ dự án, nếu bạn qua một team khác chuộng snake_case hơn, hãy tuân thủ nghiêm ngặt. Cho dù là quy ước nào thì điều quan trọng nhất chính là tính nhất quán.
/* Bad */
const page_count = 5
const isUser = true
/* Good */
const pageCount = 5
const isUser = true
Nguyên tắc S-I-D
Short (ngắn gọn): tên không được dài, không phải mất thời gian để gõ và nhớ.
Intuitive (tự nhiên): tên khi đọc lên phải cho cảm giác xuôi tai, gần gũi với văn nói.
Descriptive (súc tích): tên phải mô tả được ý nghĩa, tác dụng của nó, bằng cách hiệu quả nhất.
/* Bad */
const totalNumberOfPublishedArticles = 10 // tên quá dài
const a = 5 // “a” không mô tả được số 5 để làm gì
const isDisplayable = a > 5 // “isDisplayable” nghe không tự nhiên lắm
/* Good */
const totalArticles = 10
const postCount = 5
const shouldDisplay = postCount > 5