Đa năng hóa toán tử trong Lập trình hướng đối tượng (Phần 2)
Các toán tử một ngôi được đa năng hóa trong hình 4.8 sau: Các toán tử một ngôi được đa năng hóa (Hình 4.8) Một toán tử một ngôi của lớp được đa năng hóa như một hàm thành viên không tĩnh ...
Các toán tử một ngôi được đa năng hóa trong hình 4.8 sau:

Một toán tử một ngôi của lớp được đa năng hóa như một hàm thành viên không tĩnh với không có tham số hoặc như một hàm không thành viên với một tham số; Tham số đó phải hoặc là một đối tượng lớp hoặc là một tham chiếu đến đối tượng lớp.
Ví dụ 4.4: Lấy lại ví dụ 4.3 và thêm toán tử dấu trừ một ngôi.
1: #include <iostream.h> 2: #include <math.h> 3: 4: class Complex 5: { 6: private: 7: double Real,Imaginary; 8: public: 9: Complex(); // Constructor mặc định 10: Complex(double R,double I); 11: Complex (const Complex & Z); // Constructor sao chép 12: Complex (double R); // Constructor chuyển đổi 13: void Print(); // Hiển thị số phức 14: // Các toán tử tính toán 15: friend Complex operator + (Complex Z1,Complex Z2); 16: friend Complex operator - (Complex Z1,Complex Z2); 17: Complex operator += (Complex Z); 18: Complex operator -= (Complex Z); 19: // Toán tử trừ một ngôi 20: Complex operator – (); 21: // Các toán tử so sánh 22: friend int operator == (Complex Z1,Complex Z2); 23: friend int operator != (Complex Z1,Complex Z2); 24: friend int operator > (Complex Z1,Complex Z2); 25: friend int operator >= (Complex Z1,Complex Z2); 26: friend int operator < (Complex Z1,Complex Z2); 27: friend int operator <= (Complex Z1,Complex Z2); 28: private: 29: double Abs(); // Giá trị tuyệt đối của số phức 30: }; 31: 32: Complex::Complex() 33: { 34: Real = 0.0; 35: Imaginary = 0.0; 36: } 37: 38: Complex::Complex(double R,double I) 39: { 40: Real = R; 41: Imaginary = I; 42: } 43: 44: Complex::Complex(const Complex & Z) 45: { 46: Real = Z.Real; 47: Imaginary = Z.Imaginary; 48: } 49: 50: Complex::Complex(double R) 51: { 52: Real = R; 53: Imaginary = 0.0; 54: } 55: 56: void Complex::Print() 57: { 58: cout<<'('<<Real<<','<<Imaginary<<')'; 59: } 60: 61: Complex operator + (Complex Z1,Complex Z2) 62: { 63: Complex Tmp; 64: 65: Tmp.Real = Z1.Real + Z2.Real; 66: Tmp.Imaginary = Z1.Imaginary + Z2.Imaginary; 67: return Tmp; 68: } 69: 70: Complex operator - (Complex Z1,Complex Z2) 71: { 72: Complex Tmp; 73: 74: Tmp.Real = Z1.Real - Z2.Real; 75: Tmp.Imaginary = Z1.Imaginary - Z2.Imaginary; 76: return Tmp; 77: } 78: 79: Complex Complex::operator += (Complex Z) 80: { 81: Real += Z.Real; 82: Imaginary += Z.Imaginary; 83: return *this; 84: } 85: 86: Complex Complex::operator -= (Complex Z) 87: { 88: Real -= Z.Real; 89: Imaginary -= Z.Imaginary; 90: return *this; 91: } 92: 93: Complex Complex::operator - () 94: { 95: Complex Tmp; 96: 97: Tmp.Real = -Real; 98: Tmp.Imaginary = -Imaginary; 99: return Tmp; 100: } 101 102: int operator == (Complex Z1,Complex Z2) 103: { 104: return (Z1.Real == Z2.Real) && (Z1.Imaginary == Z2.Imaginary); 105: } 106: 107: int operator != (Complex Z1,Complex Z2) 108: { 109: return (Z1.Real != Z2.Real) || (Z1.Imaginary != Z2.Imaginary); 110: } 111: 112: int operator > (Complex Z1,Complex Z2) 113: { 114: return Z1.Abs() > Z2.Abs(); 115: } 116: 117: int operator >= (Complex Z1,Complex Z2) 118: { 119: return Z1.Abs() >= Z2.Abs(); 120: } 121: 122: int operator < (Complex Z1,Complex Z2) 123: { 124: return Z1.Abs() < Z2.Abs(); 125: } 126: 127: int operator <= (Complex Z1,Complex Z2) 128: { 129: return Z1.Abs() <= Z2.Abs(); 130: } 131: 132: double Complex::Abs() 133: { 134: return sqrt(Real*Real+Imaginary*Imaginary); 135: } 136: 137: int main() 138: { 139: Complex X, Y(4.3,8.2), Z(3.3,1.1); 140: 141: cout<<"X: "; 142: X.Print(); 143: cout<<endl<<"Y: "; 144: Y.Print(); 145: cout<<endl<<"Z: "; 146: Z.Print(); 147: X = -Y + 3.6; 148: cout<<endl<<endl<<"X = -Y + 3.6: "; 149: X.Print(); 150: cout<<" = "; 151: (-Y).Print(); 152: cout<<" + 3.6 "; 153: X = -Y + -Z; 154: cout<<endl<<"X = -Y + -Z: "; 155: X.Print(); 156: cout<<" = "; 157: (-Y).Print(); 158: cout<<" + "; 159: (-Z).Print(); 160: return 0; 161: }
Chúng ta chạy ví dụ 4.4, kết quả ở hình 4.9

