Tài liệu

Hàm Khởi Tạo Và Hàm Huỷ

Huy Erick

Chào cả nhà! Trong bài viết này, chúng ta sẽ tìm hiểu về hai hàm thành viên quan trọng của lớp - hàm khởi tạo và hàm huỷ. Hàm Khởi Tạo (Constructor) Hàm khởi tạo...

Chào cả nhà! Trong bài viết này, chúng ta sẽ tìm hiểu về hai hàm thành viên quan trọng của lớp - hàm khởi tạo và hàm huỷ.

Hàm Khởi Tạo (Constructor)

Hàm khởi tạo là gì?

Hàm khởi tạo là một hàm thành viên đặc biệt của một lớp. Nó sẽ được tự động gọi khi một đối tượng của lớp đó được khởi tạo.

Sự khác biệt giữa hàm tạo và hàm thành viên thông thường

Một hàm tạo khác với các 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 không khai báo một hàm tạo, trình biên dịch sẽ tự động tạo một hàm tạo mặc định cho chúng ta (hàm không có tham số và có phần thân trống).

Hàm tạo có thể rất hữu ích để thiết lập các giá trị khởi tạo cho các biến thành viên cụ thể.

Ví dụ đơn giản về hàm khởi tạo:

class sinhvien {      private:          string ten;          int tuoi;      public:          sinhvien(); // Đây là hàm khởi tạo          ~sinhvien();  };

Các loại hàm khởi tạo

Hàm khởi tạo về cơ bản đượ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).

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

Hàm tạo loại này không truyền vào bất kỳ một đối số nào.

class sinhvien {      private:          string ten;          int tuoi;      public:          sinhvien() {              this->ten = "";              this->tuoi = 0;          }          ~sinhvien();  }

Trong ví dụ trên, hàm tạo sinhvien() không có đối số nào được truyền vào. Theo ý kiến riêng của mình, thông thường trong hàm loại này, mình sẽ gán cho tất cả các thuộc tính giá trị mặc định.

Trong ví dụ trên:

  • Thuộc tính ten thuộc kiểu string sẽ được gán giá trị mặc định là một chuỗi rỗng "".
  • Thuộc tính tuoi thuộc kiểu int sẽ được gán giá trị mặc định là 0.

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

Với loại hàm khởi tạo này, ta có thể truyền đối số và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 như cách bạn thêm tham số vào bất kỳ hàm nào. Khi 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.

class sinhvien {      private:          string ten;          int tuoi;      public:          sinhvien(string param_ten, int param_tuoi) {              this->ten = param_ten;              this->tuoi = param_tuoi;          }          ~sinhvien();  };

Sau khi khai báo hàm trong lớp, ta có thể dễ dàng sử dụng nó bằng cách truyền tham số trong quá trình khởi tạo đối tượng.

