C++ và Lập trình hướng đối tượng - Chương 1: C++ và lập trình hướng đối tượng
Trong chương này trình bầy các vấn đề sau:
- Cách sử dụng phần mềm TC++ 3.0
- Những sửa đổi cần thiết một chương trình C để biến nó thành một chương trình C++ (chạy được trong môi trường C++)
- Tóm lược về các phương pháp lập trình cấu trúc và lập trình hướng đối tượng
- Những mở rộng của C++ so với C
a kiểu cấu trúc TS (thí sinh) gồm các thành phần : ht (họ tên), sobd (số báo danh), dt (điểm toán), dl (điểm lý), dh (điểm hoá) và td (tổng điểm), sau đó khai báo biến cấu trúc h và mảng cấu trúc ts. struct TS { char ht [25]; long sobd; float dt, dl, dh, td; } ; TS h, ts[1000] ; 6.2. Tên sau từ khoá union được xem như tên kiểu hợp Trong C++ một kiểu hợp (union) cũng được định nghĩa như C theo mẫu: union Tên_kiểu_hợp { // Khai báo các thành phần của hợp } ; Sau đó để khai báo các biến, mảng kiểu hợp , trong C dùng mẫu sau: union Tên_kiểu_hợp danh sách biến, mảng kiểu hợp ; Như vậy trong C, tên viết sau từ khoá union chưa phải là tên kiểu và chưa có thể dùng để khai báo. Trong C++ xem tên viết sau từ khoá union là tên kiểu hợp và có thể dùng nó để khai báo. Như vậy để khai báo các biến, mảng kiểu hợp, trong C++ có thể dùng mẫu sau: Tên_kiểu_hợp danh sách biến, mảng kiểu hợp ; 6.3. Các union không tên Trong C++ cho phép dùng các union không tên dạng: union { // Khai báo các thành phần } ; Khi đó các thành phần (khai báo trong union) sẽ dùng chung một vùng nhớ. Điều này cho phép tiết kiệm bộ nhớ và cho phép dễ dàng tách các byte của một vùng nhớ. Ví dụ nếu các biến nguyên i , biến ký tự ch và biến thực x không đồng thời sử dụng thì có thể khai báo chúng trong một union không tên như sau: union { int i ; char ch ; float x ; } ; Khi đó các biến i , ch và f sử dụng chung một vùng nhớ 4 byte. Xét ví dụ khác, để tách các byte của một biến unsigned long ta dùng union không tên sau: union { unsigned long u ; unsigned char b[4] ; }; Khí đó nếu gán u = 0xDDCCBBAA; // Số hệ 16 thì : b[0] = 0xAA b[1] = 0xBB b[2] = 0xCC b[3] = 0xDD 6.4. Kiểu liệt kê (enum) + Cũng giống như cấu trúc và hợp, tên viết sau từ khoá enum được xem là kiểu liệt kê và có thể dùng để khai báo, ví dụ: enum MAU { xanh, do, tim, vang } ; // Định nghĩa kiểu MAU MAU m, dsm[10] ; // Khai báo các biến, mảng kiểu MAU + Các giá trị kiểu liệt kê (enum) là các số nguyên. Do đó có thể thực hiện các phép tính trên các giá trị enum, có thể in các giá trị enum, có thể gán giá trị enum cho biến nguyên, ví dụ: MAU m1 , m2 ; int n1, n2 ; m1 = tim ; m2 = vàng ; n1 = m1 ; // n1 = 2 n2 = m1 + m2 ; // n2 = 5 printf (“\n %d “ , m2 ); // in ra số 3 + Không thể gán trực tiếp một giá trị nguyên cho một biến enum mà phải dùng phép ép kiểu, ví dụ: m1 = 2 ; // lỗi m1 = MAU(2) ; // đúng § 7. Cấp phát bộ nhớ 7.1. Trong C++ có thể sử dụng các hàm cấp phát bộ nhớ động của C như: hàm malloc để cấp phát bộ nhớ, hàm free để giải phóng bộ nhớ được cấp phát. 7.2. Ngoài ra trong C++ còn đưa thêm toán tử new để cấp phát bộ nhớ và toán tử delete để giải phóng bộ nhớ được cấp phát bởi new 7.3. Cách dùng toán tử new để cấp phát bộ nhớ như sau: + Trước hết cần khai báo một con trỏ để chứa địa chỉ vùng nhớ sẽ được cấp phát: Kiểu *p; ở đây Kiểu có thể là: - các kiểu dữ liệu chuẩn của C++ như int , long, float , double, char , ... - các kiểu do lập trình viên định nghĩa như: mảng, hợp, cấu trúc, lớp, ... + Sau đó dùng toán tử new theo mẫu: p = new Kiểu ; // Cấp phát bộ nhớ cho một biến (một phần tử) p = new Kiểu[n] ; //Cấp phát bộ nhớ cho n phần tử Ví dụ để cấp phát bộ nhớ cho một biến thực ta dùng câu lệnh sau: float *px = new float ; Để cấp phát bộ nhớ cho 100 phần tử nguyên ta dùng các câu lệnh: int *pn = new int[100] ; for (int i=0 ; i < 100 ; ++i ) pn[i] = 20*i ; // Gán cho phần tử thứ i 7.4. Hai cách kiểm tra sự thành công của new Khi dùng câu lệnh: Kiểu *p = new Kiểu[n] ; hoặc câu lệnh: Kiểu *p = new Kiểu ; để cấp phát bộ nhớ sẽ xuất hiện một trong 2 trường hợp: thành công hoặc không thành công. Nếu thành công thì p sẽ chứa địa chỉ đầu vùng nhớ được cấp phát. Nếu không thành công thì p = NULL. Đoạn chương trình sau minh hoạ cách kiểm tra lỗi cấp phát bộ nhớ: double *pd ; int n ; cout << “\n Số phần tử : “ ; cin >> n ; pd = new double[n] ; if (pd==NULL) { cout << “ Lỗi cấp phát bộ nhớ “ exit (0) ; } Cách thứ 2 để kiểm tra sự thành công của toán tử new là dùng con trỏ hàm: _new_handler được định nghĩa trong tệp “new.h”. Khi gặp lỗi trong toán tử new (cấp phát không thành công) thì chương trình sữ thực hiện một hàm nào đó do con trỏ _new_handler trỏ tới. Cách dùng con trỏ này như sau: + Xây dựng một hàm dùng để kiểm tra sự thành công của new + Gán tên hàm này cho con trỏ _new_handler Như vậy hàm kiểm tra sẽ được gọi mỗi khi có lỗi xẩy ra trong toán tử new. Đoạn chương trình kiểm tra theo cách thứ nhất có thể viết theo cách thứ hai như sau: void kiem_tra_new(void) // Lập hàm kiểm tra { cout << “ Lỗi cấp phát bộ nhớ “ exit (0) ; } _new_handler = kiem_tra_new // Gán tên hàm cho con trỏ double *pd ; int n ; cout << “\n Số phần tử : “ ; cin >> n ; pd = new double[n] ; // Khi xẩy ra lỗi sẽ gọi hàm kiểm_tra_new Chú ý: Có thể dùng lệnh gán để gán tên hàm xử lý lỗi cho con trỏ _new_handler như trong đoạn chương trình trên, hoặc dùng hàm: set_new_handler(Tên hàm) ; (xem các chương trình minh hoạ bên dưới) 7.5. Toán tử delete dùng để giải phóng vùng nhớ được cấp phát bởi new Cách dùng như sau: delete p ; // p là con trỏ dùng trong new Ví dụ: float *px ; px = new float[2000] ; // Cấp phát bộ nhớ cho 2000 phần tử thực // Sử dụng bộ nhớ được cấp phát delete px ; // giải phóng bộ nhớ 7.6. Hai chương trình minh hoạ Chương trình thứ nhất minh hoạ cách dùng new để cấp phát bộ nhớ chứa n thí sinh. Mỗi thí sinh là một cấu trúc gồm các trường ht (họ tên), sobd (số báo danh) và td (tổng điểm). Chương trình sẽ nhập n, cấp phát bộ nhớ chứa n thí sinh, kiểm tra lỗi cấp phát bộ nhớ (dùng cách 1), nhập n thí sinh, sắp xếp thí sinh theo thứ tự giảm của tổng điểm, in danh sách thí sinh sau khi sắp xếp, và cuối cùng là giải phóng bộ nhớ đã cấp phát. #include #include #include #include struct TS { char ht[20]; long sobd; float td; } ; void main(void) { TS*ts ; int n; cout << "\n So thi sinh n = " ; cin >> n; ts = new TS[n+1]; if(ts==NULL) { cout << "\nLoi cap phat bo nho " ; getch(); exit(0); } for (int i=1;i<=n;++i) { cout <<"\nThi sinh thu " << i; cout << "\nHo ten: " ; cin.ignore(1) ; cin.get(ts[i].ht,20); cout << "So bao danh: " ; cin >> ts[i].sobd ; cout << "Tong diem: " ; cin >> ts[i].td ; } for (i=1;i<=n-1;++i) for (int j=i+1;j<=n;++j) if (ts[i].td < ts[j].td) { TS tg=ts[i]; ts[i]=ts[j]; ts[j]=tg; } cout << setiosflags(ios::showpoint) << setprecision(1) ; for (i=1;i<=n;++i) cout << "\n" << setw(20) << ts[i].ht << setw(6)<< ts[i].sobd <<setw(6)<< ts[i].td; delete ts; getch(); } Chương trình thứ hai minh hoạ cách dùng con trỏ _new_handler để kiểm tra sự thành công của toán tử new. Chương trình sẽ cấp phát bộ nhớ cho một mảng con trỏ và sẽ theo rõi khi nào thì không đủ bộ nhớ để cấp phát. #include #include #include #include int k; void loi_bo_nho(void) { cout << "\nLoi bo nho khi cap phat bo nho cho q[" << k <<"]"; getch(); exit(0); } void main() { double *q[100] ; long n; clrscr(); set_new_handler(loi_bo_nho) ; // _new_handler=loi_bo_nho; n=10000; for ( k=0;k<100;++k) q[k] = new double[n]; cout << "Khong loi"; getch(); } § 8. Các hàm trong C++ Trong C++ có rất nhiều mở rộng, cải tiến về hàm làm cho việc xây dựng và sử dụng hàm rất tiện lợi. Điều này sẽ trình bầy kỹ trong chương sau. Trong mục này chỉ thống kê một số điểm mới về hàm mà C++ đưa vào. 8.1. Đối kiểu tham chiếu Trong C, để nhận kết quả của hàm cần dùng đối con trỏ, làm cho việc xây dựng cũng như sử dụng hàm khá phiền phức. Trong C++ đưa vào đối kiểu tham chiếu (giống như PASCAL) dùng để chứa kết quả của hàm, khiến cho việc tạo lập cũng như sử dụng hàm đơn giản hơn. 8.2. Đối tham chiếu const Đối tham chiếu có đặc điểm là các câu lệnh trong thân hàm có thể truy nhập tới và dễ dàng làm cho giá trị của nó thay đổi. Nhiều khi ta muốn dùng đối kiểu tham chiếu chỉ để tăng tốc độ trao đổi dữ liệu giữa các hàm , không muốn dùng nó để chứa kết quả của hàm. Khi đó có thể dùng đối tham chiếu const để bảo toàn giá trị của đối trong thân hàm. 8.3. Đối có giá trị mặc định Trong nhiều trương hợp người dùng viết một lời gọi hàm nhưng còn chưa biết nên chọn giá trị nào cho các đối . Để khắc phục khó khăn này, C++ đưa ra giải pháp đối có giá trị mặc định. Khi xây dựng hàm, ta gán giá trị mặc định cho một số đối. Người dùng nếu không cung cấp giá trị cho các đối này, thì hàm sẽ dùng giá trị mặc định. 8.4. Hàm on line Đối với một đoạn chương trình nhỏ (số lệnh không lớn) thì việc thay các đoạn chương trình này bằng các lời gọi hàm sẽ làm cho chương trình gọn nhẹ đôi chút nhưng làm tăng thời gian máy. Trong các trường hợp này có thể dùng hàm trực tuyến (on line) vừa giảm kích thước chương trình nguồn, vừa không làm tăng thời gian chạy máy. 8.5. Các hàm trùng tên (định nghĩa chồng các hàm) Để lấy giá trị tuyệt đối của một số, trong C cần lập ra nhiều hàm với tên khác nhau, ví dụ abs cho số nguyên, fabs cho số thực, labs cho số nguyên dài, cabs cho số phức. Điều này rõ ràng gây phiền toái cho người sử dụng. Trong C++ cho phép xây dựng các hàm trùng tên nhưng khác nhau về kiểu đối. Như vậy chỉ cần lập một hàm để lấy giá trị tuyệt đối cho nhiều kiểu dữ liệu khác nhau. 8.6. Định nghĩa chồng toán tử Việc dùng các phép toán thay cho một lời gọi hàm rõ ràng làm cho chương trình ngắn gọn, sáng sủa hơn nhiều. Ví dụ để thực hiện phép cộng 2 ma trận nếu dùng phép cộng và viết: C = A + B ; thì rất gần với toán học. Trong C++ cho phép dùng các phép toán chuẩn để đặt tên cho các hàm (gọi là định nghĩa chồng toán tử). Sau đó có thể thay lời gọi hàm bằng các phép toán như nói ở trên. Như vậy một phép toán mang nhiều ý nghĩa, ví dụ phép + có thể hiểu là cộng 2 số nguyên, 2 số thực hoặc 2 ma trận. C++ sẽ căn cứ vào kiểu của các số hạng mà quyết định chọn phép cộng cụ thể.
File đính kèm:
- C++ và Lập trình hướng đối tượng - Chương 1 C++ và lập trình hướng đối tượng.DOC