diff --git a/02AttributeAndMethod.md b/02AttributeAndMethod.md new file mode 100644 index 0000000..d2869b5 --- /dev/null +++ b/02AttributeAndMethod.md @@ -0,0 +1,175 @@ +# 2. Thuộc tính đối tượng và phương thức # +**Dẫn nhập:** Bài này chúng ta sẽ bắt đầu xây dựng các thành phần cơ bản của một class, cụ thể trong bài này chúng ta sẽ tạo hàm constructor và destructor cho một class. + + class + { + (); //constructor + ~(); //destructor + }; +## 1. Phương thức tạo lập (CONSTRUCTOR) ## +Phương thức constructor có nhiệm vụ tạo một đối tượng của lớp đó. Phương thức này có đặc điểm là không có kiểu trả về và được gọi tự động ghi tạo một đối tượng thuộc class đó. + + class + { + (); //constructor + } + +Phương thức constructor có thể có hoặc không có tham số, tùy theo kiểu tham số ta chia ra nhiều loại, phụ thuộc vào từng mục đích sử dụng. **Nhưng cơ bản vẫn là tạo ra một đối tượng từ các tham số truyền vào, xét thuộc tính và có thể thực hiện thêm các thao tác khác**. Một class có thể cài đặt nhiều constructor. +### 1.1 Parameterized constructors ### +Đây là constructor có chứa tham số đơn thuần. + + class HocSinh + { + private: + int MaSo; + string HoTen; + public: + HocSinh(int MaSo) + { + this->MaSo = MaSo; + } + }; +class `HocSinh` có `Parameterized constructors` là `HocSinh(int MaSo);` khi cần tạo một đối tượng chúng ta sẽ gọi lệnh như sau + + HocSinh hs(186); + +hoặc + + HocSinh* hs = new HocSinh(186); + +Chương trình sẽ tự động gọi constructor phù hợp với tham số đầu vào. Và kết quả là ta có một đối tượng `hs` có MaSo là 186. +Trong constructor có sử dụng một biến là `this`, đây là một con trỏ, con trỏ này chứa địa chỉ của đối tượng đó. `this->MaSo` có nghĩa là truy cập đến thuộc tính `MaSo` của đối tượng đó. +Chúng ta có thể cài đặt đầy đủ + + class HocSinh + { + private: + int MaSo; + string HoTen; + public: + HocSinh(int MaSo, string HoVaTen) + { + this->MaSo = MaSo; + HoTen = HoVaTen; + } + }; + +HoTen lúc này không cần con trỏ this trỏ đến vì không có tham số nào trùng tên với nó và có thể dùng trực tiếp, trong khi đó thì tên tham số `MaSo` của constructor trùng với tên thuộc tính `MaSo` của class đó, nên cần sử dụng con trỏ `this` để phân biệt. + +Ngoài ra bạn có thể tạo constructor như sau. + + HocSinh(int MaSoHS, string HoVaTen):MaSo(MaSoHS),HoTen(HoVaTen) + { + } +### 1.2 Default constructors ### +Đây là constructor không có tham số, khá đơn giản để cài đặt. + + class HocSinh + { + private: + int MaSo; + string HoTen; + public: + HocSinh() //Default constructors + { + + } + }; +Có thể không cần cài đặt. Compiler đã tự động tạo sẵn một `Default constructors`, vì vậy bạn có thể thấy là không cần cài đặt bất cứ constructor nào mà vẫn tạo được đối tượng là vì Compiler đã tạo sẵn `Default constructors`. Nếu chúng ta muốn thay đổi `Default constructors` thì chỉ cần sửa đổi bên trong constructor là được. + + class HocSinh + { + private: + int MaSo; + string HoTen; + public: + HocSinh() + { + MaSo = 0; + } + }; + +Tốt nhất là luôn tự cài đặt các constructor, đừng nhờ vả đến compiler, vì có thể gây lỗi không đồng bộ khi chuyển code qua các compiler khác để build. +### 1.3 Copy constructors ### +Copy constructors là một dạng "nhái" các thuộc tính của một đối tượng này cho một đối tượng khác. + + HocSinh hs1(186,"ltd"); + HocSinh hs2(hs1); //Copy constructors + +Lúc này `hs2` sẽ có thuộc tính giống như hs1, nhưng copy constructor có 2 loại là `deep copy` và `shallow copy`, vì vậy việc copy cũng khác nhau đôi chút. +Shallow copy là copy một cách "nông cạn", chỉ nhái những thứ có thể trực tiếp nhìn thấy được. +Deep copy là copy hoàn toàn tất cả nội dung của đối tượng kia. + + class HocSinh + { + private: + int MaSo; + string HoTen; + HocSinh* Ban; + public: + HocSinh(HocSinh& that) + { + this->MaSo = that.MaSo; + this->HoTen = that.HoTen; + this->Ban = that.Ban; + } + }; + +Đây là một shallow copy thông thường, khá nguy hiểm vì khi này 2 đối tượng có thể dùng chung một vùng nhớ Ban. Chúng ta cần copy một cách rõ hơn và tách biệt giữa 2 đối tượng này. + + HocSinh(HocSinh& that) + { + this->MaSo = that.MaSo; + this->HoTen = that.HoTen; + this->Ban = new HocSinh(*that.Ban); + } +## 2. Phương thức hủy (DESTRUCTOR) ## +Tương tự như constructor nhưng destructor được gọi tự động ghi phương thức bị hủy hoặc ra ngoài scope của nó. + + class + { + ~(); //destructor + }; +Phương thức destructor sẽ không có tham số, thường trong destructor chúng ta sẽ cài đặt để trả các vùng nhớ mà chúng ta xin cấp phát. +## 3. Định nghĩa toán tử (OPERATOR OVERLOADING) ## +Operator không hề xa lạ với các bạn. + + int a = 3; + int b = 4; + int c = a + b; +Operator chính là các phép toán mà ngôn ngữ hỗ trợ `(+,-,*,/,...)`, với mỗi kiểu dữ liệu chúng ta cần định nghĩa các operator cho nó (ví dụ như kiểu int đã được C++ định nghĩa sẵn kèm theo các operator để chúng ta có thể thực hiện được phép cộng). Ở phần này chúng ta sẽ cài đặt các operator cho class. + +Cú pháp chung cho việc cài đặt các toán tử như sau. + + operator(); +Ví dụ + + HocSinh& operator=(const HocSinh& that) + { + this->HoTen = that.HoTen; + this->MaSo = that.MaSo; + return *this; + } + +Có các toán tử sau không thể cài đặt chồng là + +> `.` (toán tử lấy thành phàn trực tiếp) +> +> `?:` (toán tử điều kiện) +> +> `sizeof` (toán tử lấy kích thước) +> +> `::` (toán tử chỉ phạm vi) +> +> `.*` (toán tử lấy thành phần thông qua con trỏ) +> +> `typeid` (toán tử lấy kiểu dữ liệu) + +Lưu ý về cài đặt toán tử nhập xuất, chúng ta cần khai báo như sau. + +friend ostream& HocSinh::operator<<(ostream &out, const HocSinh& src) + +Ở đây có 2 thứ cần chú ý: +1. Chúng ta sử dụng từ khóa friend do toán tử xuất được viết chồng bằng hàm ở bên ngoài nên để tủy cập được các thành phần private bên trong lớp HocSinh chúng ta phải sử dụng từ khóa friend. +2. Cần truyền tham chiếu đối tượng ostream (đây là đối tượng dùng để xuất ra màn hình). + diff --git a/03PolymophismInheritanceAndEncapsulation.md b/03PolymophismInheritanceAndEncapsulation.md new file mode 100644 index 0000000..03771de --- /dev/null +++ b/03PolymophismInheritanceAndEncapsulation.md @@ -0,0 +1,256 @@ +<<<<<<< HEAD +# 3. Đa hình, kế thừa và đóng gói # +**Dẫn nhập:** Ở phần này chúng ta sẽ tìm hiểu về các tính chất cơ bản của OOP bao gồm đa hình (polymophism), kế thừa (inheritance) và đóng gói (encapsulation). Đây là các thành phần cơ bản và cực kì hữu ích giúp cho việc phát triển một chương trình bằng OOP trở nên thuận tiện và dễ dàng trong việc sửa đổi mã nguồn. +## 6.1 Đa hình và kế thừa ## +Chúng ta lấy ví dụ về HocSinh, trong một lớp học, sẽ có nhiều chức vụ khác nhau cho HocSinh, ví dụ như lớp trưởng, lớp phó học tập,... Vậy khi cần triển khai các lớp cho mỗi loại HocSinh này chúng ta phải cài đặt từng lớp đối tượng LopTruong, LopPhoHocTap,... mặc dù các lớp này đều có chung một bản chất là HocSinh? + +Đa hình và kế thừa sẽ giúp chúng ta tận dụng lại mã nguồn của lớp HocSinh (ví dụ như những phương thức HocBai(), DiemDanh(),... hay các thuộc tính HoTen, MaSo,...) + + class HocSinh + { + protected: + int MaSo; + string HoTen; + string ChucDanh; + public: + void HocBai(); + void DiemDanh(); + + virtual string getChucDanh(); + }; + + class LopTruong : HocSinh + { + public: + void BaoCao(); + + string getChucDanh(); + }; + + class LopPhoHocTap : HocSinh + { + public: + void ThongBao(); + + string getChucDanh(); + }; +Ở ví dụ trên, lớp LopTruong sẽ có mọi thuộc tính và phương thức của HocSinh ngoài ra còn có phương thức BaoCao(). Ta gọi LopTruong là lớp được kế thừa từ lớp HocSinh. Lớp LopPhoHocTap cũng tương tự. + +Các thuộc tính được kế thừa được nằm dưới từ khóa protected, từ khóa này tương đương với private (tức là các lớp khác không thể truy cập đến các phương thức hay thuộc tính nằm dưới từ khóa private) ngoài ra nó còn cho phép các lớp con kế thừa các thuộc tính hay phương thức nằm dưới từ khóa protected ( + +Tính đa hình được thể hiện khá rõ ở ví dụ trên qua phương thức getChucDanh(), phương thức này sẽ trả về chức danh của đối tượng. Với từng loại HocSinh sẽ có chức danh khác nhau, đó chính là tính đa hình trong OOP. Để có cơ chế đa hình này thì lớp cha cần sử dụng từ khóa virtual, chúng ta sẽ tìm hiểu ở phần dưới. + +Ngoài điểm mạnh về việc tận dụng mã nguồn đã được cài đặt, nó còn giúp cho việc đồng bộ giữa các đối tượng có cùng cha. + +Ví dụ như LopTruong và LopPhoHocTap đều có HocBai giống với HocSinh, khi giáo viên yêu cầu HocSinh học thế nào thì chỉ cần sửa mã nguồn HocBai của HocSinh thì các đối tượng được kế thừa đều thực hiện HocBai như vậy. + +## 6.2 Lớp trừu tượng, phương thức ảo và phương thức thuần ảo ## +Chúng ta sẽ tìm hiểu cách sử dụng. + + class HocSinh + { + protected: + int MaSo; + string HoTen; + public: + void HocBai(); + void DiemDanh(); + + virtual string getChucDanh() + { + return "Hoc Sinh"; + } + }; + + class LopTruong : HocSinh + { + public: + void BaoCao(); + + string getChucDanh() + { + return "Lop Truong"; + } + }; + + int main() + { + HocSinh* pHS; + LopTruong lt; + HocSinh hs; + + pHS = &hs; + cout << pHS->getChucDanh(); + + pHS = < + cout << pHS->getChucDanh(); + + return 0; + } + +Nhờ có từ khóa virtual mà khi pHS gọi getChucDanh() sau khi được trở thành LopTruong sẽ có chức danh "Lop Truong". Và ta gọi phương thức này là phương thức ảo. Nếu như không có từ khóa virtual này thì khi getChucDanh sẽ vẫn là chức danh "Hoc Sinh". + +Vậy thì phương thức "thuần" ảo là gì? Phương thức thuần ảo là phương thức mà cha sẽ không định nghĩa cho nó, mà các lớp con sẽ phải định nghĩa cho nó. + + class HocSinh + { + protected: + int MaSo; + string HoTen; + public: + void HocBai(); + void DiemDanh(); + + virtual string getChucDanh() = 0; //phuong thuc thuan ao + }; + + class LopTruong : HocSinh + { + public: + void BaoCao(); + + string getChucDanh() + { + return "Lop Truong"; + } + }; + +Phương thức thuần ảo được khai báo như sau `virtual = 0;` lúc này dẫn đến HocSinh trở thành một lớp trừu tượng. Lớp trừu tượng khác với lớp bình thường ở một số đặc điểm sau: + + +1. Không thể tạo một đối tượng từ lớp trừu tượng, mà ta đã biết một đối tượng sẽ có vùng nhớ của nó, nếu không thể tạo được vùng nhớ thì nó chỉ có thể là một con trỏ. Và ta có thể hiểu lớp trừu tượng chỉ là "khái niệm" chứ không có đối tượng cụ thể. +2. Nếu có một phương thức ảo trong lớp thì lớp đó sẽ trở thành lớp trừu tượng. +3. Các lớp con kế thừa từ lớp trừu tượng bắt buộc phải định nghĩa các phương thức thuần ảo. + +## 6.3 Đóng gói ## +Đóng gói một lớp ở đây chính là bảo vệ dữ liệu của lớp đó. Bảo vệ ở việc truy cập dữ liệu và các thao tác dữ liệu. Nếu cần lấy hoặc sửa đổi dữ liệu thường thông qua các phương thức get và set (lấy và chỉnh sửa dữ liệu) để từ đó quản lý dữ liệu. + +======= +# 3. Đa hình, kế thừa và đóng gói # +**Dẫn nhập:** Ở phần này chúng ta sẽ tìm hiểu về các tính chất cơ bản của OOP bao gồm đa hình (polymophism), kế thừa (inheritance) và đóng gói (encapsulation). Đây là các thành phần cơ bản và cực kì hữu ích giúp cho việc phát triển một chương trình bằng OOP trở nên thuận tiện và dễ dàng trong việc sửa đổi mã nguồn. +## 6.1 Đa hình và kế thừa ## +Chúng ta lấy ví dụ về HocSinh, trong một lớp học, sẽ có nhiều chức vụ khác nhau cho HocSinh, ví dụ như lớp trưởng, lớp phó học tập,... Vậy khi cần triển khai các lớp cho mỗi loại HocSinh này chúng ta phải cài đặt từng lớp đối tượng LopTruong, LopPhoHocTap,... mặc dù các lớp này đều có chung một bản chất là HocSinh? + +Đa hình và kế thừa sẽ giúp chúng ta tận dụng lại mã nguồn của lớp HocSinh (ví dụ như những phương thức HocBai(), DiemDanh(),... hay các thuộc tính HoTen, MaSo,...) + + class HocSinh + { + protected: + int MaSo; + string HoTen; + string ChucDanh; + public: + void HocBai(); + void DiemDanh(); + + virtual string getChucDanh(); + }; + + class LopTruong : HocSinh + { + public: + void BaoCao(); + + string getChucDanh(); + }; + + class LopPhoHocTap : HocSinh + { + public: + void ThongBao(); + + string getChucDanh(); + }; +Ở ví dụ trên, lớp LopTruong sẽ có mọi thuộc tính và phương thức của HocSinh ngoài ra còn có phương thức BaoCao(). Ta gọi LopTruong là lớp được kế thừa từ lớp HocSinh. Lớp LopPhoHocTap cũng tương tự. + +Các thuộc tính được kế thừa được nằm dưới từ khóa protected, từ khóa này tương đương với private (tức là các lớp khác không thể truy cập đến các phương thức hay thuộc tính nằm dưới từ khóa private) ngoài ra nó còn cho phép các lớp con kế thừa các thuộc tính hay phương thức nằm dưới từ khóa protected ( + +Tính đa hình được thể hiện khá rõ ở ví dụ trên qua phương thức getChucDanh(), phương thức này sẽ trả về chức danh của đối tượng. Với từng loại HocSinh sẽ có chức danh khác nhau, đó chính là tính đa hình trong OOP. Để có cơ chế đa hình này thì lớp cha cần sử dụng từ khóa virtual, chúng ta sẽ tìm hiểu ở phần dưới. + +Ngoài điểm mạnh về việc tận dụng mã nguồn đã được cài đặt, nó còn giúp cho việc đồng bộ giữa các đối tượng có cùng cha. + +Ví dụ như LopTruong và LopPhoHocTap đều có HocBai giống với HocSinh, khi giáo viên yêu cầu HocSinh học thế nào thì chỉ cần sửa mã nguồn HocBai của HocSinh thì các đối tượng được kế thừa đều thực hiện HocBai như vậy. + +## 6.2 Lớp trừu tượng, phương thức ảo và phương thức thuần ảo ## +Chúng ta sẽ tìm hiểu cách sử dụng. + + class HocSinh + { + protected: + int MaSo; + string HoTen; + public: + void HocBai(); + void DiemDanh(); + + virtual string getChucDanh() + { + return "Hoc Sinh"; + } + }; + + class LopTruong : HocSinh + { + public: + void BaoCao(); + + string getChucDanh() + { + return "Lop Truong"; + } + }; + + int main() + { + HocSinh* pHS; + LopTruong lt; + HocSinh hs; + + pHS = &hs; + cout << pHS->getChucDanh(); + + pHS = < + cout << pHS->getChucDanh(); + + return 0; + } + +Nhờ có từ khóa virtual mà khi pHS gọi getChucDanh() sau khi được trở thành LopTruong sẽ có chức danh "Lop Truong". Và ta gọi phương thức này là phương thức ảo. Nếu như không có từ khóa virtual này thì khi getChucDanh sẽ vẫn là chức danh "Hoc Sinh". + +Vậy thì phương thức "thuần" ảo là gì? Phương thức thuần ảo là phương thức mà cha sẽ không định nghĩa cho nó, mà các lớp con sẽ phải định nghĩa cho nó. + + class HocSinh + { + protected: + int MaSo; + string HoTen; + public: + void HocBai(); + void DiemDanh(); + + virtual string getChucDanh() = 0; //phuong thuc thuan ao + }; + + class LopTruong : HocSinh + { + public: + void BaoCao(); + + string getChucDanh() + { + return "Lop Truong"; + } + }; + +Phương thức thuần ảo được khai báo như sau `virtual = 0;` lúc này dẫn đến HocSinh trở thành một lớp trừu tượng. Lớp trừu tượng khác với lớp bình thường ở một số đặc điểm sau: + + +1. Không thể tạo một đối tượng từ lớp trừu tượng, mà ta đã biết một đối tượng sẽ có vùng nhớ của nó, nếu không thể tạo được vùng nhớ thì nó chỉ có thể là một con trỏ. Và ta có thể hiểu lớp trừu tượng chỉ là "khái niệm" chứ không có đối tượng cụ thể. +2. Nếu có một phương thức ảo trong lớp thì lớp đó sẽ trở thành lớp trừu tượng. +3. Các lớp con kế thừa từ lớp trừu tượng bắt buộc phải định nghĩa các phương thức thuần ảo. + +## 6.3 Đóng gói ## +Đóng gói một lớp ở đây chính là bảo vệ dữ liệu của lớp đó. Bảo vệ ở việc truy cập dữ liệu và các thao tác dữ liệu. Nếu cần lấy hoặc sửa đổi dữ liệu thường thông qua các phương thức get và set (lấy và chỉnh sửa dữ liệu) để từ đó quản lý dữ liệu. + +>>>>>>> origin/master +Bảo vệ bản thân lập trình viên tránh khỏi các sai sót khi truy cập giữa các class khác nhau. Nên nhớ rằng OOP là một công cụ "hỗ trợ" lập trình viên, đừng nên cố gắng hoặc lách luật các tính chất của OOP. \ No newline at end of file diff --git a/04ParameterizedType.md b/04ParameterizedType.md new file mode 100644 index 0000000..a7ea162 --- /dev/null +++ b/04ParameterizedType.md @@ -0,0 +1,50 @@ +# 4. Tham số hóa kiểu dữ liệu # +**Dẫn nhập:** OOP chính là một cách để phát triển phần mềm có thể mở rộng một cách dễ dành, chính vì vậy việc tham số hóa kiểu dữ liệu để có thể chỉnh sửa các kiểu dữ liệu bên trong lớp một cách hợp lý là một điều cần thiết. +## 4.1 Tham số hóa cho hàm ## +Đôi khi bạn phải thực hiện nhiều tác vụ tương tự nhau cho từng kiểu dữ liệu. + + int add(int a, int b) + { + return a+b; + } + long add(long a, long b) + { + return a+b; + } +Để có thể tận dụng lại mã nguồn chung ta có thể tham số hóa cho hàm trên, thay vì viết 2 hàm, chúng ta chỉ cần viết một hàm. + + template + T add(T a, T b) + { + return a+b; + } + +Chỉ cần như vậy chúng ta có thể tham số hóa cho tất cả các lớp đã định nghĩa toán tử +. +## 4.2 Tham số hóa cho lớp ## +Tham số hóa cho lớp cũng gần tương tự như tham số hóa cho hàm, nếu các bạn đã từng sài đến kiểu dữ liệu vector, kiểu dữ liệu dùng để quản lý mảng, bạn sẽ thấy kiểu vector có cấu trúc khai báo như sau, ví dụ cho khai báo cấu trúc vector gồm các phần tử số nguyên: + + vector vt; + +Chúng ta thông báo cho đối tượng vector vt sẽ quản lý các phần tử int. + +Để có thể tham số hóa cho một kiểu dữ liệu bất kì, các bạn cài đặt như sau: + + template + class tenclass + { + T thuoctinh; + T phuongthuc(T thamso); + } + +Sử dụng T như một kiểu bất kì, các bạn cũng có thể tham số hóa nhiều hơn nữa. + + template + class tenclass + { + T thuoctinh; + T phuongthuc(Z thamso); + } + +Lưu ý Z và T có thể không cùng kiểu dữ liệu với nhau. + +Trong quá trình compile trên Visual Studio các bạn có thể gặp lỗi khi tham số hóa (lỗi linker), bạn cần đưa tất cả mã cài đặt phương thức của class đó vào cùng file .h của class bạn cài đặt. \ No newline at end of file