Constructor, destructor và Copy constructor trong hướng đối tượng c++

Copy Constructor được dùng rất nhiều trong lập trình và so với lập trình hướng đối tượng người dùng khi tài liệu của đối tượng người tiêu dùng là một con trỏ thì nó lại rất cực kỳ quan trọng mà bạn phải chú tâm đến .Ở bài viết trước [ Học OOP ] Bài 3 : Lớp trong lập trình hướng đối tượng người dùng mình đã ra mắt sơ về Constructor và Destructor. Trong bài này mình liên tục trình làng về Copy Constructor và đây là một chủ đề khá quan trọng nó sẽ giúp bạn hiểu hơn về những chính sách hoạt động giải trí của ngôn từ lập trình .

1. Copy Constructor là gì ?

a. Giới thiệu Copy Constructor

Copy Constructor là một Constructor đặc biệt quan trọng, nó được dùng để tạo một bản sao của một đối tượng người dùng đã có trước đó. Copy Constructor có tham số là địa chỉ vùng nhớ tham chiếu đến đối tượng người tiêu dùng cần sao chép .Copy Constructor 2 đối tượng trong lập trình hướng đối tượng

Trong hình minh họa trên, Ban đầu chúng ta có đối tượng A có địa chỉ vùng nhớ là 0x0100, nó là một hình tròn màu vàng viền đen. Do nhu cầu nên chúng ta muốn tạo ra một bản sao giống hệt Đối tượng A và đặt tên là Đối tượng B có địa chỉ vùng nhớ 0x0300. Vai trò copy constructor trong ví dụ trên chỉ có vậy.

b. Vì sao phải dùng Copy Constructor ?

Sau khi đọc xong phần trình làng có lẽ rằng bạn đặt khá nhiều câu hỏi, vai trò chỉ là sao chép thì tại sao lại nói là nó quan trọng, trong khi phép gán đã xử lý được yếu tố sao chép ?Như bạn cũng biết, Pointer ( Con trỏ ) trong C + + lưu địa chỉ vùng nhớ, chính về thế, nếu bạn muốn sao chép nội dung 2 pointer trỏ đến bằng phép gán b = a thì lúc này nó chỉ gán địa chỉ vùng nhớ của a cho b mà thôi, và nội dung của 2 là một, nên khi bạn tác động ảnh hưởng lên tài liệu mà a hoặc b trỏ đến thì cả nội dung của a và b đều sẽ đổi khác. Nên giải pháp gán này không hài hòa và hợp lý .Trong C + + khi bạn truyền vào hàm, thủ tục một đối tượng người tiêu dùng, thì nó sẽ gọi copy constructor của kiểu tài liệu đó để triển khai 1 số sao chép. Nếu kiểu tài liệu đó không có copy constructor thì nó sẽ tự gán giá trị của biến đó cho một biến khác. Ví dụ

1234567

intm=1,n=2;

intmax(inta,intb)

{

if(a>b)returna;

returnb;

}

Trong ví dụ trên khi bạn gọi max ( m, n ) ở đây bạn truyền vào tham trị nó sẽ gọi copy constructor của class số int và bạn hiểu là phương pháp đó thực thi sao chép tài liệu để nếu bạn có đổi khác giá trị a b ở trong hàm thì 2 biến m, n ở ngoài truyền vào sẽ không ảnh hưởng tác động .Sau khi xử lí xong, và thoát khỏi hàm, nó sẽ gọi hàm destructor để hủy những biến mà nó tạo ra, trong trường hợp này gồm có những biến được tạo bằng constructor và Copy constructor. Phần về Destructor mình sẽ nói bên dưới .Như vậy nếu đối tượng người tiêu dùng của mình có tài liệu là một pointer cấp phát động, ví dụ điển hình mình sẽ tạo ra một class chứa mảng số nguyên như sau :

1234567891011121314151617181920212223242526272829303132333435363738

classMangInt

