Lập trình tân binh | 2.6. Lớp và con trỏ

Trong những bài học kinh nghiệm trước, tôi đã cố ý tránh sử dụng con trỏ cùng với những lớp. Thực ra, con trỏ trong C + + là 1 chủ đề khá rộng và nhạy cảm. Như những bạn hoàn toàn có thể nhận thấy, nếu thao tác với con trỏ thì cần phải rất thận trọng vì một lỗi nhỏ nhất hoàn toàn có thể đem đến cho tất cả chúng ta những phiền phức rất lớn :

  • Tốn quá nhiều bộ nhớ nếu quên giải phóng con trỏ.
  • Treo nếu con trỏ trỏ đến ô nhớ không hợp lệ trong bộ nhớ

Vậy làm thế nào để sử dụng những lớp cùng với con trỏ ? Các nguyên tắc cần tuân theo là gì ?
Đừng lo, tất cả chúng ta sẽ dành hẳn 1 bài học kinh nghiệm dưới đây để vấn đáp cho những câu hỏi đó .

! Tôi đánh giá mức độ khó của bài học này là rất cao. Dù sao thì chúng ta cũng đang làm việc với 1 trong những công cụ mạnh mẽ nhất của C++. Vì vậy bài học này đòi hỏi tập trung cao hơn nhiều những bài học chúng ta đã xem trong quá khứ.

Một lớp trỏ tới 1 lớp khác

Chúng ta hãy quay về với lớp NhanVat quen thuộc. Trong bài học kinh nghiệm trước, tất cả chúng ta đã trực tiếp tạo ra 1 thuộc tính là 1 đối tượng người dùng VuKhi .

class NhanVat{
   public:
   //Vai phuong thuc

   private:
     Vukhi m_vuKhi;
   //…
};

Trong thực tiễn, có rất nhiều cách để link những lớp với nhau. Cách mà tất cả chúng ta đã sử dụng khá hiệu suất cao. Tuy nhiên sự sống sót của đối tượng người tiêu dùng VuKhi gắn liền với đối tượng người dùng NhanVat và không hề tách rời được. Đấy là 1 bất lợi .
Chúng ta hoàn toàn có thể tưởng tượng quan hệ của 2 đối tượng người tiêu dùng như sau :

Đối tượng VuKhi sống sót bên trong NhanVat .

Một kỹ thuật khác trong C++ cho phép chúng ta liên kết các đối tượng, phức tạp hơn nhưng giữa các đối tượng lại có ít tính ràng buộc hơn, đấy là sử dụng 1 con trỏ thay vì tích hợp trực tiếp đối tượng VuKhi vào trong NhanVat. Đoạn mã khai báo sẽ không thay đổi nhiều so với lúc trước, chỉ thêm 1 dấu *.

class NhanVat{
   public:
   //Vai phuong thuc

   private:
     Vukhi *m_vuKhi; //Thuoc tinh nay la 1 con tro tro len 1 VuKhi
   //…
};

Lúc này tất cả chúng ta không hề coi VuKhi như là 1 bộ phận của NhanVat được nữa .

Chúng ta coi VuKhi nằm ngoài NhanVat .
Một số điểm mạnh của kiểu link này là :

  • Đối tượng NhanVat có thể dễ dàng thay đổi vũ khí bằng cách trỏ lên 1 đối tượng khác. Ví dụ trong trường hợp NhanVat có 1 túi đồ dùng, đối tượng có thể dễ dang đổi vũ khí bằng cách trỏ sang 1 đối tượng khác trong túi.
  • NhanVat có thể trao VuKhi cho 1 NhanVat khác chỉ đơn giản bằng cách thay đổi con trỏ của mỗi đối tượng.
  • Nếu NhanVat không có VuKhi, đơn giản là đưa giá trị con trỏ về 0.

