Chương 2 Lớp , Đ ối tượng và tính đóng gói 46 46 int i, j; public: cl2(int a, int b) { i = a; j = b; } // . }; int main() { cl1 x(10, 20); cl2 y(0, 0); x = y; // . } 2. Viết chương trình dựa vào lớp queue trong bài tập II/ 1. chương II chứng tỏ một hàng đợi có thể được gán cho một hàng đợi khác. 3. Nếu lớp queue trong câu 2. cấp phát bộ nhớ động để giữ hàng đợi, tại sao trong trường hợp này, một hàng đợi không thể được gán cho hàng đợi khác. VI/ Truyềncácđốitượngsanghàm 1/ Việc truyền các đốitượng cho hàm giống như truyềncácđối số thông thường. Tham số của hàm có kiểu dữ liệu là kiểu lớp, và đối số truyền cho hàm chính là đốitượng . Giống như các kiểu dữ liệu khác, theo ngầm đònh, tất cả cácđốitượng được truyền bởi giá trò cho một hàm. Ví dụ 6.1 Truyền một đốitượng cho hàm. #include <iostream.h> class samp { int i; public: Chương 2 Lớp , Đ ối tượng và tính đóng gói 47 47 samp(int n) { i = n; } int get_i() { return i; } }; // Return square of o.i. int sqr_it( samp o ) { return o.get_i() * o.get_i(); } int main() { samp a(10), b(2); cout << sqr_it( a ) << "\n"; cout << sqr_it( b ) << "\n"; return 0; } 2/ Do đốitượng được truyền bởi giá trò cho một hàm, có nghóa là việc sao chép từng bit của đối số được thực hiện và chính sao chép này được sử dụng bởi hàm. Do đó, những thay đổi cho đốitượng bên trong hàm không có ảnh hưởng đến sự gọi đối tượng. Ví dụ 6.2 #include <iostream.h> class samp { int i; public: samp(int n) { i = n; } void set_i(int n) { i = n; } int get_i() { return i; } }; // Set o.i to its square. This has no effect on the object used to call sqr_it(), Chương 2 Lớp , Đ ối tượng và tính đóng gói 48 48 however. void sqr_it( samp o ) { o.set_i( o.get_i() * o.get_i() ) ; cout << "Copy of a has i value of " << o.get_i(); cout << "\n"; } int main() { samp a(10); sqr_it( a ) ; // a passed by value, displays 100 cout << "But, a.i is unchanged in main: "; cout << a.get_i(); // displays 10 return 0; } 3/ Khi truyền điạ chỉ của đốitượngsanghàm (sẽ không tạo ra bản sao đối tượng), nội dung của đốitượng sẽ bò thay đổi. Ví dụ 6.3 #include <iostream.h> class samp { int i; public: samp(int n) { i = n; } void set_i(int n) { i = n; } int get_i() { return i; } }; /* Set o.i to its square. This affects the calling argument. */ void sqr_it( samp *o ) { Chương 2 Lớp , Đ ối tượng và tính đóng gói 49 49 o->set_i(o->get_i() * o->get_i()); cout << "Copy of a has i value of " << o->get_i(); cout << "\n"; } int main() { samp a(10); sqr_it( &a ); // pass a's address to sqr_it(), displays 100 cout << "Now, a in main() has been changed: "; cout << a.get_i(); // displays 100 return 0; } 4/ Bản sao của đốitượng Khi truyền một đốitượng cho hàm, một bản sao của đốitượng được thực hiện, có nghóa là một đốitượng mới xuất hiện. Do đó khi hàm kết thúc làm việc, bản sao của đốitượng đó (đối số của hàm) sẽ bò hủy. Điều này làm nảy sinh hai vấn đề : - Hàm tạo của đốitượng được gọi khi bản sao thực hiện ? - Hàm hủy của đốitượng được gọi khi bản sao bò hủy ? Khi bản sao của một đốitượng được thực hiện để dùng gọi hàm, thì hàm tạo không được gọi . Do hàm tạo thường được dùng để khởi đầu một khiá cạnh nào đó của đối tượng. Khi truyền một đốitượng cho một hàm, bạn cần đến trạng thái hiện hành của đốitượng chứ không phải trạng thái ban đầu. Khi hàm kết thúc và bản sao bò hủy, hàm hủy được gọi . Ví dụ 6.4 #include <iostream.h> class samp { int i; Chương 2 Lớp , Đ ối tượng và tính đóng gói 50 50 public: samp(int n) { i = n; cout << "Constructing\n"; } ~samp() { cout << "Destructing\n"; } int get_i() { return i; } }; // Return square of o.i. int sqr_it( samp o ) { return o.get_i() * o.get_i(); } int main() { samp a(10); cout << sqr_it( a ) << "\n"; return 0; } Nội dung chương trình thực hiện Constructing Destructing 100 Destructing Giải thích ? Hàm hủy của bản sao đốitượng được gọi khi hàm kết thúc có thể là nguyên nhân của nhiều vấn đề. Chẳng hạn, nếu đốitượng có hàm tạo cấp phát bộ nhớ động hoặc hàm hủy giải phóng bộ nhớ động, thì bản sao của đốitượng sẽ giải phóng bộ nhớ động khi hàm Chương 2 Lớp , Đ ối tượng và tính đóng gói 51 51 hủy của nó được gọi. Điều này làm cho đốitượng gốc bò hỏng và trở thành vô dụng. Ví dụ 6.5 // This program contains an error. #include <iostream.h> #include <stdlib.h> class dyna { int *p; public: dyna(int i); ~dyna() { free(p); cout << "freeing \n"; } int get() { return *p; } }; dyna::dyna(int i) { p = (int *) malloc(sizeof(int)); if(!p) { cout << "Allocation failure\n"; exit(1); } *p = i; } // Return negative value of *ob.p int neg( dyna ob ) { return -ob.get(); } int main() { dyna o(-10); cout << o.get() << "\n"; // -10 Chương 2 Lớp , Đ ối tượng và tính đóng gói 52 52 cout << neg( o ) << "\n"; // freeing // 10 dyna o2(20); cout << o2.get() << "\n"; // 20 cout << neg( o2 ) << "\n"; // freeing // -20 cout << o.get() << "\n"; // 20 do *p được cấp đòa chỉ vùng nhớ động cout << neg( o ) << "\n"; // freeing // -20 return 0; // freeing o // freeing o2 // Null pointer assignment } Giải thích nguyên nhân gây lổi ? • Có thể khắc phục bằng cách truyền điạ chỉ của đốitượng cho hàm. Vì sẽ không có đốitượng mới được tạo ra và không có hàm hủy của bản sao đốitượng được gọi khi hàm trả về. Tuy nhiên, giải pháp tốt nhất là sử dụng hàm tạo bản sao (copy constructor), cho phép đònh nghiã cách thức tạo các bản sao của cácđốitượng (xem chương sau). Bài tập VI 1. Viết chương trình tạo lớp stack trong ví dụ 5.3 chương 2, bổ sung hàm showstack() để truyền một đốitượng kiểu stack. Cho hàm này hiển thò nội dung của ngăn xếp. 2. Tìm lỗi sai trong chương trình này #include <iostream.h> #include <stdlib.h> class dyna { int *p; public: Chửụng 2 Lụựp , ẹ oỏi tửụùng vaứ tớnh ủoựng goựi 53 53 dyna(int i); ~dyna() { free(p); cout << "freeing \n"; } int get() { return *p; } }; dyna::dyna(int i) { p = (int *) malloc(sizeof(int)); if(!p) { cout << "Allocation failure\n"; exit(1); } *p = i; } // Return negative value of *ob.p int neg(dyna ob) { return -ob.get(); } int main() { dyna o(-10); cout << o.get() << "\n"; cout << neg(o) << "\n"; dyna o2(20); cout << o2.get() << "\n"; cout << neg(o2) << "\n"; cout << o.get() << "\n"; cout << neg(o) << "\n"; return 0; . hàng đợi khác. VI/ Truyền các đối tượng sang hàm 1/ Việc truyền các đối tượng cho hàm giống như truyền các đối số thông thường. Tham số của hàm có kiểu dữ. và đối số truyền cho hàm chính là đối tượng . Giống như các kiểu dữ liệu khác, theo ngầm đònh, tất cả các đối tượng được truyền bởi giá trò cho một hàm.