Tải bản đầy đủ (.pdf) (27 trang)

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

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (921.63 KB, 27 trang )

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 mô 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ữ mô 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 mô 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 mô 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: Mô tả kiểu dữ liệu được cấp phát. Nếu kiểu dữ liệu mô 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.

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×