! Những lợi thế này được cho phép tất cả chúng ta xử lý yếu tố gặp phải trong ví dụ game show Warcraft ở bài trước khi tất cả chúng ta muốn đổi khác tiềm năng của nhân vật nhờ 1 con trỏ nội tại .
Ưu điểm là vậy nhưng không hề bỏ lỡ điểm yếu kém của kỹ thuật này. Các bạn sẽ thấy là thao tác với lớp có thêm con trỏ sẽ trở nên vô cùng đau đầu và không còn thuận tiện nữa .
! Hãy ghi nhớ là không có giải pháp nào là tối ưu cho mọi trường hợp. Mỗi chiêu thức đều co điểm mạnh và điểm yếu của nó. Các bạn cần phải tự mình quyết định hành động trong mỗi trường hợp xem có nên hay không sử dụng 1 con trỏ bên trong 1 đối tượng người dùng vì để đổi lấy những tính năng ưu việt, những thao tác sẽ trở nên rắc rối hơn nhiều .

Quản lý phân bổ động

Bắt đầu từ đây, tất cả chúng ta sẽ xem làm thế nào để thao tác với lớp có con trỏ. Ví dụ của tất cả chúng ta chính là trường hợp của lớp NhanVat mà tôi đã nêu bên trên .

class NhanVat{
   public:
   //Vai phuong thuc

   private:
     Vukhi *m_vuKhi; //Thuoc tinh nay la 1 con tro tro len 1 VuKhi
   //…
};

! Tôi cố ý không viết đoạn mã hoàn hảo để những bạn hoàn toàn có thể chú tâm đến trọng điểm .

Vũ khí của chúng ta bây giờ chỉ là 1 con trỏ. Cần phải tạo ra 1 thực thể thông qua phân bổ động nhờ new, nếu không thì đối tượng sẽ không tự mình sinh ra được.

Phân bổ động cho đối tượng

Các bạn đoán ra nơi chúng cần thêm vào đoạn mã phân chia động cho đối tượng người tiêu dùng VuKhi chứ ? Thật ra thì cũng không có quá nhiều lựa chọn : tất cả chúng ta cần triển khai bên trong phương pháp khởi tạo ! Trên trong thực tiễn thì cũng khá dễ hiểu chính do phương pháp khởi tạo là nơi bảo vệ 1 đối tượng người tiêu dùng được tạo ra hoàn hảo, nghĩa là nếu bên trong nó có con trỏ thì con trỏ này cần trỏ tới 1 cái gì đó .

Trong trường hợp của chúng ta, chúng ta bắt buộc cần sử dụng phân bổ động thông qua new. Phương thức khởi tạo mặc định của lớp sẽ có 1 số thay đổi.

NhanVat::NhanVat() : m_vuKhi(0), m_hp(100), m_mp(100){
   m_vuKhi = new VuKhi();
}

Nếu tôi nhớ không lầm thì tất cả chúng ta còn tạo ra 1 phwuong thức khởi tạo khác được cho phép người dùng sử dụng 1 vũ khí khác với vũ khí mặc định. Phương thức này cũng sẽ cần thêm phép phân chia động .

NhanVat::NhanVat(string vuKhi, int dmgVuKhi) : m_vuKhi(0), m_hp(100), m_mp(100){
   m_vuKhi = new VuKhi(vuKhi, dmgVuKhi);
}

new VuKhi(); sẽ gọi phương thức khởi tạo mặc định của VuKhi trong khi new VuKhi(vuKhi, dmgVuKhi); sẽ gọi phương thức khởi tạo nạp chồng của lớp này. new sẽ trả về địa chỉ của đối tượng được tạo ra và giá trị này sẽ được lưu vào con trỏ m_vuKhi.

Để bảo vệ, tất cả chúng ta sẽ khởi tạo con trỏ với giá trị là 0 trong list khởi tạo, sau đó mới triển khai phân chia động trong giải quyết và xử lý của phương pháp .

Giải phóng vùng nhớ của đối tượng