Trong phần này chúng ta sẽ tìm hiểu cách cài đặt một vài toán tử đặc biệt như () [] ++ -- , = ->
Toán tử [ ]
Khi cài đặt các lớp vector hoặc chuỗi ký tự, chúng ta cần phải truy cập đến từng phần tử của chúng, trong ngôn ngữ C/C++ đã có toán tử [] để truy cập đến một phần tử của mảng. Đây là toán tử hai ngôi, có dạng a[b] và khi đa năng toán tử này thì hàm toán tử tương ứng phải là thành viên của một lớp.
Ví dụ 4.5: Đa năng hóa toán tử [] để truy cập đến một phần tử của vector.
1: #include <iostream.h> 2: 3: class Vector 4: { 5: private: 6: int Size; 7: int *Data; 8: public: 9: Vector(int S=2,int V=0); 10: ~Vector(); 11: void Print() const; 12: int & operator [] (int I); 13: }; 14: 15: Vector::Vector(int S,int V) 16: { 17: Size = S; 18: Data=new int[Size]; 19: for(int I=0;I<Size;++I) 20: Data[I]=V; 21: } 22: 23: Vector::~Vector() 24: { 25: delete []Data; 26: } 27: void Vector::Print() const 28: { 29: cout<<"Vector:("; 30: for(int I=0;I<Size-1;++I) 31: cout<<Data[I]<<","; 32: cout<<Data[Size-1]<<")"<<endl; 33: } 34: 35: int & Vector::operator [](int I) 36: { 37: return Data[I]; 38: } 39: 40: int main() 41: { 42: Vector V(5,1); 43: V.Print(); 44: for(int I=0;I<5;++I) 45: V[I]*=(I+1); 46: V.Print(); 47: V[0]=10; 48: V.Print(); 49: return 0; 50: }
Chúng ta chạy ví dụ 4.5, kết quả ở hình 4.10
Kết quả của ví dụ 4.5 (Hình 4.10)
Trong chương trình ở ví dụ 4.5, hàm toán tử của toán tử [] ở lớp Vector trả về một tham chiếu vì toán tử này có thể dùng ở vế trái của phép gán.
Toán tử ()
Toán tử () được dùng để gọi hàm, toán tử này gồm hai toán hạng: toán hạng đầu tiên là tên hàm, toán hạng thứ hai là danh sách các tham số của hàm. Toán tử này có dạng giống như toán tử [] và khi đa năng toán tử này thì hàm toán tử tương ứng phải là thành viên của một lớp.
Ví dụ 4.6: Lấy lại ví dụ 4.5 nhưng đa năng hóa toán tử () để truy cập đến một phần tử của vector.
1: #include <iostream.h> 2: 3: class Vector 4: { 5: private: 6: int Size; 7: int *Data; 8: public: 9: Vector(int S=2,int V=0); 10: ~Vector(); 11: void Print() const; 12: int & operator () (int I); 13: }; 14: 15: Vector::Vector(int S,int V) 16: { 17: Size = S; 18: Data=new int[Size]; 19: for(int I=0;I<Size;++I) 20: Data[I]=V; 21: } 22: 23: Vector::~Vector() 24: { 25: delete []Data; 26: } 27: void Vector::Print() const 28: { 29: cout<<"Vector:("; 30: for(int I=0;I<Size-1;++I) 31: cout<<Data[I]<<","; 32: cout<<Data[Size-1]<<")"<<endl; 33: } 34: 35: int & Vector::operator ()(int I) 36: { 37: return Data[I]; 38: } 39: 40: int main() 41: { 42: Vector V(5,1); 43: V.Print(); 44: for(int I=0;I<5;++I) 45: V(I)*=(I+1); 46: V.Print(); 47: V(0)=10; 48: V.Print(); 49: return 0; 50: }
Chúng ta chạy ví dụ 4.6, kết quả ở hình 4.11

