Tất Tần Tật Về Lập Trình Hướng Đối Tượng? (P3)

Tất Tần Tật Về Lập Trình Hướng Đối Tượng? (P3)

Chúng ta đã được biết về lớp, đối tượng và các cách xác định hàm ở bài trước. Trong bài này mình sẽ tìm hiểu về hàm khởi tạo và hàm hủy trong C++ là gì và khác gì so với các hàm thông thường nhé.

Bài viết sẽ sử dụng C++ là ngôn ngữ chính để giải thích và code minh họa cho OOP nên chúng ta cần hiểu rõ một đặc tính của C++ trong lập trình hướng đối tượng.

1. Hàm khởi tạo (Constructor):

Hàm khởi tạo (constructor) là một phương thức đặc biệt được gọi tự động tại thời điểm đối tượng được tạo. Mục đích của hàm khởi tạo là để khởi tạo các thành viên dữ liệu của đối tượng.

Một hàm khởi tạo sẽ khác những hàm thông thường ở những điểm sau:

  • Có tên trùng với tên lớp
  • Không có kiểu dữ liệu trả về ( kể cả kiểu void)
  • Tự động được gọi khi một đối tượng thuộc lớp được tạo ra
  • Nếu chúng ta không khai báo một hàm khởi tạo, trình biên dịch C++ sẽ tự động tạo một hàm khởi tạo mặc định cho chúng ta (sẽ là hàm ​​không có tham số nào và có phần thân trống).

Ví dụ ta có lớp Mayvitinh có 2 thuộc tính là chieudaimausac, thì hàm khởi tạo có thể định nghĩa cho lớp Mayvitinh như sau:

class Mayvitinh{
    int chieudai;
    string mausac;
    public:
        Mayvitinh();                   // Đây là hàm khởi tạo
        Mayvitinh(int cd) {
            chieudai = cd;
        }
        Mayvitinh(string ms) {         // Đây là hàm khởi tạo
            mausac = ms;
        }
        Mayvitinh(int cd, string ms) { // Đây là hàm khởi tạo
            chieudai = cd;
            mausac = ms;
        }
};

Hàm khởi tạo về cơ bản sẽ được chia làm 3 loại:

  1. Hàm khởi tạo không tham số (Cũng có thể gọi là hàm tạo mặc định – Default Constructor)
  2. Hàm khởi tạo có tham số (Parameterized Constructor)
  3. Hàm khởi tạo sao chép (Copy Constructor)

1.1. Hàm khởi tạo không tham số (Default Constructor):

Mình lấy một ví dụ:

#include <iostream>  
using namespace std;  
class Mayvitinh {  
   public:  
        Mayvitinh() {    
            cout << "Ham khoi tao tu dong duoc goi." << endl;    
        }    
};  
 
int main() {  
    Mayvitinh mayAsus;     //khởi tạo đối tượng mayAsus
    Mayvitinh mayAcer;     //khởi tạo đối tượng mayAcer
    return 0;  
}

Khi chạy chương trình ta sẽ có kết quả như sau:

Trong ví dụ trên, hàm tạo Mayvitinh() không hề có đối số nào được truyền vào. 

1.2. Hàm khởi tạo có tham số (Parameterized Constructor):

Với loại hàm tạo này ta có thể truyền đối số cho chúng. Thông thường, các đối số này giúp khởi tạo một đối tượng khi nó được tạo.

Để khai báo một hàm khởi tạo có tham số chỉ cần thêm các tham số vào nó giống như cách bạn thêm tham số bất kỳ hàm nào khác. Khi bạn xác định phần thân của hàm tạo, hãy sử dụng các tham số để khởi tạo đối tượng.

Chúng ta cùng xem xét một ví dụ đơn giản về hàm khởi tạo có tham số trong C++ như sau:

#include <iostream>  
using namespace std;  
class Mayvitinh { 
    int chieurong;   
    int chieudai; 
    string tenmay;
    public:  
       Mayvitinh(int cd) {    
            chieudai = cd;
       }
       Mayvitinh(int cd, string tm) {    
            chieudai = cd;    
            tenmay = tm;
            chieurong = 20;
       }
       Mayvitinh(int cd, string tm, int cr) {    
            chieudai = cd;    
            tenmay = tm;    
            chieurong = cr; 
       }    
       void HienThi() {    
            cout << tenmay << endl;
            cout << "   Chieu dai: " << chieudai << endl;
            cout << "   Chieu rong: " << chieurong << endl;
       }    
};  
  
int main() {  
    Mayvitinh mayAsus =  Mayvitinh(50, "may Asus", 25);    
    Mayvitinh mayAcer =  Mayvitinh(45, "mau Acer");
    Mayvitinh mayMac =  Mayvitinh(30, "may Mac");
    mayAsus.HienThi();    
    mayAcer.HienThi();
    mayMac.HienThi();
    return 0;  
}

Sau khi chạy chương trình ta sẽ có kết quả:

Lưu ý:

  • Khi một đối tượng được khai báo trong hàm khởi tạo có tham số, các giá trị ban đầu phải được truyền dưới dạng đối số cho hàm tạo.
  • Các hàm khởi tạo có thể được gọi một cách rõ ràng hoặc ngầm định.
Mayvitinh mayAcer = Mayvitinh("may Acer", 25); // Đây là cách rõ ràng
Mayvitinh mayAcer("May Acer", 25);             // Đây là cách ngầm định