Bởi vì vũ khí của chúng ta bây giờ là 1 con trỏ nên khi chúng ta muốn xóa đối tượng NhanVat, đối tượng VuKhi sẽ không tự động biến mất. Nếu chúng ta chỉ thêm new vào phương thức khởi tạo mà không thêm gì vào phương thức hủy, chúng ta sẽ gặp vấn đề khi đối tượng NhanVat bị xóa.

Đối tượng NhanVat được xóa hoàn toàn nhưng đối tượng VuKhi thì vẫn tồn tại. Nguy hiểm hơn là không có con trỏ nào lưu trữ giá trị của ô nhớ chứa đối tượng này. Vậy nên đối tượng sẽ vẫn luôn tồn tại trong bộ nhớ và chúng ta mãi chẳng thể nào xóa nó đi được. Đây chính là cái mà chúng ta gọi là rò rỉ bộ nhớ.

Để giải quyết vấn đề này, cần phải thực thi delete đối tượng VuKhi trong phương thức hủy của NhanVat để xóa đối tượng VuKhi trước khi xóa NhanVat. Mã thực thi khá là đơn giản.

NhanVat::~NhanVat(){
   delete m_vuKhi;
}

Lúc này thì phương pháp hủy trở nên quan trọng và không hề thiếu. Từ lúc này, mỗi khi có ai đó nhu yếu xóa NhanVat, chương trình sẽ triển khai những giải quyết và xử lý sau :

  1. Gọi phương thức hủy. Trong đó sẽ thực hiện xóa đối tượng VuKhi với delete.
  2. Xóa đối tượng NhanVat.

Cuối cùng thì cả 2 đối tượng người tiêu dùng đều được xóa và bộ nhớ trở lại ngăn nắp .

Chú ý là m_vuKhi bây giờ là 1 con trỏ. Điều đó có nghĩa là chúng ta phải thay đổi tất cả các phương thức sử dụng thuộc tính này. Ví dụ :

void NhanVat::tanCong(NhanVat &mucTieu){
   mucTieu.nhanSatThuong(m_vuKhi.getDmg());
}

sẽ trở thành

void NhanVat::tanCong(NhanVat &mucTieu){
   mucTieu.nhanSatThuong(m_vuKhi->getDmg());
}

Hãy để ý là dấu . đã bị thay thế bởi dấu ->m_vuKhi là 1 con trỏ.

Con trỏ this

Phần dưới đây, tôi xin trình bày với các bạn 1 khái niệm khá thú vị khi chúng ta thao tác với con trỏ cùng các lớp trong OOP, đấy là con trỏ this.

Trong tất cả các lớp đều tồn tại 1 con trỏ đặc biệt tên là this. Con trỏ này trỏ lên chính bản thân đối tượng. Tôi hiểu rằng là hơi khó để hình dung khái niệm này, vậy nên đã làm cho các bạn 1 hình vẽ.

Mỗi đối tượng người dùng đều có chiếm hữu con trỏ này .

! this được sử dụng trong tất cả các lớp trong ngôn ngữ C++. Các bạn sẽ không thể tạo ra 1 biến tên là this bởi vì việc này sẽ sinh ra xung đột. Cũng vì lý do này, các bạn sẽ không thể tạo ra các biến hay hàm có tên là class, new, delete, return, vv… vì các từ khóa này đã được giữ bởi C++ dùng vào các mục đích riêng.

? Vậy con trỏ this dùng để làm gì ?

Một câu hỏi khá tinh xảo ! Con trỏ này thường được dùng khi một phương pháp cần phải trả về giá trị là con trỏ trỏ về đối tượng người dùng bản thể của phương pháp .

NhanVat* NhanVat::getDiaChi() const{
   return this;
}

Chắc các bạn vẫn còn nhớ là chúng ta đã từng dùng đến nó khi ghi đè phép toán operator+= chứ.

ThoiGian& ThoiGian::operator+=(ThoiGian const& thoiGian2){
   //Cac xu ly …
   return *this;
}