{

private:

int*A;

intn;

public:

voidxuat()

{

for(inti=0;i

cout<

}

MangInt()

{

n=10;

A=newint[n];/ / cấp phát động mảng A 10 thành phần

for(inti=0;i< =9;i+ +)

A[i]=i;/ / gán giá trị 0-9 cho những thành phần

}

~MangInt()

{

delete[]A;

}

};

voidvidu(MangIntarray)

{

array.xuat();

}

intmain()

{

MangIntArray1;

vidu(Array1);

}

Ở ví dụ trên khi khởi tạo đối tượng mình sẽ cho nó cấp phát động mảng A, và theo nguyên tắc thông thường, có cấp phát động phải có thu hồi bộ nhớ, nên mình sẽ thu hồi bộ nhớ ở destructor, và bây giờ mình khai báo MangInt Array1;  sau đó truyền Array1 vào hàm vidu

Như miêu tả bên trên nó sẽ gọi copy constructor nhưng mình chưa khai báo cho nó nên mặc định nó sẽ làm là

– Gán Array1. n cho array. n– Gán Array1. A cho array. A ( quan tâm đây là pointer nên nó chép địa chỉ )

Sau khi xử lí xong hàm, lúc thoát khỏi hàm nó sẽ gọi Destructor cho những gì nó tạo khi chạy hàm đó. Như vậy bao gồm nó sẽ gọi destructor của array (trong đây nó thu hồi bộ nhớ của array)

Như vậy sau khi chạy xong hàm vidu trong main() thì nó sẽ kết thúc hàm main và vẫn theo nguyên tắc cũ là gọi Destructor cho những gì nó đã tạo trong hàm đó. Lúc này Array1 được gọi hủy. Theo như trong code hàm hủy, mình delete pointer A, mà địa chỉ pointer A đó đã bị hủy trước đó trong hàm vidu, nên lúc này xảy ra hiện tượng lỗi thu hồi 2 lần trên cùng một vùng nhớ.

Chính do đó mà vai trò Copy Constructor là khi nó được chạy thì nó sẽ tạo ra vùng nhớ mới bằng cách cấp phát động và gán giá trị qua chứ không phải gán địa chỉ của 2 pointer theo cách mặc định của c + +

2. Cài đặt Copy Constructor như thế nào ?

“Bài viết này mình nói khá dài dòng, nhưng bạn chỉ cần nhớ một câu duy nhất Khi nào dữ liệu của class có cấp phát động, hãy cài đặt Copy Constructor

a. Khai báo copy constructor

12345

Tên_Class(constTên_Classvàtenbien)

{

/ / cấp phát động cho tài liệu có con trỏ

/ / gán những tài liệu của tên biến cho đối tượng người dùng hiện tại

}

b. Ví dụ về khai báo copy constructor

Mình sẽ lấy ví dụ về MangInt bên trên để viết copy constructor cho nó

1234567

MangInt(constMangIntvàmang)

{

n=mang.n;/ / gán n của mang qua n của đối tượng người tiêu dùng cần sao chép

A=newint[n];/ / cấp phát động lại vùng nhớ mới

for(inti=0;i

A[i]=mang.A[i];/ / sao chép hết giá trị từng thành phần qua vung nhớ mới

}

3. Bài tâp ứng dụng Copy Constructor

a. Đề bài

Viết định nghĩa lớp String để biểu diễn khái niệm chuỗi ký tự với các phương thức thiết lập và huỷ bỏ, các hàm thành phần tính chiều dài chuỗi, so sánh hai chuỗi, nối hai chuỗi, đảo chuỗi, xuất chuỗi.

Lưu ý: không được sử dụng kiểu string của C++ (của thư viện chuẩn )

Ở bài này theo nhu yếu bạn nên tạo một class có 2 dữ liệu là poiner bộc lộ mảng kí tự char và một biến số nguyên lưu độ dài xâu .

b. Code lời giải

Bài này mình đã làm quá lâu rồi, nên một số ít chỗ chưa hài hòa và hợp lý, tuy nhiên những bạn hoàn toàn có thể tìm hiểu thêm để hiểu về cách ứng dụng Copy Constructor và viết lại theo cách riêng của mình .

String. h

123456789101112131415161718

# pragma once

classString

{

private:

char*pString;

intDoDai;

public:

String();

String(constchar*str);

String(constStringvàa);/ / copy constructor

~String();

intGetLength();

voidxuat();

intSoSanh(constStringvàa);

voidNoiChuoi(constStringvàa);

voidnhap();

voidDaoChuoi();

};

String. cpp

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889

#include

# include ” String. h “

#include

usingnamespacestd;

String::String()

{

DoDai=1;

pString=NULL;

}

String::String

(

constchar*str)

{

DoDai=strlen(str)+1;

pString=newchar[DoDai];

strcpy_s(pString,DoDai,str);

pString[DoDai-1]=’ \ 0 ‘;

}

String::String(constStringvàa)

{

DoDai=a.DoDai;

pString=newchar[DoDai];

strcpy_s(pString,DoDai,a.pString);

}

String::~String()

{

delete[]pString;

}

intString::GetLength()

{

returnDoDai-1;

}

voidString::xuat()

{

if(pString! =NULL)

cout<

}

intString::SoSanh(constStringvàa)

{

if(a.pString= =NULLvà vàpString= =NULL)

return0;

if(a.pString= =NULL)

return1;

if(pString= =NULL)

return-1;

returnstrcmp(pString,a.pString);

}

voidString::NoiChuoi(constStringvàa)

{

if(a.pString= =NULLvà vàpString= =NULL)

return;

if(a.pString= =NULL)

return;

char*p=newchar[DoDai+a.DoDai-1];

if(pString= =NULL)

{

strcpy_s(p,a.DoDai,a.pString);

delete[]pString;

pString=p;

return;

}

for(inti=0;i< =DoDai-2;i+ +)

p[i]=pString[i];

for(inti=0;i

p[i+DoDai-1]=a.pString[i];

delete[]pString;

pString=p;

DoDai=DoDai+a.DoDai-1;

}

voidString::nhap()

{

cin>>pString;

DoDai=strlen(pString);

}

voidString::DaoChuoi()

{

_strrev(pString);

}

Main. cpp

1234567891011121314151617

#include

# include ” String. h “

usingnamespacestd;

intmain()

{

Stringa(” con vit “),b(” con ga “);

a.NoiChuoi(b);

cout< <" length = "<

a.xuat();

a.DaoChuoi();

cout<

a.xuat();

system(” pause “);

return0;

}

Sau khi giải xong bài này bạn sẽ đặt hỏi vì sao khi khai báo kiểu String a = ” con vit “, b = ” con ga ” ; bị sai hiệu quả hay vì sao không hề gọi ghép string bằng a + b ví dụ điển hình, thì nó cần đến overloading operator để tạo lại toán tử, cả phép gán = cũng hoàn toàn có thể overloading lại, nên khi bạn không định nghĩa toán tử này mà sử dụng nó sẽ cho hiệu quả không mong ước. Ở những bài sau mình sẽ nói về overloading operator =

Bạn có thể làm thêm các bài tập dưới đây:

Bài tập 3 – Bài này mình giải có sai sót, bạn vui mừng làm lại và ứng dụng copy constructor .