Ví dụ 4.7: Đa năng hóa toán tử () để truy cập đến phần tử của ma trận.
1: #include <iostream.h> 2: 3: class Matrix 4: { 5: private: 6: int Rows,Cols; 7: int **Data; 8: public: 9: Matrix(int R=2,int C=2,int V=0); 10: ~Matrix(); 11: void Print() const; 12: int & operator () (int R,int C); 13: }; 14: 15: Matrix::Matrix(int R,int C,int V) 16: { 17: int I,J; 18: Rows=R; 19: Cols=C; 20: Data = new int *[Rows]; 21: int *Temp=new int[Rows*Cols]; 22: for(I=0;I<Rows;++I) 23: { 24: Data[I]=Temp; 25: Temp+=Cols; 26: } 27: for(I=0;I<Rows;++I) 28: for(J=0;J<Cols;++J) 29: Data[I][J]=V; 30: } 31: 32: Matrix::~Matrix() 33: { 34: delete [] Data[0]; 35: delete [] Data; 36: } 37: 38: void Matrix::Print() const 39: { 40: int I,J; 41: for(I=0;I<Rows;++I) 42: { 43: for(J=0;J<Cols;++J) 44: { 45: cout.awidth(5); // Hiển thị canh lề phải với chiều dài 5 ký tự 46: cout<<Data[I][J]; 47: } 48: cout<<endl; 49: } 50: } 51: 52: int & Matrix::operator () (int R,int C) 53: { 54: return Data[R][C]; 55: } 56: 57: int main() 58: { 59: int I,J; 60: Matrix M(2,3,1); 61: cout<<"Matrix:"<<endl; 62: M.Print(); 63: for(I=0;I<2;++I) 64: for(J=0;J<3;++J) 65: M(I,J)*=(I+J+1); 66: cout<<"Matrix:"<<endl; 67: M.Print(); 68: return 0; 69: }
Chúng ta chạy ví dụ 4.7, kết quả ở hình 4.12
Kết quả của ví dụ 4.7 (Hình 4.12 )
Phần lớn các chương trình xử lý thông tin sự đa dạng của các kiểu. Đôi khi tất cả các thao tác "dừng lại bên trong một kiểu". Chẳng hạn, một số nguyên với một số nguyên tạo thành một số nguyên (miễn là kết quả không quá lớn để được biểu diễn như một số nguyên). Nhưng thật cần thiết để chuyển đổi dữ liệu của một kiểu tới dữ liệu của kiểu khác. Điều này có thể xảy ra trong các phép gán, các kết quả tính toán, trong việc chuyển các giá trị tới hàm, và trong việc trả về trị từ hàm. Trình biên dịch biết làm thế nào để thực hiện các chuyển đổi nào đó trong số các kiểu có sẵn. Các lập trình viên có thể ép buộc các chuyển đổi trong số các kiểu có sẵn bởi ép kiểu.
Nhưng đối với các kiểu do người dùng định nghĩa thì trình biên dịch không thể tự động biết làm thế nào chuyển đổi trong số các kiểu dữ liệu do người dùng định nghĩa và các kiểu có sẵn. Lập trình viên phải chỉ rõ làm sao các chuyển đổi như vậy sẽ xuất hiện. Các chuyển đổi như thế có thể được thực hiện với constructor chuyển đổi.
Một toán tử chuyển đổi kiểu có thể được sử dụng để chuyển đổi một đối tượng của một lớp thành đối tượng của một lớp khác hoặc thành một đối tượng của một kiểu có sẵn. Toán tử chuyển đổi kiểu như thế phải là hàm thành viên không tĩnh và không là hàm friend. Prototype của hàm thành viên này có cú pháp:
operator <data type> ();
Ví dụ 4.14: Toán tử chuyển đổi kiểu
1: #include <iostream.h> 2: 3: class Number 4: { 5: private: 6: float Data; 7: public: 8: Number(float F=0.0) 9: { 10: Data=F; 11: } 12: operator float() 13: { 14: return Data; 15: } 16: operator int() 17: { 18: return (int)Data; 19: } 20: }; 21: 22: int main() 23: { 24: Number N1(9.7), N2(2.6); 25: float X=(float)N1; //Gọi operator float() 26: cout<<X<<endl; 27: int Y=(int)N2; //Gọi operator int() 28: cout<<Y<<endl; 29: return 0; 30: }
Chúng ta chạy ví dụ 4.14, kết quả ở hình 4.19