this là con trỏ lên bản thân đối tượng nên *this chính là đối tượng. Lý do tại sao chúng ta cần trả về đối tượng thì khá là phức tạp để giải thích, chúng ta chỉ cần biết là đấy mới là nguyên mẫu chuẩn của phép toán, nên tốt nhất nếu có thể là các bạn nên học thuộc nó.

Ngoài trường hợp khi ghi đè phép toán, chúng ta sẽ không thường xuyên bắt gặp this khi mới bắt đầu học lập trình. Tuy nhiên tôi vẫn giới thiệu con trỏ này với các bạn vì biết đâu có 1 ngày khi các bạn cần 1 con trỏ trỏ lên bản thân đối tượng thì các bạn biết rằng nó có tồn tại.

Tôi nhắc đến con trỏ này vì đây là thời gian thích hợp nhất để trình làng nó với những bạn. Chúng ta sẽ chưa tận dụng được ngay quyền lợi của nó nhưng sẽ rất hoàn toàn có thể có thời cơ trong phần sau của giáo trình .

Vấn đề của phương thức khởi tạo sao chép

Như những bạn đã biết, phương pháp khởi tạo sao chép là 1 phương pháp nạp chồng đặc biệt quan trọng của phương pháp khởi tạo. Phương thức này trở nên quan trọng khi tất cả chúng ta thao tác với những lớp có chứa con trỏ như trường hợp ví dụ hiện tại của tất cả chúng ta .

Nguyên nhân của vấn đề

Để hiểu rõ quyền lợi của phương pháp khởi tạo sao chép, tất cả chúng ta cần phải hiểu rõ giải quyết và xử lý của chương trình khi tất cả chúng ta muốn tạo ra 1 đối tượng người dùng dựa trên 1 đối tượng người dùng khác .

int main(){
   NhanVat goliath("Kiem sat", 20);
   NhanVat david(goliath);
   //Tao ra david nhu 1 ban sao cua goliath
   return 0;
}

Nhiệm vụ của phương pháp khởi tạo sao chép là chép hàng loạt giá trị của những thuộc tính của đối tượng người dùng gốc sang đối tượng người tiêu dùng bản sao. Như vậy, đối tượng người dùng david sẽ có toàn bộ những đặc thù của đối tượng người tiêu dùng goliath .

? Khi nào thì phương thức khởi tạo sao chép được sử dụng ?

Chúng ta đã thấy là phương pháp khởi tạo sao chép được gọi khi mà tất cả chúng ta muốn tạo ra 1 đối tượng người dùng bằng cách truyền cho phương pháp khởi tạo tham số là 1 đối tượng người tiêu dùng khác .

NhanVat david(goliath);

Xử lý trọn vẹn tựa như được triển khai nếu tất cả chúng ta thực thi dòng lệnh sau .

NhanVat david = goliath;

Trong cả 2 trường hợp thì phương pháp khởi tạo sao chép đều được sử dụng .
Không chỉ có thế, khi tất cả chúng ta truyền 1 đối tượng người tiêu dùng làm tham số cho hàm mà không sử dụng tham chiếu hay con trỏ thì đối tượng người dùng cũng được sao chép nhờ phương pháp này .
Ví dụ như hàm sau đây :

void ham(NhanVat nhanVat){
}

Bởi vì tham số của hàm này được truyền không phải bằng cách sử dụng tham chiếu hay con trỏ nên đối tượng người tiêu dùng tham số sẽ được sao chép trải qua phương pháp khởi tạo sao chép vào thời gian mà hàm được gọi trong đoạn mã giải quyết và xử lý .

ham(goliath);

