Lập trình hướng đối tượng - Initialization & Cleanup
MỤC LỤC
Giới thiệu . . . . 1
1. GUARANTEED INITIALIZATION WITH THE CONSTRUCTOR . 3
2. GUARANTEED CLEANUP WITH THE DESTRUCTOR . . 7
3. STACK WITH CONSTRUCTOR & DESTRUCTOR . . 9
4. COPY CONSTRUCTOR . . . 11
5. ELIMINATION OF DEFINITION BLOCK . . 12
6. FOR LOOPS . . . . 14
7. STORAGE ALLOCATION . . . 15
8. AGGREGATE INITIALIZATION . . . 16
9. DEFAULT CONSTRUCTOR. . . 18
10. SUMMARY. . . . 19
Reference . . . . 20
Dễ dàng nhận thấy rằng, với cách khai báo biến như trên, đoạn code dường như trực quan hơn và dẽ hiểu hơn nhiều. Hai biến y và biến test_p không còn phải cứng nhắc khai báo ở đầu scope nữa. Với C++, mọi thứ bây giờ trở nên tiện lợi hơn với lập trình viên. 6. FOR LOOPS. Một sự mở rộng mới từ sự uyển chuyển của việc khai báo biến linh hoạt ở C++ là việc đưa vào tính năng định nghĩa những biến xác định ngay bên trong cấu trúc vòng lặp for, là cấu trúc lặp hay được sử dụng nhất. Ví dụ sau sẽ minh họa rõ hơn cho điều này. 1 for(int j = 0; j 4 2 { 3 cout << "j = " << j << endl; 4 } 5 for(int i = 0; i 10 6 { 7 cout << "i = " << i << endl; 8 for( int k=0;k<10;k++) 9 cout << "k = " << k << endl; 10 } Từ đoạn code minh họa trên, ta dễ dàng nhận thấy rằng các biến cục bộ i,j được khai báo ngay trong cấu trúc for, đây là tính năng mới mà ngôn ngữ C chuẩn không hỗ trợ. Cũng nhờ đó mà các lập trình viên có thể nhanh chóng khai báo cho trình biên dịch biết những biến tạm nào đó sẽ được sử dụng trong vòng lặp for mà không cần quan tâm tới việc phải định nghĩa các biến này trước khi sử dụng trong vòng lặp for như họ đã từng khi làm việc với C chuẩn trước kia. Tuy nhiên, ta cần lưu ý kỹ 1 tính chất cho tính năng mới này đó là các biến tạm được khai báo trong cấu trúc vòng lặp for nào sẽ chỉ có ý nghĩa(tồn tại) trong phạm vi cục bộ của cấu trúc vòng lặp for đó mà thôi(kết thúc vòng lặp, các biến này sẽ được hủy bỏ tự động), nếu ta cố truy xuất các biến tạm đó từ bên ngoài vòng lặp for, bug về undefined variable có thể xảy ra. 15 7. STORAGE ALLOCATION. Với việc một biến có thể khai báo bất kỳ nơi đâu trong phạm vi “{}”(scope), nó cũng tương tự rằng tác vụ cấp phát vùng nhớ để lưu trữ 1 biến bất kỳ sẽ không được thực thi cho tới khi biến đó được định nghĩa 1 cách tường minh tại 2 vị trí nào đó trong scope. Về thực tế, điều này cũng giống như việc trình biên dịch sẽ theo như cách thức làm việc của C đó là cấp phát vùng lưu trữ cho các biến sẽ được sử dụng trong scope “{}” ngay sau dấu mở “{“, có chăng chỉ là sự linh hoạt về vị trí khai báo biến trong scope mà thôi. Với lập trình viên, điều này dường như cũng không quan trọng quan trọng bởi vì bạn không thể truy xuất vào vùng lưu trữ của biến-đối tượng đó tới khi nào nó được định nghĩa 1 cách tường minh cho trình biên dịch. Do đó, ta cần phải hiểu rõ rằng, mặc dù việc cấp phát vùng nhớ cho các biến-đối tượng là được thực hiện ở đầu scope, thế nhưng, hàm constructor của các biến-đối tượng tương ứng sẽ không được gọi thực thi cho tới khi nào các biến-đối tượng đó được định nghĩa 1 cách tường minh. Bên cạnh những điều đã phân tích ở trên, trình biên dịch C++ còn bảo đảm cho thao tác initialization 1 đối tượng mới là an toàn nếu lập trình viên đặt câu lệnh khai báo một đối tượng mới vào trong 1 mệnh đề điều kiện như cấu trúc if-else, switch hoặc thậm chí là một câu lệnh goto để jump tới 1 dòng lệnh bất kỳ. class point { private: int x; public: point(int px) { x=px; } ~point() {} }; void main() { int x; printf("Nhap vao gia tri x="); scanf("%d",&x); if(x>10) goto label1; new_class test1; //error initialization of 'test1' is skipped by 'goto label1' label1:switch(x) { 16 case 1: new_class test2; //error initialization of 'test2' is skipped by 'case' label break; case 2: new_class test3; break; } } Khi thử compile đoạn code minh họa trên với Visual C++ 6.0, ta nhận được 2 error khi ta cố định nghĩa các biến test1 và test2 ở những vị trí nhạy cảm, nơi mà nó có thể dễ dàng bị bỏ qua, và do đó, hàm constructor cũng sẽ không được gọi để thực thi. Ở đây, ta cũng phải hiểu rõ rằng, tất cả những quá trình cấp phát được đề cập ở trên đều hàm ý là trên stack. Nếu muốn cấp phát lưu trữ biến-đối tượng trên heap, ta có thể sử dụng con trỏ kết hợp với toán tử new để thực hiện điều đó. 8. AGGREGATE INITIALIZATION. Khởi tạo tập hợp(kết hợp) là một khái niệm trong C, là tính năng cho phép lập trình viên có thể kết hợp khai báo nhiều biến-đối tượng trên cùng 1 dòng lệnh và có thể được quản lý dưới 1 object chung nhất. Một mảng là 1 sự kết hợp của nhiều đối tượng cùng kiểu có thể được khởi tạo theo tính năng này. Với C++, trình biên dịch làm cho tính năng này có thể hỗ trợ tốt nhất cho lập trình viên và cũng cải thiện để làm cho nó an toàn hơn khi được sử dụng. Khi tiến hành tạo mới một đối tượng theo kiểu tập hợp, điều lập trình viên cần làm là tạo mới 1 chỉ định để báo cho trình biên dịch biết rằng cần phải quản lý quá trình khởi tạo cho các biến phần tử trong tập hợp đó. Thao tác tạo 1 mảng mới với kiểu dữ liệu là built-in theo kiểu tập hợp thì đơn giản như sau: int a[3] = { 3, 4, 5}; Từ ví dụ trên, ta dễ dàng rút ra được quy tắc để khai báo 1 biến mảng theo kiểu tập hợp đó là: các giá trị của các phần tử trong mảng thì được khai báo trong cặp dấu {}, vị trí của các giá trị cũng là vị trí tương ứng trong mảng, và các giá trị cách nhau bằng “,”. Thế nhưng, nếu ta cố khai báo các phần tử trong tập hợp mà số lượng các phần tử nhiều hơn kích thước mảng đã định nghĩa ở bên trái thì trình biên dịch sẽ báo lỗi. Vậy, câu hỏi đặt ra là nếu như số lượng các phần tử trong khai báo tập hợp là ít hơn kích thước mảng đã khai báo như ví dụ sau thì sẽ như thế nào? 17 int a[3] = { 3, 4}; Giải pháp mà cả C và C++ chọn giải quyết là: sẽ set giá trị 0 cho những phần tử không được định nghĩa ở vị trí tương ứng trong khai báo tập hợp ở vế phải. Điều đó có nghĩa rằng, với ví dụ trên, thì a[2] sẽ nhận giá trị 0, trong khi a[0]=3 và a[1]=4. Ngoài phương pháp khai báo mảng như trên,có một phương pháp khác ngắn gọn hơn và cũng tiện lợi hơn mà các lập trình viên có thể sử dụng đó là “automatic counting”. Khả năng này cho phép trình biên dịch tự động xác định kính thước tương ứng cho mảng khi duyệt qua tập hơp tương ứng các phần tử cho mảng ở vế phải. Nếu cần biết thông tin về số thành phần của mảng, ta có thể sử dụng công thức sizeof(object)/sizeof(*object) để xác định. int a[] = { 3, 4, 5, 6, 7}; int nums=sizeof(a)/sizeof(*a); for(int i=0;i<nums;i++) printf(“a[%d]=%d\n”,i,a[i]); Bên cạnh mảng, thì struct cũng là 1 dạng của object tập hợp. Cấu trúc và nguyên tắc khai báo 1 mảng các struct thì cũng hoàn toàn tương tự như việc khai báo 1 mảng thuộc kiểu dữ liệu built-in. typedef struct { int x; int y; }point; void main() { point pt1={1,2}; //pt1.x=1 ; pt1.y=2; point ptarr[3]={{1,3},{4,5}}; //ptarr[2].x=0; ptarr[2].y=0; } Trong trường hợp ta muốn khai báo theo phương pháp tập hợp 1 mảng các struct hoặc mảng các đối tượng thuộc 1 class nào đó đã được xây dựng hàm constructor, thì cách tốt nhất là ta nên gọi thực thi constructor để tạo ra các giá trị hằng rồi gán các giá trị hằng này vào trong tập hợp như sau: class point { 18 private: int x; int y; public: point(int tx,int ty){ x=tx; y=ty}; ~point(){} }; void main() { point ptarr[3]={point(1,3),point(4,5),point(3,6)}; } Khi khai báo 1 mảng các đối tượng point theo ví dụ trên, thì hàm constructor của lớp point sẽ được gọi 3 lần rồi gán các giá trị đó vào tập hợp bên phải, là cơ sở để khởi tạo mảng từ tập hợp được xây dựng ở bên phải thông qua việc gọi hàm constructor 1 cách tường minh cho từng phần tử. 9. DEFAULT CONSTRUCTOR. Hàm khởi tạo mặc định là hàm khởi tạo không nhận bất kỳ tham số nào và cũng không thực thi bất kỳ tác vụ nào trong thân hàm , đồng thời cũng là hàm mà trình biên dịch sẽ tự động phát sinh nếu không tìm thấy bất kỳ hàm constructor nào được xây dựng trong class. Khi 1 hàm constructor được định nghĩa trong class, thì hàm default constructor sẽ không được trình biên dịch phát sinh nữa. Trong nhiều trường hợp, thì hàm default constructor chiếm 1 vai trò khá quan trọng. Một minh chứng điển hình nhất cho tính quan trọng của hàm default constructor đó là việc khai báo một mảng các đối tượng cho class như sau: point a[5]; Câu lệnh trên hàm ý rằng lập trình viên muốn cấp phát vùng nhớ để lưu trữ 5 biến point, điều này cũng đồng nghĩa với việc trình biên dịch sẽ gọi thực thi 5 lần hàm constructor không nhận tham số đầu vào(thông thường là hàm default constructor). Nếu chẳng may lập trình viên chỉ xây dựng các hàm constructor có nhận tham số đầu vào cho class thì chắc chắn trình biên dịch sẽ báo lỗi với câu lệnh trên với 1 lý do đơn giản rằng trình biên dịch không tìm thấy hàm constructor thích hợp để gọi thực thi tương ứng. 19 Một ví dụ khác minh họa cho tính cần thiết của hàm default constructor đó là thao tác khai báo mảng theo phương pháp tập hợp như ví dụ sau: point a[2]={ point(3,5) }; Với việc khai báo như trên, trình biên dịch sẽ hiểu rằng phần tử a[1] sẽ nhận giá trị từ việc gọi thực thi hàm constructor không nhận tham số đầu vào của class point. Trong trường hợp class point không xây dựng bất kỳ hàm constructor nào, thì trình biên dịch sẽ phát sinh rồi gọi thực thi hàm default constructor đó. 10. SUMMARY. Qua những đề mục đã trình bày ở trên, ta có thể phần nào nắm được những tính năng mạnh mẽ mà C++ hỗ trợ cho các tác vụ initialization cũng như cleanup đối tượng. Với C++, những thao tác initialization & cleanup trở nên an toàn và tiện lợi hơn bao giờ hết. Bởi lẽ, với việc xây dựng constructor và destructor, C++ bảo đảm những thao tác initialization và cleanup luôn được thực hiện 1 cách đúng đắn và thích hợp nhất (trình biên dịch không cho phép một đối tượng được tạo mới hoặc hủy mà không gọi thực thi hàm constructor và destructor tương ứng). Bên cạnh đó là những tính năng khác như: tham số mặc định, copy constructor, khởi tạo mảng đối tượng theo dạng tập hợp,v.v… C++ thực sự mang lại cho các lập trình viên khái niệm của “an toàn và tiện lợi”. REFERENCE: 20 Chapter 6 – Initialization and cleanup - Thinking in C++ - Vol 1 – Bruce Eckel. The C programming language – Brian W.Kernighan , Dennis M.Ritchie. C++ - The complete reference – Third Edition – Herb Schildt. Lập trình hướng đối tượng với C++ - Phạm Văn Ất.
File đính kèm:
- Lập trình hướng đối tượng - Initialization & Cleanup.pdf