int main() {      sinhvien obj("Lap Trinh Khong Kho", 5); // Ta truyền luôn tham số trong quá trình khởi tạo đối tượng  }
  • 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ách khai báo đối tượng bình thường có thể gây lỗi. Điều này có nghĩa là thay vì khai báo một đối tượng bình thường như sau:

        sinhvien obj;

      Ta phải khai báo như sau:

        sinhvien obj("Lap Trinh Khong Kho", 5);
    • 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. Ví dụ:

        sinhvien obj = sinhvien("Lap Trinh Khong Kho", 5); // Đây là cách rõ ràng   sinhvien obj("Lap Trinh Khong Kho", 5); // Đây là cách ngầm định

      Thông thường, để tiết kiệm code, chúng ta thường sử dụng cách ngầm định hơn.

Công dụng của hàm khởi tạo có tham số
  • Nó được sử dụng để 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ó được sử dụng để nạp chồng các hàm khởi tạo. Nạp chồng có nghĩa là ta có nhiều hơn một hàm khởi tạo trong cùng một lớp.

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

Hàm khởi tạo sao chép là gì?

Hàm khởi tạo sao chép là một hàm tạo mà tạo một đối tượng bằng việc khởi tạo nó với một đối tượng của cùng lớp, đã được tạo trước đó.

Một hàm khởi tạo sao chép sẽ có nguyên mẫu chung 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.

Ví dụ đơn giản về hàm khởi tạo sao chép:

#include   using namespace std;   class Point {      private:          int x, y;      public:          Point(int x1, int y1)          {              x = x1;              y = y1;          }           // Hàm khởi tạo sao chép          Point(const Point &p2)          {              x = p2.x;              y = p2.y;          }           int getX()          {              return x;          }          int getY()          {              return y;          }  };   int main()  {      Point p1(10, 15); // Hàm khởi tạo có tham số thông thường       Point p2 = p1; // hàm khởi tạo sao chép được gọi ở đây       cout  "p1.x = "  p1.getX()  ", p1.y = "  p1.getY()  endl;      cout  "p2.x = "  p2.getX()  ", p2.y = "  p2.getY()  endl;       return 0;  }

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

p1.x = 10, p1.y = 15 p2.x = 10, p2.y = 15

Một hàm khởi tạo sao chép sẽ được gọi khi:

  1. Một đối tượng của lớp được trả về bằng một giá trị.
  2. 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.
  3. Một đối tượng được tạo ra dựa trên một đối tượng khác trong cùng lớp.
  4. Trình biên dịch tạo một đối tượng tạm thời.

Tuy nhiên, không chắc chắn rằng hàm khởi tạo sao chép sẽ được gọi trong tất cả 4 trường hợp trên. Vì C++ tiêu chuẩn cho phép trình biên dịch tối ưu hóa bản sao trong một số trường hợp nhất định.

Một ví dụ cho điều này là tối ưu hoá giá trị trả về (có thể gọi tắt là RVO - Return Value Optimization). Tại đây

  • Lưu ý:
    • Nếu một hàm khởi tạo sao chép không được định nghĩa trong một lớp, trình biên dịch sẽ tự động định nghĩa nó. Vì vậy, nếu lớp có các biến con trỏ hoặc sử dụng cấp phát bộ nhớ động, chúng ta nên viết lại hàm khởi tạo sao chép.
    • Chúng ta nên đưa các thuộc tính của lớp về giá trị mặc định trong hàm khởi tạo sao chép, dù có sử dụng con trỏ hay cấp phát động hay không.

Hàm Huỷ (Destructor)

Hàm huỷ là gì?

Hàm huỷ cũng là một hàm thành viên đặc biệt giống như hàm khởi tạo, nó được sử dụng để phá huỷ hoặc xoá một đối tượng trong lớp.

Hàm huỷ sẽ được gọi khi nào?

Hàm huỷ sẽ được gọi tự động khi một đối tượng thoát khỏi phạm vi của nó (Scope):

  1. Một chức năng kết thúc.
  2. Chương trình kết thúc.
  3. Một khối chứa các biến cục bộ kết thúc.
  4. Một toán tử delete được gọi.

Hàm huỷ khác với các hàm thành viên bình thường ở điểm nào?

  • Giống với hàm khởi tạo, hàm huỷ có tên trùng với tên của lớp, nhưng điều khác biệt ở đây là sẽ có thêm ~ đặt ở đầu tên hàm.

  • Hàm huỷ là một hàm không có đối số truyền vào và cũng không trả về giá trị (kể cả kiểu void).

class sinhvien {      private:          string ten;          int tuoi;      public:          sinhvien() {              this->ten = "";              this->tuoi = 0;          }          ~sinhvien() // Đây là hàm huỷ         {              this->ten = "";              this->tuoi = 0;          }  }

Có thể có nhiều hơn một hàm huỷ trong cùng một lớp không?

Không. Khác với hàm khởi tạo, một lớp chỉ có thể có một hàm huỷ.

Khi nào thì ta cần tự định nghĩa một hàm huỷ?

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

Với bản thân mình, thông thường mình sẽ khai báo một hàm huỷ cho dù có sử dụng con trỏ hay cấp phát động hay không. Trong hàm huỷ đó, mình sẽ đưa các thuộc tính của lớp về giá trị mặc định (giống với hàm khởi tạo không tham số).

Một hàm huỷ có thể là một hàm ảo hay không?

Tất nhiên là có. Và bạn có thể xem chi tiết tại đây.

Bài viết của mình xin được kết thúc tại đây. Mọi thắc mắc và ý kiến, các bạn có thể để lại comment ở bên dưới nếu thấy bất cứ điều gì không chính xác hoặc đơn giản là muốn chia sẻ thêm kiến thức với mọi người. Cảm ơn mọi người. Chào tạm biệt và hẹn gặp lại!

Tài liệu tham khảo

  1. Geeksforgeeks
  2. Tutorialspoint
1