Đồng ý là tất cả chúng ta ưu tiên việc sử dụng tham chiếu để hạn chế sao chép đối tượng người dùng do sẽ làm tăng thời hạn giải quyết và xử lý và tốn tài nguyên bộ nhớ. Thế nhưng cũng không hề loại trừ những trường hợp mà tất cả chúng ta bắt buộc phải sử dụng hàm thao tác với bản sao của đối tượng người dùng .
! Nếu những bạn không tự mình viết 1 phương pháp khởi tạo sao chép cho lớp thì trình biên dịch sẽ tự động hóa tạo ra 1 phương pháp cho bạn. Vấn đề là trình biên dịch lại không quá mưu trí trong yếu tố này. Phương thức được tự động hóa tạo ra này chỉ đơn thuần là sao chép giá trị của toàn bộ những thuộc tính, nghĩa là cả của con trỏ, lên đối tượng người tiêu dùng mới .
Vấn đề nằm chính ở chỗ này. Tại sao ? Bởi vì trong số thuộc tính của tất cả chúng ta có 1 con trỏ. Khi phương pháp khởi tạo sao chép được gọi, nó sẽ chép giá trị của con trỏ này, tức là địa chỉ của đối tượng người tiêu dùng VuKhi, sang đối tượng người dùng bản sao. Kết quả là tất cả chúng ta sẽ có 2 đối tượng người tiêu dùng với thuộc tính con trỏ cùng trỏ tới 1 đối tượng người dùng VuKhi .

! Nếu tất cả chúng ta không xử lý yếu tố này thì sẽ sinh ra rắc rối. Thử tưởng tượng nếu 2 nhân vật chiến đấu với nhau, 1 trong 2 bị hủy hoại và bị xóa khỏi game show thì vũ khí của nhân vật đó sẽ biến mất và nhân vật thứ 2 cũng mất luôn vũ khí của mình. Thêm nữa là khi nhân vật thứ 2 cũng bị hủy hoại thì lệnh xóa vũ khí sẽ làm treo chương trình do vũ khí đã bị xóa từ trước rồi .
Nguồn gốc của yếu tố là do phương pháp khởi tạo sao chép tạo ra bởi trình biên dịch không đủ mưu trí để thực thi phân chia động tạo ra 1 đối tượng người tiêu dùng VuKhi mới. Vì thế nên tất cả chúng ta cần phải hướng dẫn nó thực thi giải quyết và xử lý này .

Tạo ra phương thức khởi tạo sao chép

Như đã nói bên trên thì phương pháp khởi tạo sao chép chỉ là 1 phương pháp nào chồng đặc biệt quan trọng của phương pháp khởi tạo mặc định. Phương thức này nhận vào 1 tham chiếu hằng trên 1 đối tượng người tiêu dùng cùng loại. Nếu những bạn vẫn thấy chưa rõ ràng thì sau đây là 1 ví dụ .

class NhanVat{
   public:
     NhanVat();
     NhanVat(NhanVat const& nhanVatGoc);
    //Nguyen mau phuong thuc khoi tao sao chep
    NhanVat(std::string vuKhi, int dmgVuKhi);
    ~NhanVat();

   private:
     int m_hp;
     int m_mp;
     VuKhi *m_vuKhi;
};

Tóm lại, nguyên mẫu tổng quát của phương pháp này sẽ là :

DoiTuong(DoiTuong const& doiTuongGoc);

Từ khóa const chỉ ra rằng chúng ta không có quyền thay đổi giá trị của tham chiếu.

Trong mã xử lý của phương thức, chúng ta sẽ phải chép các thuộc tính của doiTuongGoc vào đối tượng mà chúng ta sẽ tạo ra. Sẽ dễ dàng hơn nếu chúng ta bắt đầu từ những thuộc tính thông thường, không phải con trỏ.

NhanVat::NhanVat(NhanVat const& nhanVatGoc) : m_hp(nhanVatGoc.m_hp), m_mp(nhanVatGoc.m_mp), m_vuKhi(0){
}

! 1 số bạn sẽ thắc mắc là làm thế naò mà chúng ta có thể truy cập đến các thuộc tính của đối tượng doiTuongGoc. Nếu các bạn tự đặt ra câu hỏi đấy thì tôi xin chúc mừng vì các bạn đã bắt đầu ghi nhớ về tính đóng gói của OOP.