Công dụng của hàm khởi tạo có tham số

  • Khởi tạo các thành phần dữ liệu khác nhau của các đối tượng khác nhau với các giá trị khác nhau khi chúng được tạo.
  • Nạp chồng các hàm khởi tạo. (Có thế các bạn chưa hiểu "nạp chồng" là gì phải không? Nói nôm na là ta sẽ có nhiều hơn một hàm khởi tạo trong cùng một lớp. Mình sẽ giải thích rõ hơn về vấn đề này ở bài sau, các bạn nhớ đón xem nhé.)

1.3. Hàm khởi tạo sao chép (Copy Constructor):

Hàm khởi tạo sao chép (Copy Constructor) là một hàm xây dựng được sử dụng để khai báo và khởi tạo một đối tượng từ một đối tượng khác.

Cú pháp của hàm khởi tạo sao chép (Copy Constructor) như sau:

ClassName(const ClassName &old_obj){
    // Code
}

Trong đó Classname là tên của lớp, old_obj là đối tượng cũ sẽ lấy làm gốc để sao chép sang đối tượng mới.

Chúng ta lấy ví dụ đơn giản về hàm khởi tạo sao chép nhé:

#include <iostream>  
using namespace std;  
class Mayvitinh { 
    int chieurong;   
    int chieudai; 
    string tenmay;
    public:  
       Mayvitinh(int cd, string tm, int cr) {    
            chieudai = cd;    
            tenmay = tm;    
            chieurong = cr; 
       }    
       Mayvitinh(Mayvitinh &m) {
           tenmay = m.tenmay;
           chieudai = m.chieudai;
           chieurong = m.chieurong;
       }
       void HienThi() {    
            cout << tenmay << endl;
            cout << "   Chieu dai: " << chieudai << endl;
            cout << "   Chieu rong: " << chieurong << endl;
       }    
};  
  
int main() {  
    Mayvitinh mayAsus(50, "may Asus", 25);    
    Mayvitinh mayAcer(mayAsus);
    mayAsus.HienThi();    
    mayAcer.HienThi();
    return 0;  
}

Và kết quả sau khi thực thi chương trình trên như sau:

Hàm khởi tạo sao chép (Copy Constructor) được gọi trong các trường hợp sau đây:

  • Khi một đối tượng của lớp được trả về bằng một giá trị.
  • Khi một đối tượng của lớp được truyền đối số dưới dạng tham số của một hàm.
  • Khi một đối tượng được tạo ra dựa trên một đối tượng khác cùng lớp.

Ví dụ: Mayvitinh mayAcer = mayAsus

  • Khi trình biên dịch tạo một đối tượng tạm thời.

Lưu ý:

Bất cứ khi nào chúng ta định nghĩa một hoặc nhiều hàm tạo có tham số cho một lớp, hàm tạo mặc định (không có tham số) cũng phải được xác định rõ ràng vì trình biên dịch sẽ không cung cấp hàm tạo mặc định trong trường hợp này. Điều đó tuy không cần thiết nhưng nó được coi là cách tốt nhất để luôn xác định một hàm tạo mặc định.

2. Hàm hủy (Desconstructor):

Hàm hủy (Destructorngược lại với hàm khởi tạo, trong khi hàm khởi tạo dùng để khởi tạo giá trị cho đối tượng thì hàm hủy dùng để hủy đối tượng.

Chỉ có duy nhất một hàm hủy trong một lớp. Hàm hủy tự động được gọi. Nếu như chúng ta không định nghĩa hàm hủy thì mặc định trình biên dịch sẽ tự tạo ra một hàm hủy mặc nhiên.

Cũng giống như hàm xây dựng, hàm hủy được định nghĩa có cùng tên với tên lớp, khôn có bất cứ kiểu gì trả về kể cẳ kiểu void, tuy nhiên phải có dấu ~ trước tên của hàm hủy.

Cú pháp của hàm hủy (Destructor) như sau:

~Classname() { };

Chúng ta cùng xem xét một ví dụ đơn giản nhất về hàm hủy như sau:

#include <iostream>  
using namespace std;  
class Mayvitinh  {  
   public:  
        Mayvitinh() {    
            cout << "Ham khoi tao duoc goi" << endl;    
        }    
        ~Mayvitinh() {    
            cout << "Ham huy duoc goi" << endl;    
        }  
};  
int main() {  
    Mayvitinh mayAsus;   
    Mayvitinh mayAcer; 
    return 0;  
}

Và sau khi chạy chương trình sẽ có kết quả như sau: 

Như trong code trên, ta có thể thấy hàm hủy (Destructor) sẽ được gọi tự động khi đối tượng đi ra khỏi phạm vi:

  • Kết thúc hàm
  • Kết thúc chương trình
  • Kết thúc một block
  • Toán tử delete được gọi

Và có hai hạn chế khi sử dụng hàm hủy đó là:

  • Chúng ta không thể lấy địa chỉ của nó
  • Lớp con không có thừa kế hàm hủy từ lớp cha của nó

Với C++ thì nếu ta không khai báo một hàm hủy, trình biên dịch cũng sẽ tự định nghĩa một hàm hủy. Thông thường thì hàm hủy này hoạt động khá tốt, nhưng khi bài toán có sử dụng con trỏ, hoặc cấp phát bộ nhớ động thì ban nên khai báo một hàm huỷ riêng để tránh rỏ rỉ bộ nhớ.

Tạm kết

Như vậy là chúng ta đã tìm hiểu xong hàm khởi tạo (Constructor) và hàm hủy (Destructor), hai hàm này sẽ gắn liền xuyên suốt trong OOP.

Mình sẽ kết thúc bài này tại đây. Các bạn có thể rate và comment góp ý ở bên dưới nếu thấy bất cứ điều gì không chính xác để những bài sau tốt hơn.

Cảm ơn các bạn đã dành thời gian theo dõi.