Tải bản đầy đủ (.docx) (30 trang)

Lịch sử 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 (313.7 KB, 30 trang )

2.1 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.
2.2 CÁC MỞ RỘNG CỦA C++
2.2.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

2.2.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 :
CT2_1.CPP
1: /*
2: Chương trình in các số từ 0 đến 9.
3: */
4: #include <iostream.h>
5: int main()
6: {
7: int I;
8: for(I = 0; I < 10 ; ++ I)// 0 - 9
9: cout<<I<<"\n"; // In ra 0 - 9
10: return 0;
11: }
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.
2.2.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.
CT2_2.CPP
1: #include <iostream.h>
2: int main()
3: {

4: int X, Y;
5: cout<< "Nhap vao mot so X:";
6: cin>>X;
7: cout<< "Nhap vao mot so Y:";
8: cin>>Y;
9: cout<<"Tong cua chung:"<<X+Y<<"\n";
10: cout<<"Hieu cua chung:"<<X-Y<<"\n";
11: return 0;
12: }
Để 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
Hình 2.3: Dòng nhập/xuất dữ liệu
2.2.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:
CT2_3.CPP
1: #include <iostream.h>
2: int main()
3: {
4: int X = 200;
5: long Y = (long) X; //Chuyển đổi kiểu theo cách của C
6: long Z = long(X); // Chuyển đ ổi kiểu theo cách mới
của C++
7: cout<< "X = "<<X<<"\n";
8: cout<< "Y = "<<Y<<"\n";
9: cout<< "Z = "<<Z<<"\n";

10: return 0;
11: }
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
2.2.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
CT2_4.CPP
1: #include <iostream.h>
2: int main()
3: {
4: int X;
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ó.
2.2.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ó).
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.
2.2.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;
2.2.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 :
CT2_5.CPP
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
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.
2.2.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";
Để 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.
CT2_6.CPP
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: }
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 :
CT2_7.CPP

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: }
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 {
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);//Hien thi 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. Khi toán tử new được sử dụng để cấp phát động và một lỗi
xảy ra do cấp phát, C++ tự gọi đến hàm được chỉ bởi con trỏ này. Định nghĩa của con trỏ
này như sau:

typedef void (*pvf)();
pvf _new_handler(pvf p);
Điều này có nghĩa là con trỏ _new_handler là con trỏ trỏ đến hàm không có tham số và
không trả về giá trị. Sau khi chúng ta định nghĩa hàm như vậy và gán địa chỉ của nó cho
_new_handler chúng ta có thể bắt được tất cả các lỗi do cấp phát động.
Ví dụ 2.8:
CT2_8.CPP
1: #include <iostream.h>
2: #include <stdlib.h>

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

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