Trên trong thực tiễn, những thuộc tính trên có quyền truy vấn « private » nên không hề sử dụng từ bên ngoài của lớp … trừ trong 1 trường hợp đặc biệt quan trọng : nếu những bạn đang ở trong 1 phương pháp của lớp thì bạn có quyền truy vấn tới toàn bộ những thành phần của bất kể đối tượng người tiêu dùng nào của lớp kể cả khi thành phần đó có quyền truy vấn là « private » .

Vậy là chỉ còn việc sao chép thuộc tính m_vuKhi nữa là xong.

Nếu tất cả chúng ta sử dụng dòng lệnh sau :

m_vuKhi = nhanVatGoc.m_vuKhi;

thì cũng sẽ phạm phải cùng sai lầm như trình biên dịch, đấy là sao chép địa chỉ của đối tượng vũ khí chứ không phải bản thân đối tượng. Để giải quyết vấn đề này thì chúng ta cần thực hiện 1 phân bổ động với new.

m_vuKhi = new VuKhi();

Dòng lệnh trên tạo ra 1 đối tượng mới nhưng vẫn không phải thứ chúng ta muốn vì nó sẽ chỉ tạo ra 1 đối tượng VuKhi cơ bản còn chúng ta muốn đối tượng VuKhi mới phải là bản sao của vũ khí của đối tượng nhanVatGoc.

Thật may là chúng ta còn có thể lợi dụng phương thức khởi tạo sao chép được tự động tạo ra bởi trình biên dịch. Khi nào mà không một thuộc tính nào của lớp là con trỏ thì phương thức này còn hoạt động rất tốt. Vậy nên trong trường hợp của lớp VuKhi, chúng ta không cần đắn đo nhiều khi sử dụng phương thức này. Chỉ cần chú ý là tham số của phương thức này là bản thân đối tượng chứ không phải là địa chỉ của đối tượng được lưu bên trong con trỏ, Vậy nên chúng ta cần dùng đến dấu *.

m_vuKhi = new VuKhi(*(nhanVatGoc.m_vuKhi));

Dòng lệnh trên sẽ thực sự tạo ra 1 đối tượng người tiêu dùng VuKhi mới dựa trên đối tượng người tiêu dùng vũ khí của nhân vật bị sao chép .
Tôi biết là khá lằng nhằng nhưng đừng ngại đọc lại từng bước lý luận của tôi và những bạn sẽ cảm thấy dễ hiểu hơn nhiều. Các bạn cũng cần nắm vững trước đó những kỹ năng và kiến thức về con trỏ, tham chiếu và phương pháp khởi tạo sao chép để hoàn toàn có thể tiếp thu trọn vẹn những lập luận này .

Kết quả

Phương thức sao chép đúng sẽ có dạng như sau :

NhanVat::NhanVat(NhanVat const& nhanVatGoc) : m_hp(nhanVatGoc.m_hp), m_mp(nhanVatGoc.m_mp),
                                              m_vuKhi(0){
  m_vuKhi = new VuKhi(*(nhanVatGoc.m_vuKhi));
}

2 đối tượng người dùng NhanVat giống nhau được tạo ra mà không gặp phải yếu tố được nêu bên trên .

Phép gán

Trong bài trước thì chúng ta từng đề cập đến việc ghi đè các phép toán. Thế nhưng còn 1 phép toán tôi chưa trình bày với các bạn, đấy là phép gán operator=.

! Trình biên dịch cũng tự động hóa tạo ra 1 phép gán mặc định cho lớp. Thế nhưng cũng giống với trường hợp của phương pháp sao chép, phép toán này cũng có sai lầm đáng tiếc khi chỉ đơn thuần chép lại giá trị của những thuộc tính sang đối tượng người dùng mới .