Các toán tử new và delete toàn cục có thể được đa năng hóa. Điều này cho phép các lập trình viên C++ có khả năng xây dựng một hệ thống cấp phát bộ nhớ theo ý người dùng, cói cùng giao tiếp như hệ thống cấp phát mặc định.
Có hai cách đa năng hóa các toán tử new và delete:
- Có thể đa năng hóa một cách toàn cục nghĩa là thay thế hẳn các toán tử new và delete mặc định.
- Chúng ta đa năng hóa các toán tử new và delete với tư cách là hàm thành viên của lớp nếu muốn các toán tử new và delete áp dụng đối với lớp đó. Khi chúng ta dùng new và delete đối với lớp nào đó, trình biên dịch sẽ kiểm tra xem new và delete có được định nghĩa riêng cho lớp đó hay không; nếu không thì dùng new và delete toàn cục (có thể đã được đa năng hóa).
Hàm toán tử của toán tử new và delete có prototype như sau:
void * operator new(size_t size); void operator delete(void * ptr);
Trong đó tham số kiểu size_t được trình biên dịch hiểu là kích thước của kiểu dữ liệu được trao cho toán tử new.
Đa năng hóa toán tử new và delete toàn cục
Ví dụ 4.15: Đa năng hóa toán tử new và delete toàn cục đồng thời chứng tỏ rằng toán tử new và delete do đa năng hóa thay thế toán tử new và delete mặc định.
1: #include <iostream.h> 2: #include <stdlib.h> 3: 4: class Point 5: { 6: private: 7: int X, Y; 8: public: 9: Point(int A=0,int B=0) 10: { 11: X=A; 12: Y=B; 13: cout<<"Constructor!"<<endl; 14: } 15: ~Point() 16: { 17: cout<<"Destructor!"<<endl; 18: } 19: void Print() const 20: { 21: cout<<"X="<<X<<","<<"Y="<<Y<<endl; 22: } 23: }; 24: 25: void * operator new(size_t Size) 26: { 27: return malloc(Size); 28: } 29: 30: void operator delete(void *Ptr) 31: { 32: free(Ptr); 33: } 34: 35: int main() 36: { 37: Point *P1,*P2; 38: P1= new Point(10,20); 39: if (P1==NULL) 40: { 41: cout<<"Out of memory!"<<endl; 42: return 1; 43: } 44: P2= new Point(-10,-20); 45: if (P2==NULL) 46: { 47: cout<<"Out of memory!"<<endl; 48: return 1; 49: } 50: int *X=new int; 51: if (X==NULL) 52: { 53: cout<<"Out of memory!"<<endl; 54: return 1; 55: } 56: *X=10; 57: cout<<"X="<<*X<<endl; 58: cout<<"Point 1:"; 59: P1->Print(); 60: cout<<"Point 2:"; 61: P2->Print(); 62: delete P1; 63: delete P2; 64: delete X; 65: return 0; 66: }
Chúng ta chạy ví dụ 4.15, kết quả ở hình 4.20
Kết quả của ví dụ 4.15 (Hình 4.20)
Đa năng hóa toán tử new và delete cho một lớp
Nếu muốn toán tử new và delete có tính chất đặc biệt chỉ khi áp dụng cho đối tượng của lớp nào đó, chúng ta có thể đa năng hóa toán tử new và delete với tư cách là hàm thành viên của lớp đó. Việc này không khác lắm so với cách đa năng hóa toán tử new và delete một cách toàn cục.
Ví dụ 4.16: Đa năng hóa toán tử new và delete cho một lớp.
1: #include <iostream.h> 2: #include <stdlib.h> 3: class Number 4: { 5: private: 6: int Data; 7: public: 8: Number(int X=0) 9: { 10: Data=X; 11: } 12: 13: void * operator new(size_t Size) 14: { 15: cout<<"Toan tu new cua lop!"<<endl; 16: return ::new unsigned char[Size]; 17: } 18: 19: void operator delete(void *Ptr) 20: { 21: cout<<"Toan tu delete cua lop!"<<endl; 22: ::delete Ptr; 23: } 24: 25: void Print() const 26: { 27: cout<<"Data:"<<Data<<endl; 28: } 29: 30: }; 31: 32: int main() 33: { 34: Number *N; 35: N= new Number(10); 36: if (N==NULL) 37: { 38: cout<<"Out of memory!"<<endl; 39: return 1; 40: } 41: int *X=new int; 42: if (X==NULL) 43: { 44: cout<<"Out of memory!"<<endl; 45: return 1; 46: } 47: *X=10; 48: cout<<"X="<<*X<<endl; 49: N->Print(); 50: delete N; 51: delete X; 52: return 0; 53: }
Chúng ta chạy ví dụ 4.16, kết quả ở hình 4.21
