Việc thực hiện các công việc như : khởi tạo giá trị các thuộc tính, cấp phát vùng nhớ cho con trỏ, mở tệp tin hay thu hồi vùng nhớ,… đều được constructor và destructor thực hiện trong OOP

Constructor

Hàm thiết lập (constructor) là 1 phương thức đặc biệt dùng để khởi tạo thể hiện (instance) của lớp.

Công dụng của constructor là khởi tạo các giá trị mặc định cho các thuộc tính hay các bước thiết lập cần thiết cho lớp (mở tệp, kết nối cơ sở dữ liệu,…).

Constructor có các quy tắc như sau:

  • Phạm vi truy xuất phải là public
  • Không có kiểu trả về (không phải void, cứ để trống)
  • Có tên trùng với tên lớp

Một số đặc tính của constructor là:

  • Có thể overloading
  • Có thể truyền tham số
  • Có thể sử dụng tham số ngầm định

Default constructor

Hàm thiết lập mặc định (default constructor) làm constructor không chứa tham số. Được gọi ngay khi khai báo biến lớp đó.

    #include <iostream>
    using namespace std;
     
    class PhanSo {
    	int TuSo;
    	int MauSo;
     
    	public:
    	PhanSo() { //default constructor
    		TuSo = 1;
    		MauSo = 1;
    	}
     
    	int getTuSo() {
    		return TuSo;
    	}
      	int getMauSo() {
    		return MauSo;
    	}
     
    };
     
    int main() {
    	PhanSo ps; //call constructor
     
    	cout << ps.getTuSo() << "/" << ps.getMauSo() << endl;
    	return 0;
    }

Chúng ta sẽ được kết quả in ra màn hình là: 1/1

Constructor có tham số

Nhờ đặc tính overloading, chúng ta có thể tạo ra thêm nhiều constructor khác nhau với số lượng và loại tham số khác nhau. Nó sẽ có ích trong trường hợp chúng ta muốn khởi tạo giá trị mong muốn cho lớp ngay khi tạo biến.

        #include <iostream>
        using namespace std;
     
        class PhanSo {
        	int TuSo;
        	int MauSo;
     
        	public:
        	PhanSo() { //default constructor
        		TuSo = 1;
        		MauSo = 1;
        	}
        	PhanSo(int t, int m) {
        		TuSo = t;
        		MauSo = m;
        	}
     
        	int getTuSo() {
        		return TuSo;
        	}
          	int getMauSo() {
        		return MauSo;
        	}
     
        };
     
        int main() {
        	PhanSo ps; //call default constructor
        	PhanSo ps1(1,2);
     
        	cout << ps1.getTuSo() << "/" << ps1.getMauSo() << endl;
        	return 0;
        }

Chúng ta sẽ được kết quả in ra màn hình là: 1/2

Lưu ý: nếu các bạn đã định nghĩa 1 (hoặc nhiều) constructor có tham số nhưng không định nghĩa constructor mặc định thì việc khởi tạo sau đây là không hợp lệ

        #include <iostream>
        using namespace std;
     
        class PhanSo {
        	int TuSo;
        	int MauSo;
     
        	public:
        	PhanSo(int t, int m = 1) {
        		TuSo = t;
        		MauSo = m;
        	}
     
        	int getTuSo() {
        		return TuSo;
        	}
          	int getMauSo() {
        		return MauSo;
        	}
     
        };
     
        int main() {
        	PhanSo ps; // Không được
        	PhanSo ps1(1,2);
     		PhanSo ps2(2); // Được do có tham số ngầm định: 2/1
        	cout << ps1.getTuSo() << "/" << ps1.getMauSo() << endl;
        	return 0;
        }

Vậy nên khi đã khởi tạo constructor có tham số các bạn nên phải viết cả constructor mặc định trong trường hợp muốn khởi tạo đối tượng mà không truyền tham số. Còn nếu các bạn không định nghĩa constructor nào, 1 constructor mặc định sẽ được ngầm tạo ra (và bên trong thân hàm không có gì cả).

Copy constructor

Chúng ta có thể tạo 1 kiểu hàm thiết lập sao chép để copy 1 số đặc điểm (do chúng ta quyết định) từ đối tượng này sang đối tượng khác.

        #include <iostream>
        using namespace std;
     
        class PhanSo {
        	int TuSo;
        	int MauSo;
     
        	public:
        	PhanSo() { //default constructor
        		TuSo = 1;
        		MauSo = 1;
        	}
        	PhanSo(int t, int m) {
        		TuSo = t;
        		MauSo = m;
        	}
        	PhanSo(const PhanSo& ps) {
        		this->TuSo = ps.TuSo;
        		this->MauSo = ps.MauSo;
        	}
     
        	int getTuSo() {
        		return TuSo;
        	}
          	int getMauSo() {
        		return MauSo;
        	}
     
        };
     
        int main() {
        	PhanSo ps; //call constructor
        	PhanSo ps1(1,2);
        	PhanSo ps3 (ps1);
        	cout << ps3.getTuSo() << "/" << ps3.getMauSo() << endl;
        	return 0;
        }

Phân số ps3 đã copy TuSo và MauSo từ ps2 thông qua copy constructor.

Constructor member initializer lists

Ngoài cách gán giá trị thông thường, chúng ta có thể dùng member initializer list như sau:

    #include <iostream>
    using namespace std;
     
    class Tester {
    	public: 
     
    	int value;
     	int value2;
  
    	Tester() {
    		value = 0;
    	}
  
     	//member initializer lists
    	Tester(int v): value(v), value2(v) {
    	}
     
    };
     
    int main() {
    	Tester tes(2);
    	cout << tes.value << "-" << tes.value2 << endl;
    	return 0;
    }

Chúng ta dùng toán tử : để truyền giá trị cho thuộc tính, các thuộc tính ngăn cách bằng dấu ,. Điều này giúp code chúng ta ngắn gọn hơn.

Nhờ member initialier list, chúng ta có thể gọi constructor từ 1 constructor khác, chẳng hạn:

    class Tester {
    	public: 
     
    	int value;
     	int value2;
  
    	Tester() {
    		value = 0;
    	}
  
     	//member initializer lists
    	Tester(int v): Tester() {
  		//do something here!
    	}
     
    };

Constructor Tester(int v) được gọi là delegating constructor (constructor gọi 1 constructor khác).

Destructor

Ngược lại với constructor, hàm hủy - destructor sẽ làm nhiệm vụ dọn dẹp mọi thứ khi lớp bị hủy.

Nếu lớp của bạn chỉ có những thuộc tính đơn giản (không cấp phát động) hoặc các phương thức đơn giản (không thao tác với file, không thao tác cơ sở dữ liệu) thì việc tạo 1 destructor là điều không cần thiết. Ta xét ví dụ dùng destructor khi lớp có cấp phát động như sau:

    #include <iostream>
    using namespace std;
     
    class Mang {
    	int *arr;
    	int lenght;
     
    	public:
    	~Mang() {
    		if(arr)
    			delete arr;
    	}
    };
     
    int main() {
    	// your code goes here
    	return 0;
    }

Destructor cũng có 1 số quy tắc như:

  • Thêm dấu ~ trước tên
  • Tên destructor phải trùng tên lớp
  • Không có kiểu trả về (như constructor)

Tổng kết

OK vậy là các bạn đã biết thế nào là constructor và destructor cùng cách sử dụng rồi nha! Nếu có thắc mắc các bạn bình luận bên dưới nhé. Pie~