operator= sẽ được gọi khi chúng ta muốn gán giá tri của 1 đối tượng cho 1 đối tượng khác.

david = goliath;

! Không nên nhầm lẫn giữa phương thức sao chép với phép toán ghi đè operator=. Chúng khá là giống nhau trừ việc là phương thức sao chép thì được gọi khi thực hiện khởi tạo đối tượng còn phép gán thì được sử dụng khi ta muốn gán 1 giá trị cho đối tượng sau khi đã khởi tạo trước đó.

NhanVat david = goliath; //Phuong thuc khoi tao sao chep
david = goliath; //Phep gan

Xử lý của phép toán này giống hệt như phương pháp sao chép nên đoạn mã của nó sẽ khá đơn thuần nếu tất cả chúng ta đã hiểu được nguyên tắc .

NhanVat& NhanVat::operator=(NhanVat const& nhanVatGoc) {
   if(this != &nhanVatGoc) {
   //Kiem tra xem ban the voi nhan vat muon sao chep co phai cung 1 doi tuong khong
       m_hp = nhanVatGoc.m_hp;
       m_mp = nhanVatGoc.m_mp;
       delete m_vuKhi; //Xoa vu khi cu
       m_vuKhi = new VuKhi(*(nhanVatGoc.m_vuKhi));
   }
   return *this;
}

Có 4 điểm độc lạ so với phương pháp khởi tạo :

  • Đây không phải phương thức khởi tạo nên không có cách nào sử dụng danh sách khởi tạo. Tất cả diễn ra bên trong dấu {}.
  • Cần kiểm tra là nhân vật mà chúng ta muốn gán giá trị và nhân vật gốc của phép gán không phải cùng 1 nhân vật, nghĩa là tránh thực hiện david = david;. Để làm vậy thì chúng ta cần sử dụng đến địa chỉ của các đối tượng để so sánh.
  • Kết quả trả về phải là *this giống như các phép toán khác.
  • Cần phải xóa vũ khí cũ trước khi tạo ra vũ khí mới. Chúng ta không cần làm thế trong phương thức khởi tạo sao chép vì khi khởi tạo thì nhân vật chưa có vũ khí.

Các bạn sẽ thấy là với những lớp khác nhau thì phần khung của hàm này không có nhiều biến hóa, chỉ độc lạ do thuộc tính của những lớp khác nhau nên những lệnh gán sẽ khác nhau .

1 điều quan trọng cần ghi nhớ là việc ghi đè operator= luôn đi kèm với việc sửa đổi phương thức khởi tạo sao chép. Không nên nhớ làm việc này mà quên đi việc kia vì chúng cần phải đi đôi với nhau. Chú ý là rất cần thiết phải tuân thủ quy tắc này, nếu không các bạn có thể gặp những vấn đề lớn với con trỏ.

Các bạn sẽ từ từ thấy lập trình hướng đối tượng người tiêu dùng trở nên phức tạp nhất là khi có sự tham gia của con trỏ. Thật may là trong phần sau, tất cả chúng ta sẽ thao tác với chúng nhiều hơn và tôi sẽ có thời cơ chỉ cho những bạn những sai lầm đáng tiếc hay gặp để tránh mắc phải khi thao tác con trỏ .

Tóm tắt bài hoc :

  • Để liên kết các lớp, chúng ta có thể sử dụng con trỏ : 1 lớp có thể có 1 thuộc tính là con trỏ lên 1 lớp khác.
  • Khi liên kết các lớp bằng con trỏ, cần chú ý việc giải phóng bộ nhớ khi xóa các đối tượng.
  • Phương thức khởi tạo sao chép là phương thức được gọi khi chúng ta muốn tạo ra bản sao của 1 đối tượng. Chúng ta cần phải định nghĩa lại nó 1 cách cẩn thận khi sử dụng con trỏ bên trong đối tượng.
  • Con trỏ this tồn tại trong tất cả các đối tượng. Đó là con trỏ trỏ lên bản thân đối tượng.