CÁC MỞ RỘNG CỦA C++

27 584 0
CÁC MỞ RỘNG CỦA C++

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Giáo trình môn Lập trình hướng đối tượng Trang Biên soạn: Lê Thị Mỹ Hạnh 12 CHƯƠNG 2 CÁC MỞ RỘNG CỦA C++ I. LỊCH SỬ CỦA C++ Vào những năm đầu thập niên 1980, người dùng biết C++ với tên gọi "C with Classes" được tả trong hai bài báo của Bjarne Stroustrup (thuộc AT&T Bell Laboratories) với nhan đề "Classes: An Abstract Data Type Facility for the C Language" và "Adding Classes to C : AnExercise in Language Evolution". Trong công trình này, tác giả đã đề xuất khái niệm lớp, bổ sung việc kiểm tra kiểu tham số của hàm, các chuyển đổi kiểu và một số mở rộng khác vào ngôn ngữ C. Bjarne Stroustrup nghiên cứu mở rộng ngôn ngữ C nhằm đạt đến một ngôn ngữ phỏng (simulation language) với những tính năng hướ ng đối tượng. Trong năm 1983, 1984, ngôn ngữ "C with Classes" được thiết kế lại, mở rộng hơn rồi một trình biên dịch ra đời. Và chính từ đó, xuất hiện tên gọi "C++". Bjarne Stroustrup tả ngôn ngữ C++ lần đầu tiên trong bài báo có nhan đề "Data Abstraction in C". Sau một vài hiệu chỉnh C++ được công bố rộng rãi trong quyển "The C++ Programming Language" của Bjarne Stroustrup xuất hiện đánh dấu sự hiện diện thực sự của C++, người lập tình chuyên nghiệp từ đ ây đã có một ngôn ngữ đủ mạnh cho các dữ án thực tiễn của mình. Về thực chất C++ giống như C nhưng bổ sung thêm một số mở rộng quan trọng, đặc biệt là ý tưởng về đối tượng, lập trình định hướng đối tượng.Thật ra các ý tưởng về cấu trúc trong C++ đã xuất phát vào các năm 1970 từ Simula 70 và Algol 68. Các ngôn ngữ này đã đưa ra các khái niệm về lớp và đơn thể. Ada là m ột ngôn ngữ phát triển từ đó, nhưng C++ đã khẳng định vai trò thực sự của mình. II. CÁC MỞ RỘNG CỦA C++ II.1. Các từ khóa mới của C++ Để bổ sung các tính năng mới vào C, một số từ khóa (keyword) mới đã được đưa vào C++ ngoài các từ khóa có trong C. Các chương trình bằng C nào sử dụng các tên trùng với các từ khóa cần phải thay đổi trước khi chương trình được dịch lại bằng C++. Các từ khóa mới này là : asm catch class delete friend inline new operator private protected public template this throw try virtual II.2. Cách ghi chú thích C++ chấp nhận hai kiểu chú thích. Các lập trình viên bằng C đã quen với cách chú thích bằng /*…*/. Trình biên dịch sẽ bỏ qua mọi thứ nằm giữa /*…*/. Ví dụ 2.1 : Trong chương trình sau : #include <iostream.h> int main() { int I; for(I = 0; I < 10 ; ++ I) // 0 - 9 cout<<I<<"\n"; // In ra return 0; } Giáo trình môn Lập trình hướng đối tượng Trang Biên soạn: Lê Thị Mỹ Hạnh 13 Mọi thứ nằm giữa /*…*/ từ dòng 1 đến dòng 3 đều được chương trình bỏ qua. Chương trình này còn minh họa cách chú thích thứ hai. Đó là cách chú thích bắt đầu bằng // ở dòng 8 và dòng 9. Chúng ta chạy ví dụ 2.1, kết quả ở hình 2.1. Hình 2.1: Kết quả của ví dụ 2.1 Nói chung, kiểu chú thích /*…*/ được dùng cho các khối chú thích lớn gồm nhiều dòng, còn kiểu // được dùng cho các chú thích một dòng. II.3. Dòng nhập/xuất chuẩn Trong chương trình C, chúng ta thường sử dụng các hàm nhập/xuất dữ liệu là printf() và scanf(). Trong C++ chúng ta có thể dùng dòng nhập/xuất chuẩn (standard input/output stream) để nhập/xuất dữ liệu thông qua hai biến đối tượng của dòng (stream object) là cout và cin. Ví dụ 2.2: Chương trình nhập vào hai số. Tính tổng và hiệu của hai số vừa nhập. //Chuong trinh 2.2 #include <iostream.h> int main() { int X, Y; cout<< "Nhap vao mot so X:"; cin>>X; cout<< "Nhap vao mot so Y:"; cin>>Y; cout<<"Tong cua chung:"<<X+Y<<"\n"; cout<<"Hieu cua chung:"<<X-Y<<"\n"; return 0; } Để thực hiện dòng xuất chúng ta sử dụng biến cout (console output) kết hợp với toán tử chèn (insertion operator) << như ở các dòng 5, 7, 9 và 10. Còn dòng nhập chúng ta sử dụng biến cin (console input) kết hợp với toán tử trích (extraction operator) >> như ở các dòng 6 và 8. Khi sử dụng cout hay cin, chúng ta phải kéo file iostream.h như dòng 1. Chúng ta sẽ tìm hiểu kỹ về dòng nhập/xuất ở chương 8. Chúng ta chạy ví dụ 2.2 , kết quả ở hình 2.2. Hình 2.2: Kết quả của ví dụ 2.2 Giáo trình môn Lập trình hướng đối tượng Trang Biên soạn: Lê Thị Mỹ Hạnh 14 Hình 2.3: Dòng nhập/xuất dữ liệu II.4. Cách chuyển đổi kiểu dữ liệu Hình thức chuyển đổi kiểu trong C tương đối tối nghĩa, vì vậy C++ trang bị thêm một cách chuyển đổi kiểu giống như một lệnh gọi hàm. Ví dụ 2.3: #include <iostream.h> int main() { int X = 200; long Y = (long) X; //Chuyen doi kieu theo cach cua C long Z = long(X); //Chuyen doi kieu theo cach moi cua C++ cout<< "X = "<<X<<"\n"; cout<< "Y = "<<Y<<"\n"; cout<< "Z = "<<Z<<"\n"; return 0; } Chúng ta chạy ví dụ 2.3 , kết quả ở hình 2.4. Hình 2.4: Kết quả của ví dụ 2.3 II.5. Vị trí khai báo biến Trong chương trình C đòi hỏi tất cả các khai báo bên trong một phạm vi cho trước phải được đặt ở ngay đầu của phạm vi đó. Điều này có nghĩa là tất cả các khai báo toàn cục phải đặt trước tất cả các hàm và các khai báo cục bộ phải được tiến hành trước tất cả các lệnh thực hiện. Ngược lại C++ cho phép chúng ta khai báo linh hoạt bất kỳ vị trí nào trong một phạm vi cho trước (không nhất thiết ph ải ngay đầu của phạm vi), chúng ta xen kẽ việc khai báo dữ liệu với các câu lệnh thực hiện. Ví dụ 2.4: Chương trình phỏng một máy tính đơn giản 1: #include <iostream.h> 2: int main() 3: { 4: int X; Giáo trình môn Lập trình hướng đối tượng Trang Biên soạn: Lê Thị Mỹ Hạnh 15 5: cout<< "Nhap vao so thu nhat:"; 6: cin>>X; 7: int Y; 8: cout<< "Nhap vao so thu hai:"; 9: cin>>Y; 10: char Op; 11: cout<<"Nhap vao toan tu (+-*/):"; 12: cin>>Op; 13: switch(Op) 14: { 15: case ‘+’: 16: cout<<"Ket qua:"<<X+Y<<"\n"; 17: break; 18: case ‘-’: 19: cout<<"Ket qua:"<<X-Y<<"\n"; 20: break; 21: case ‘*’: 22: cout<<"Ket qua:"<<long(X)*Y<<"\n"; 23: break; 24: case ‘/’: 25: if (Y) 26: cout<<"Ket qua:"<<float(X)/Y<<"\n"; 27: else 28: cout<<"Khong the chia duoc!" <<"\n"; 9; 9; 29: break; 30: default : 31: cout<<"Khong hieu toan tu nay!"<<"\n"; 32: } 33: return 0; 34: } Trong chương trình chúng ta xen kẻ khai báo biến với lệnh thực hiện ở dòng 4 đến dòng 12. Chúng ta chạy ví dụ 2.4, kết quả ở hình 2.5. Hình 2.5: Kết quả của ví dụ 2.4 Khi khai báo một biến trong chương trình, biến đó sẽ có hiệu lực trong phạm vi của chương trình đó kể từ vị trí nó xuất hiện. Vì vậy chúng ta không thể sử dụng một biến được khai báo bên dưới nó. II.6. Các biến const Trong ANSI C, muốn định nghĩa một hằng có kiểu nhất định thì chúng ta dùng biến const (vì nếu dùng #define thì tạo ra các hằng không có chứa thông tin về kiểu). Trong C++, các biến const linh hoạt hơn một cách đáng kể: C++ xem const cũng như #define nếu như chúng ta muốn dùng hằng có tên trong chương trình. Chính vì vậy chúng ta có thể dùng const để quy định kích thước của một mảng như đoạn mã sau: const int ArraySize = 100; int X[ArraySize]; Khi khai báo một biến const trong C++ thì chúng ta ph ải khởi tạo một giá trị ban đầu nhưng đối với ANSI C thì không nhất thiết phải làm như vậy (vì trình biên dịch ANSI C tự động gán trị zero cho biến const nếu chúng ta không khởi tạo giá trị ban đầu cho nó). Giáo trình môn Lập trình hướng đối tượng Trang Biên soạn: Lê Thị Mỹ Hạnh 16 Phạm vi của các biến const giữa ANSI C và C++ khác nhau. Trong ANSI C, các biến const được khai báo ở bên ngoài mọi hàm thì chúng có phạm vi toàn cục, điều này nghĩa là chúng có thể nhìn thấy cả ở bên ngoài file mà chúng được định nghĩa, trừ khi chúng được khai báo là static. Nhưng trong C++, các biến const được hiểu mặc định là static. II.7. Về struct, union và enum Trong C++, các struct và union thực sự các các kiểu class. Tuy nhiên có sự thay đổi đối với C++. Đó là tên của struct và union được xem luôn là tên kiểu giống như khai báo bằng lệnh typedef vậy. Trong C, chúng ta có thể có đoạn mã sau : struct Complex { float Real; float Imaginary; }; ………………… struct Complex C; Trong C++, vấn đề trở nên đơn giản hơn: struct Complex { float Real; float Imaginary; }; ………………… Complex C; Quy định này cũng áp dụng cho cả union và enum. Tuy nhiên để tương thích với C, C++ vẫn chấp nhận cú pháp cũ. Một kiểu union đặc biệt được thêm vào C++ gọi là union nặc danh (anonymous union). Nó chỉ khai báo một loạt các trường(field) dùng chung một vùng địa chỉ bộ nhớ. Một union nặc danh không có tên tag, các trường có thể được truy xuất trực tiếp bằng tên của chúng. Chẳng hạn như đoạn mã sau: union { int Num; float Value; }; Cả hai Num và Value đều dùng chung một vị trí và không gian bộ nhớ. Tuy nhiên không giống như kiểu union có tên, các trường của union nặc danh thì được truy xuất trực tiếp, chẳng hạn như sau: Num = 12; Value = 30.56; II.8. Toán tử định phạm vi Toán tử định phạm vi (scope resolution operator) ký hiệu là ::, nó được dùng truy xuất một phần tử bị che bởi phạm vi hiện thời. Ví dụ 2.5 : 1: #include <iostream.h> 2: int X = 5; 3: int main() 4: { 5: int X = 16; 6: cout<< "Bien X ben trong = "<<X<<"\n"; 7: cout<< "Bien X ben ngoai = "<<::X<<"\n"; 8: return 0; 9: } Chúng ta chạy ví dụ 2.5 , kết quả ở hình 2.6 Hình 2.6: Kết quả của ví dụ 2.5 Giáo trình môn Lập trình hướng đối tượng Trang Biên soạn: Lê Thị Mỹ Hạnh 17 Toán tử định phạm vi còn được dùng trong các định nghĩa hàm của các phương thức trong các lớp, để khai báo lớp chủ của các phương thức đang được định nghĩa đó. Toán tử định phạm vi còn có thể được dùng để phân biệt các thành phần trùng tên của các lớp cơ sở khác nhau. II.9. Toán tử new và delete Trong các chương trình C, tất cả các cấp phát động bộ nhớ đều được xử lý thông qua các hàm thư viện như malloc(), calloc() và free(). C++ định nghĩa một phương thức mới để thực hiện việc cấp phát động bộ nhớ bằng cách dùng hai toán tử new và delete. Sử dụng hai toán tử này sẽ linh hoạt hơn rất nhiều so với các hàm thư viện của C. Đoạn chươ ng trình sau dùng để cấp phát vùng nhớ động theo lối cổ điển của C. int *P; P = malloc(sizeof(int)); if (P==NULL) printf("Khong con du bo nho de cap phat\n"); else { *P = 290; printf("%d\n", *P); free(P); } Trong C++, chúng ta có thể viết lại đoạn chương trình trên như sau: int *P; P = new int; if (P==NULL) cout<<"Khong con du bo nho de cap phat\n"; else { *P = 290; cout<<*P<<"\n"; delete P; } Chúng ta nhận thấy rằng, cách viết của C++ sáng sủa và dễ sử dụng hơn nhiều. Toán tử new thay thế cho hàm malloc() hay calloc() của C có cú pháp như sau : new type_name new ( type_name ) new type_name initializer new ( type_name ) initializer Trong đó : type_name: tả kiểu dữ liệu được cấp phát. Nếu kiểu dữ liệu tả phức tạp, nó có thể được đặt bên trong các dấu ngoặc. initializer: Giá trị khởi động c ủa vùng nhớ được cấp phát. Nếu toán tử new cấp phát không thành công thì nó sẽ trả về giá trị NULL. Còn toán tử delete thay thế hàm free() của C, nó có cú pháp như sau : delete pointer delete [] pointer Chúng ta có thể vừa cấp phát vừa khởi động như sau : int *P; P = new int(100); if (P!=NULL) { cout<<*P<<"\n"; delete P; } else cout<<"Khong con du bo nho de cap phat\n"; Giáo trình môn Lập trình hướng đối tượng Trang Biên soạn: Lê Thị Mỹ Hạnh 18 Để cấp phát một mảng, chúng ta làm như sau : int *P; P = new int[10]; //Cấp phát mảng 10 số nguyên if (P!=NULL) { for(int I = 0;I<10;++) P[I]= I; for(I = 0;I<10;++) cout<<P[I]<<"\n"; delete []P; }else cout<<"Khong con du bo nho de cap phat\n"; Chú ý : Đối với việc cấp phát mảng chúng ta không thể vừa cấp phát vừa khởi động giá trị cho chúng, chẳng hạn đoạn chương trình sau là sai : int *P; P = new (int[10])(3); //Sai !!! Ví dụ 2.6: Chương trình tạo một mảng động, khởi động mảng này với các giá trị ngẫu nhiên và sắp xếp chúng. 1: #include <iostream.h> 2: #include <time.h> 3: #include <stdlib.h> 4: int main() 5: { 6: int N; 7: cout<<"Nhap vao so phan tu cua mang:"; 8: cin>>N; 9: int *P=new int[N]; 10: if (P==NULL) 11: { 12: cout<<"Khong con bo nho de cap phat\n"; 13: return 1; 14: } 15: srand((unsigned)time(NULL)); 16: for(int I=0;I<N;++I) 17: P[I]=rand()%100; //Tạo các số ngẫu nhiên từ 0 đến 99 18: cout<<"Mang truoc khi sap xep\n"; 19: for(I=0;I<N;++I) 20: cout<<P[I]<<" "; 21: for(I=0;I<N-1;++I) 22: for(int J=I+1;J<N;++J) 23: if (P[I]>P[J]) 24: { 25: int Temp=P[I]; 26: P[I]=P[J]; 27: P[J]=Temp; 28: } 29: cout<<"\nMang sau khi sap xep\n"; 30: for(I=0;I<N;++I) 31: cout<<P[I]<<" "; 32: delete []P; 33: return 0; 34: } Giáo trình môn Lập trình hướng đối tượng Trang Biên soạn: Lê Thị Mỹ Hạnh 19 Chúng ta chạy ví dụ 2.6, kết quả ở hình 2.7 Hình 2.7: Kết quả của ví dụ 2.6 Ví dụ 2.7: Chương trình cộng hai ma trận trong đó mỗi ma trận được cấp phát động. Chúng ta có thể xem mảng hai chiều như mảng một chiều như hình 2.8 Hình 2.8: Mảng hai chiều có thể xem như mảng một chiều. Gọi X là mảng hai chiều có kích thước m dòng và n cột. A là mảng một chiều tương ứng. Nếu X[i][j] chính là A[k] thì k = i*n + j Chúng ta có chương trình như sau : 1: #include <iostream.h> 2: #include <conio.h> 3: //prototype 4: void AddMatrix(int * A,int *B,int*C,int M,int N); 5: int AllocMatrix(int **A,int M,int N); 6: void FreeMatrix(int *A); 7: void InputMatrix(int *A,int M,int N,char Symbol); 8: void DisplayMatrix(int *A,int M,int N); 9: 10: int main() 11: { 12: int M,N; 13: int *A = NULL,*B = NULL,*C = NULL; 14: 15: clrscr(); 16: cout<<"Nhap so dong cua ma tran:"; 17: cin>>M; 18: cout<<"Nhap so cot cua ma tran:"; 19: cin>>N; 20: //Cấp phát vùng nhớ cho ma trận A 21: if (!AllocMatrix(&A,M,N)) 22: { //endl: Xuất ra kí tự xuống dòng (‘\n’) 23: cout<<"Khong con du bo nho!"<<endl; 24: return 1; 25: } Giáo trình môn Lập trình hướng đối tượng Trang Biên soạn: Lê Thị Mỹ Hạnh 20 26: //Cấp phát vùng nhớ cho ma trận B 27: if (!AllocMatrix(&B,M,N)) 28: { 29: cout<<"Khong con du bo nho!"<<endl; 30: FreeMatrix(A);//Giải phóng vùng nhớ A 31: return 1; 32: } 33: //Cấp phát vùng nhớ cho ma trận C 34: if (!AllocMatrix(&C,M,N)) 35: { 36: cout<<"Khong con du bo nho!"<<endl; 37: FreeMatrix(A);//Giải phóng vùng nhớ A 38: FreeMatrix(B);//Giải phóng vùng nhớ B 39: return 1; 40: } 41: cout<<"Nhap ma tran thu 1"<<endl; 42: InputMatrix(A,M,N,'A'); 43: cout<<"Nhap ma tran thu 2"<<endl; 44: InputMatrix(B,M,N,'B'); 45: clrscr(); 46: cout<<"Ma tran thu 1"<<endl; 47: DisplayMatrix(A,M,N); 48: cout<<"Ma tran thu 2"<<endl; 49: DisplayMatrix(B,M,N); 50: AddMatrix(A,B,C,M,N); 51: cout<<"Tong hai ma tran"<<endl; 52: DisplayMatrix(C,M,N); 53: FreeMatrix(A);//Giải phóng vùng nhớ A 54: FreeMatrix(B);//Giải phóng vùng nhớ B 55: FreeMatrix(C);//Giải phóng vùng nhớ C 56: return 0; 57: } 68: //Cộng hai ma trận 69: void AddMatrix(int *A,int *B,int*C,int M,int N) 70: { 71: for(int I=0;I<M*N;++I) 72: C[I] = A[I] + B[I]; 73: } 74: //Cấp phát vùng nhớ cho ma trận 75: int AllocMatrix(int **A,int M,int N) 76: { 77: *A = new int [M*N]; 78: if (*A == NULL) 79: return 0; 80: return 1; 81: } 82: //Giải phóng vùng nhớ 83: void FreeMatrix(int *A) 84: { 85: if (A!=NULL) 86: delete [] A; 87: } 88: //Nhập các giá trị của ma trận 89: void InputMatrix(int *A,int M,int N,char Symbol) 90: { 91: for(int I=0;I<M;++I) 92: for(int J=0;J<N;++J) 93 { Giáo trình môn Lập trình hướng đối tượng Trang Biên soạn: Lê Thị Mỹ Hạnh 21 94: cout<<Symbol<<"["<<I<<"]["<<J<<"]="; 95: cin>>A[I*N+J]; 96: } 97: } 100: //Hiển thị ma trận 101: void DisplayMatrix(int *A,int M,int N) 102: { 103: for(int I=0;I<M;++I) 104: { 105: for(int J=0;J<N;++J) 106: { 107: out.width(7);//canh le phai voi chieu dai 7 ky tu 108: cout<<A[I*N+J]; 109: } 110: cout<<endl; 111: } 112: } Chúng ta chạy ví du 2.7 , kết quả ở hình 2.9 Hình 2.9: Kết quả của ví dụ 2.7 Một cách khác để cấp phát mảng hai chiều A gồm M dòng và N cột như sau: int ** A = new int *[M]; int * Tmp = new int[M*N]; for(int I=0;I<M;++I) { A[I]=Tmp; Tmp+=N; } //Thao tác trên mảng hai chiều A ………………… delete [] *A; delete [] A; Toán tử new còn có một thuận lợi khác, đó là tất cả các lỗi cấp phát động đều có thể bắt được bằng một hàm xử lý lỗi do người dùng tự định nghĩa. C++ có định nghĩa một con trỏ (pointer) trỏ đến hàm đặc biệt. [...]... nghĩa của toán tử nào đó có các tham số đối sánh với các toán hạng để dùng Chúng ta sẽ tìm hiểu kỹ về việc đa năng hóa các toán tử trong chương 4 Các giới hạn của đa năng hóa toán tử: • Chúng ta không thể định nghĩa các toán tử mới • Hầu hết các toán tử của C++ đều có thể được đa năng hóa Các toán tử sau không được đa năng hóa là : Toán tử Ý nghĩa Toán tử định phạm vi :: Truy cập đến con trỏ là trường của. .. của class .* Truy cập đến trường của struct hay thành viên của class Toán tử điều kiện ?: sizeof và chúng ta cũng không thể đa năng hóa bất kỳ ký hiệu tiền xử lý nào • Chúng ta không thể thay đổi thứ tự ưu tiên của một toán tử hay không thể thay đổi số các toán hạng của nó • Chúng ta không thể thay đổi ý nghĩa của các toán tử khi áp dụng cho các kiểu có sẵn • Đa năng hóa các toán tử không thể có các. .. có phiên bản hàm nào để đối sánh) Các giới hạn của việc đa năng hóa các hàm: • Bất kỳ hai hàm nào trong tập các hàm đã đa năng phải có các tham số khác nhau • Các hàm đa năng hóa với danh sách các tham số cùng kiểu chỉ dựa trên kiểu trả về của hàm thì trình biên dịch báo lỗi Chẳng hạn như, các khai báo sau là không hợp lệ: void Print(int X); int Print(int X); Không có cách nào để trình biên dịch nhận... cho cùng kết quả nhưng lệnh của C++ thì dễ hiểu hơn C++ làm được điều này bằng cách tạo ra các hàm định nghĩa cách thực hiện của một toán tử cho các kiểu dữ liệu tự định nghĩa Một hàm định nghĩa một toán tử có cú pháp sau: data_type operator operator_symbol ( parameters ) { ……………………………… } Trong đó: data_type: Kiểu trả về operator_symbol: Ký hiệu của toán tử parameters: Các tham số (nếu có) Trong chương... nguyên dương h (2 . phát triển từ đó, nhưng C++ đã khẳng định vai trò thực sự của mình. II. CÁC MỞ RỘNG CỦA C++ II.1. Các từ khóa mới của C++ Để bổ sung các tính năng mới vào. soạn: Lê Thị Mỹ Hạnh 12 CHƯƠNG 2 CÁC MỞ RỘNG CỦA C++ I. LỊCH SỬ CỦA C++ Vào những năm đầu thập niên 1980, người dùng biết C++ với tên gọi "C with Classes"

Ngày đăng: 06/10/2013, 16:20

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan