Các mở rộng của C++ - 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ử ...
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 ");
else
{
*P = 290;
printf("%d ", *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 ";
else
{
*P = 290;
cout<<*P<<" ";
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<<" ";
delete P;
}
else
cout<<"Khong con du bo nho de cap phat ";
Để 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]<<" ";
delete []P;
}
else
cout<<"Khong con du bo nho de cap phat ";
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
";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 9918: cout<<"Mang truoc khi sap xep
";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<<"
Mang sau khi sap xep
";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: //prototype4: 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 A21: if (!AllocMatrix(&A,M,N))22: { //endl: Xuất ra kí tự xuống dòng (‘
’)23: cout<<"Khong con du bo nho!"<<endl; 24: return 1;25: }26: //Cấp phát vùng nhớ cho ma trận B27: if (!AllocMatrix(&B,M,N))28: {29: cout<<"Khong con du bo nho!"<<endl;30: FreeMatrix(A);//Giải phóng vùng nhớ A31: return 1;32: }33: //Cấp phát vùng nhớ cho ma trận C34: if (!AllocMatrix(&C,M,N))35: {36: cout<<"Khong con du bo nho!"<<endl;37: FreeMatrix(A);//Giải phóng vùng nhớ A38: FreeMatrix(B);//Giải phóng vùng nhớ B39: 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ớ A54: FreeMatrix(B);//Giải phóng vùng nhớ B55: FreeMatrix(C);//Giải phóng vùng nhớ C56: return 0;57: }68: //Cộng hai ma trận69: 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ận75: 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ận89: 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ận101: 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.awidth(7);//Hien thi canh le phai voi chieu dai 7 ky tu108: 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>3: #include <new.h>4:5: void MyHandler();6:7: unsigned long I = 0; 9; 8: void main()9: {10: int *A;11: _new_handler = MyHandler;12: for( ; ; ++I)13: A = new int;14: 15: }16:17: void MyHandler()18: {19: cout<<"Lan cap phat thu "<<I<<endl;20: cout<<"Khong con du bo nho!"<<endl;21: exit(1);22: }![]() ![]() |
||
|
||
|
Sử dụng con trỏ _new_handler chúng ta phải include file new.h như ở dòng 3. Chúng ta chạy ví dụ 2.8, kết quả ở hình 2.10.
Hình 2.10: Kết quả của ví dụ 2.8
Thư viện cũng còn có một hàm được định nghĩa trong new.h là hàm có prototype sau :
void ( * set_new_handler(void (* my_handler)() ))();
Hàm set_new_handler() dùng để gán một hàm cho _new_handler.
Ví dụ 2.9:
|
||
CT2_9.CPP 1: #include <iostream.h>2: #include <new.h>3: #include <stdlib.h>4:5: void MyHandler();6:7: int main(void)8: {9:10: char *Ptr;11:12: set_new_handler(MyHandler);13: Ptr = new char[64000u];14: set_new_handler(0); //Thiết lập lại giá trị mặc định15: return 0;16: }17:18: void MyHandler()19: {20: cout <<endl<<"Khong con du bo nho";21: exit(1);22 } ![]() ![]() |
||
|
||
|
Chúng ta chạy ví dụ 2.9, kết quả ở hình 2.11
Hình 2.11: Kết quả của ví dụ 2.9
Tiếp theo phần 2
Một chương trình có cấu trúc tốt sử dụng các hàm để chia chương trình thành các đơn vị độc lập có logic riêng. Tuy nhiên, các hàm thường phải chứa một loạt các xử lý điểm vào (entry point): tham số phải được đẩy vào stack, một lệnh gọi phải được thực hiện và sau đó việc quay trở về cũng phải được thực hiện bằng cách giải phóng các tham số ra khỏi stack. Khi các xử lý điểm vào chậm chạp thường các lập trình viên C phải sử dụng cách chép lập lại các đoạn chương trình nếu muốn tăng hiệu quả.
Để tránh khỏi phải xử lý điểm vào, C++ trang bị thêm từ khóa inline để loại việc gọi hàm. Khi đó trình biên dịch sẽ không biên dịch hàm này như một đoạn chương trình riêng biệt mà nó sẽ được chèn thẳng vào các chỗ mà hàm này được gọi. Điều này làm giảm việc xử lý điểm vào mà vẫn cho phép một chương trình được tổ chức dưới dạng có cấu trúc. Cú pháp của hàm inline như sau :
inline data_type function_name ( parameters )
{
……………………………..
}
Trong đó:
data_type: Kiểu trả về của hàm.
Function_name:Tên của hàm.
Parameters: Các tham số của hàm.
Ví dụ 2.10: Tính thể tích của hình lập phương
|
||
CT2_10.CPP 1: #include <iostream.h>2: inline float Cube(float S)3: {4: return S*S*S;5: } 6: 7: int main()8: {9: cout<<"Nhap vao chieu dai canh cua hinh lap phuong:";10: float Side;11: cin>>Side;12: cout<<"The tich cua hinh lap phuong = "<<Cube(Side);13: return 0;14: }![]() ![]() |
||
|
||
|
Chúng ta chạy ví dụ 2.10, kết quả ở hình 2.12
Hình 2.12: Kết quả của ví dụ 2.10
Chú ý:
Sử dụng hàm inline sẽ làm cho chương trình lớn lên vì trình biên dịch chèn đoạn chương trình vào các chỗ mà hàm này được gọi. Do đó thường các hàm inline thường là các hàm nhỏ, ít phức tạp.
Các hàm inline phải được định nghĩa trước khi sử dụng. Ở ví dụ 2.10 chúng ta sửa lại như sau thì chương trình sẽ bị báo lỗi:
|
||
A.CPP 1: #include <iostream.h>2: float Cube(float S);3: int main()4: {5: cout<<"Nhap vao chieu dai canh cua hinh lap phuong:";6: float Side;7: cin>>Side;8: cout<<"The tich cua hinh lap phuong = "<<Cube(Side);9: return 0;10: }11:12: inline float Cube(float S)13: {14: return S*S*S;15: }![]() ![]() |
||
|
||
|
Các hàm đệ quy không được là hàm inline.















































