DẪN NHẬP
Trong chương 3, chúng ta đã tìm hiểu các điều cơ bản của các lớp C++ và khái niệm kiểu dữ liệu trừu tượng (ADTs). Các thao tác trên các đối tượng của lớp (nghĩa là các thực thể của ADTs) được thực hiện bởi gởi các thông điệp (dưới dạng các lời gọi hàm thành viên) tới các đối tượng. Ký pháp gọi hàm này thì cồng kềnh cho các loại lớp nhất định, đặc biệt là các lớp toán học. Đối với các loại lớp này sẽ là đẹp để sử dụng tập các toán tử có sẵn phong phú của C++ để chỉ rõ các thao tác của đối tượng. Trong chương này tìm hiểu làm thế nào cho phép các toán tử của C++ làm việc với các đối tượng của lớp. Xử lý này được gọi là đa năng hóa toán tử (operator overloading).
Toán tử << được sử dụng nhiều mục đích trong C++ đó là toán tử chèn dòng (stream-insertion) và toán tử dịch chuyển trái. Đây là một ví dụ của đa năng hóa toán tử. Tương tự >> cũng được đa năng hóa. Nó được sử dụng vừa toán tử trích dòng (stream-extraction) và toán tử dịch chuyển phải.
C++ cho phép các lập trình viên đa năng hóa hầu hết các toán tử để biểu thị ngữ cảnh mà trong đó chúng được sử dụng. Trình biên dịch phát sinh đoạn mã thích hợp dựa trên kiểu mà trong đó toán tử được sử dụng. Một vài toán tử được đa năng hóa thường xuyên, đặc biệt là toán tử gán và các toán tử số học như + và -. Công việc thực hiện bởi đa năng hóa các toán tử cũng có thể được thực hiện bởi các lời gọi hàm tường minh, nhưng ký pháp thường sử dụng dễ dàng để đọc.
CÁC NGUYÊN TẮC CƠ BẢN CỦA ĐA NĂNG HÓA TOÁN TỬ
Lập trình viên có thể sử dụng các kiểu có sẵn và có thể định nghĩa các kiểu mới. Các kiểu có thể được sử dụng với tập các toán tử phong phú. Các toán tử cung cấp cho các lập trình viên với ký pháp ngắn ngọn cho việc biểu thị các thao tác của đối tượng của các kiểu có sẵn.
Các lập trình viên có thể sử dụng các toán tử với các kiểu do người dùng định nghĩa. Mặc dù C++ không cho phép các toán tử mới được tạo, nó cho phép các toán tử đã tồn tại được đa năng hóa sao cho khi các toán tử này được sử dụng với các đối tượng của lớp, các toán tử có ý nghĩa thích hợp các kiểu mới. Đây chính là một đặc điểm mạnh của C++.
Các toán tử được đa năng hóa bằng cách viết một định nghĩa hàm (bao gồm phần đầu và thân) như khi chúng ta viết một hàm bình thường, ngoại trừ tên hàm bây giờ trở thành từ khóa operator theo sau bởi ký hiệu của toán tử được đa năng hóa. Prototype của nó có dạng như sau:
type operator operator_symbol ( parameter_list );
Để sử dụng một toán tử một các đối tượng của lớp, toán tử phải được đa năng hóa ngoại trừ hai điều. Điều thứ nhất toán tử gán có thể sử dụng với mọi lớp mà không cần đa năng hóa. Cách cư xử mặc định của toán tử gán là một phép gán thành viên của các thành viên dữ liệu của lớp. Chúng ta nhận thấy rằng sao chép thành viên mặc định thì nguy hiểm đối với các lớp với các thành viên mà được cấp phát động. Chúng ta sẽ đa năng hóa một cách tường minh toán tử gán đối với các lớp như thế. Điều thứ hai toán tử địa chỉ (&) cũng có thể được sử dụng với các đối tượng của bất kỳ lớp nào mà không cần đa năng hóa; Nó trả về địa chỉ của đối tượng trong bộ nhớ. Toán tử địa chỉ cũng có thể được đa năng hóa.
CÁC GIỚI HẠN CỦA ĐA NĂNG HÓA TOÁN TỬ
Phần lớn các toán tử của C++ có thể được đa năng hóa. Hình 4.1 cho thấy các toán tử có thể được đa năng hóa và hình 4.1 là các toán tử không thể đa năng hóa.
| + |
- |
* |
/ |
% |
^ |
& |
| |
| ~ |
! |
= |
< |
> |
+= |
-= |
*= |
| /= |
%= |
^= |
&= |
|= |
<< |
>> |
>>= |
| <<= |
== |
!= |
<= |
>= |
&& |
|| |
++ |
| -- |
->* |
, |
-> |
[] |
() |
new |
delete |
Hình 4.1:
Các toán tử có thể được đa năng hóa
Hình 4.2: Các toán tử không thể đa năng hóa
Chú ý rằng toán tử ngoặc tròn () trong bảng 4.1 là toán tử gọi hàm. Vì toán tử này đứng sau tên hàm có thể chứa trong nó nhiều tham số do đó toán tử ngoặc tròn là một toán tử nhiều ngôi.
Thứ tự ưu tiên của một toán tử không thể được thay đổi bởi đa năng hóa. Điều này có thể dẫn tới các tình trạng bất tiện trong đó một toán tử được đa năng hóa theo một cách đối với mức độ ưu tiên cố định của nó thì không thích hợp. Tuy nhiên, các dấu ngoặc đơn có thể được sử dụng để đặt thứ tự ước lượng của các toán tử đã đa năng hóa trong một biểu thức.
Tính kết hợp của một toán tử không thể được thay đổi bởi đa năng hóa. Các tham số mặc định không thể sử dụng với một toán tử đa năng hóa.
Không thể thay đổi số các toán hạng mà một toán tử yêu cầu: Đa năng hóa các toán tử một ngôi vẫn là các toán tử một ngôi; đa năng hóa các toán tử hai ngôi vẫn là các toán tử hai ngôi. Toán tử ba ngôi duy nhất (?:) của C++ không thể đa năng hóa. Các toán tử &, *, + và – mỗi toán tử có các phiên bản một và hai ngôi.; Các phiên bản một và hai ngôi này có thể được đa năng hóa riêng biệt.
Ý nghĩa của làm sao một toán tử làm việc trên các đối tượng của các kiểu có sẵn không thể thay đổi bởi việc đa năng hóa toán tử. Chẳng hạn, lập trình viên không thể thay đổi ý nghĩa của làm sao toán tử (+) cộng hai số nguyên. Việc đa năng hóa toán tử chỉ làm việc với các đối tượng của các kiểu do người dùng định nghĩa hoặc với một sự pha trộn của một đối tượng của kiểu do người dùng định nghĩa và một đối tượng của một kiểu có sẵn.
Đa năng hóa một toán tử gán và một toán tử cộng để cho phép các lệnh như là:
object2 = object2 + object1
không bao hàm toán tử += cũng được đa năng hóa để phép các lệnh như là:
object2 += object1
Hành vi như thế có thể được thực hiện bởi việc đa năng hóa rõ ràng toán tử += cho lớp đó.
CÁC HÀM TOÁN TỬ CÓ THỂ LÀ CÁC THÀNH VIÊN CỦA LỚP HOẶC KHÔNG LÀ CÁC THÀNH VIÊN
Các hàm toán tử có thể là các hàm thành viên hoặc hàm không thành viên; hàm không thành viên thường là các hàm friend. Các hàm thành viên sử dụng ngầm con trỏ this để chứa một trong các tham số đối tượng lớp của chúng. Tham số lớp đó phải được liệt kê một cách tường minh trong lời gọi hàm không thành viên.
Khi đa năng hóa (), [], -> hoặc =, hàm đa năng hóa toán tử phải được khai báo như một thành viên lớp. Đối với các toán tử khác, các hàm đa năng hóa toán tử có thể là các hàm không thành viên (thường là các hàm friend).
Liệu có phải một hàm toán tử được cài đặt như một hàm thành viên hoặc như hàm không thành viên, toán tử vẫn còn được sử dụng cùng cách trong biểu thức. Như vậy cách là cách cài đặt nào tốt nhất?
Khi một hàm toán tử được cài đặt như một hàm thành viên, toán hạng cực trái phải là một đối tượng lớp của toán tử. Nếu toán hạng bên trái phải là một đối tượng của lớp khác hoặc một kiểu có sẵn thì hàm toán tử này phải được cài đặt như hàm không thành viên. Một hàm toán tử cài đặt như hàm không thành viêân cần là một friend nếu hàm phải truy cập đến các thành viên private hoặc protected.
Các hàm thành viên chỉ được gọi khi toán hạng trái của một toán tử hai ngôi là một đối tượng cụ thể của lớp đó, hoặc khi toán hạng đơn của một toán tử một ngôi là một đối tượng của lớp đó.
Ví dụ 4.1: Chúng ta xây dựng lớp số phức với tên lớp là Complex và đa năng hóa toán tử + trên lớp này.
1: #include <iostream.h>
2:
3: class Complex
4: {
5: private:
6: double Real, Imaginary;
7: public:
8: Complex(double R=0.0,double I=0.0);// Constructor mặc định
9: void Print(); // Hiển thị số phức
10: Complex operator+(Complex Z); // Phép cộng giữa hai số phức
11: Complex operator+(double R); //cộng một số phức với một số thực
12: };
13:
14: Complex::Complex(double R,double I)
15: {
16: Real = R;
17: Imaginary = I;
18: }
19:
20: void Complex::Print()
21: {
22: cout<<'('<<Real<<','<<Imaginary<<')';
23: }
24:
25: Complex Complex::operator + (Complex Z)
26: {
27: Complex Tmp;
28: Tmp.Real = Real + Z.Real;
29: Tmp.Imaginary = Imaginary + Z.Imaginary;
30: return Tmp;
31: }
32:
33: Complex Complex::operator + (double R)
34: {
35: Complex Tmp;
36: Tmp.Real = Real + R;
37: Tmp.Imaginary = Imaginary;
38: return Tmp;
39: }
40:
41: int main()
42: {
43: Complex X,Y(4.3,8.2),Z(3.3,1.1);
44: cout<<"X: ";
45: X.Print();
46: cout<<endl<<"Y: ";
47: Y.Print();
48: cout<<endl<<"Z: ";
49: Z.Print();
50: X = Y + Z;
51: cout<<endl<<endl<<"X = Y + Z:"<<endl;
52: X.Print();
53: cout<<" = ";
54: Y.Print();
55: cout<<" + ";
56: Z.Print();
57: X = Y + 3.5;
58: cout<<endl<<endl<<"X = Y + 3.5:"<<endl;
59: X.Print();
60: cout<<" = ";
61: Y.Print();
62: cout<<" + 3.5";
63: return 0;
64: }
Hàm thành viên toán tử operator + () (từ dòng 25 đến 31 và từ dòng 33 đến 39) trả về một đối tượng có kiểu Complex là tổng của hai số phức hoặc tổng của một số phức với một số thực. Chú ý rằng đối tượng tam thời Tmp được dùng bên trong hàm operator + () để giữ kết quả, và đó là đối tượng được trả về.
Hình 4.3: Kết quả của ví dụ 4.1
Do đa năng hóa toán tử + trên lớp Complex ở ví dụ 4.1, chúng ta có thể viết:
X = Y + Z;
Câu lệnh này được trình biên dịch hiểu:
X = Y.operator + (Z);
Như vậy, trong biểu thức Y + Z đối tượng bên trái toán tử + (là đối tượng Y) là đối tượng mà qua đó, hàm thành viên toán tử operator + () được gọi. Do đó hàm thành viên toán tử + chỉ nhận một tham số là đối tượng bên phải toán tử và đối tượng bên trái toán tử là đối tượng tạo lời gọi cho hàm toán tử và được truyền bởi con trỏ this.
Hàm operator + () trả về một đối tượng Complex. Do vậy chúng ta có thể viết:
(Y + Z).Print();
để in trên màn hình số phức của đối tượng được trả về. Đối tượng do Y + Z sinh ra như vậy là một đối tượng tạm thời. Nó sẽ không tồn tại khi hàm thành Print() kết thúc.
Hơn nữa khi trả về một đối tượng, toán tử + cho phép một chuỗi phép cộng. Nên chúng ta cũng có thể viết:
X = X + Y + Z;
Tuy nhiên chúng ta không thể nào viết được câu lệnh sau:
X = 3.5 + Y; // Lỗi !!!
Chính vì lý do này chúng ta chọn một hàm không thành viên để đa năng hóa một toán tử để cho phép toán tử được giao hoán. Chú ý rằng hàm không thành viên không cần thiết phải là hàm friend nếu các hàm set và get thích hợp tồn tại trong phần giao diện public, và đặt biệt nhất nếu các hàm set và get là các hàm inline.
Để đa năng hóa toán tử << phải có một toán hạng trái của kiểu ostream & (như là cout trong biểu thức cout<<X), vì thế nó phải là hàm không thành viên. Tương tự, đa năng hóa toán tử >> phải có một toán hạng trái của kiểu istream & (như là cin trong biểu thức cin>>X), vì thế vì thế nó cũng phải là hàm không thành viên.
Ngoại trừ đa năng hóa toán tử >> và << liên quan đến dòng nhập/xuất dữ liệu chúng ta có hình 4.4 về cách đa năng hóa toán tử như sau:
| Biểu thức |
Hàm thành viên |
Hàm không thành viên |
| a#b |
a.operator#(b) |
operator#(a,b) |
| #a |
a.operator() |
operator#(a) |
| a=b |
a.operator=(b) |
|
| a[b] |
a.operator[](b) |
|
| a(b) |
a.operator()(b) |
|
| a-> |
a.operator->() |
|
| a++ |
a.operator++(0) |
operator++(a,0) |
| a-- |
a.operator--(0) |
operator--(a,0) |
Hình 4.4: Việc cài đặt các hàm toán tử
ĐA NĂNG HOÁ CÁC TOÁN TỬ HAI NGÔI
Các toán tử hai ngôi được đa năng hóa trong hình 4.5 sau:
| Toán tử |
Ví dụ |
Toán tử |
Ví dụ |
Toán tử |
Ví dụ |
| + |
a+b |
+= |
a+=b |
<<= |
a<<=b |
| - |
a-b |
-= |
a-=b |
== |
a==b |
| * |
a*b |
*= |
a*=b |
!= |
a!=b |
| / |
a/b |
/= |
a/=b |
<= |
a<=b |
| % |
a%b |
%= |
a%=b |
>= |
a>=b |
| ^ |
a^b |
^= |
a^=b |
&& |
a&&b |
| & |
a&b |
&= |
a&=b |
|| |
a||b |
| | |
a|b |
|= |
a|=b |
, |
a,b |
| = |
a=b |
<< |
a<<b |
[] |
a[b] |
| < |
a<b |
>> |
a>>b |
->* |
a->*b |
| > |
a>b |
>>= |
a>>=b |
|
Hình 4.5: Các toán tử hai ngôi được đa năng hóa
Một toán tử hai ngôi có thể được đa năng hóa như là hàm thành viên không tĩnh với một tham số hoặc như một hàm không thành viên với hai tham số (một trong các tham số này phải là 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.2: Chúng ta xây dựng lớp số phức với tên lớp là Complex và đa năng hóa các toán tử tính toán + - += -= và các toán tử so sánh == != > >= < <= với các hàm toán tử là các hàm thành viên.
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: Complex operator + (Complex Z);
16: Complex operator - (Complex Z);
17: Complex operator += (Complex Z);
18: Complex operator -= (Complex Z);
19: // Các toán tử so sánh
20: int operator == (Complex Z);
21: int operator != (Complex Z);
22: int operator > (Complex Z);
23: int operator >= (Complex Z);
24: int operator < (Complex Z);
25: int operator <= (Complex Z);
26: private:
27: double Abs(); // Giá trị tuyệt đối của số phức
28: };
29:
30: Complex::Complex()
31: {
32: Real = 0.0;
33: Imaginary = 0.0;
34: }
35:
36: Complex::Complex(double R,double I)
37: {
38: Real = R;
39: Imaginary = I;
40: }
41:
42: Complex::Complex(const Complex & Z)
43: {
44: Real = Z.Real;
45: Imaginary = Z.Imaginary;
46: }
47:
48: Complex::Complex(double R)
49: {
50: Real = R;
51: Imaginary = 0.0;
52: }
53:
54: void Complex::Print()
55: {
56: cout<<'('<<Real<<','<<Imaginary<<')';
57: }
58:
59: Complex Complex::operator + (Complex Z)
60: {
61: Complex Tmp;
62
63: Tmp.Real = Real + Z.Real;
64: Tmp.Imaginary = Imaginary + Z.Imaginary;
65: return Tmp;
66: }
67:
68: Complex Complex::operator - (Complex Z)
69: {
70: Complex Tmp;
71:
72: Tmp.Real = Real - Z.Real;
73: Tmp.Imaginary = Imaginary - Z.Imaginary;
74: return Tmp;
75: }
76:
77: Complex Complex::operator += (Complex Z)
78: {
79: Real += Z.Real;
80: Imaginary += Z.Imaginary;
81: return *this;
82: }
83:
84: Complex Complex::operator -= (Complex Z)
85: {
86: Real -= Z.Real;
87: Imaginary -= Z.Imaginary;
88: return *this;
89: }
90:
91: int Complex::operator == (Complex Z)
92: {
93: return (Real == Z.Real) && (Imaginary == Z.Imaginary);
94: }
95:
96: int Complex::operator != (Complex Z)
97: {
98: return (Real != Z.Real) || (Imaginary != Z.Imaginary);
99: }
100:
101: int Complex::operator > (Complex Z)
102: {
103: return Abs() > Z.Abs();
104: }
105:
106: int Complex::operator >= (Complex Z)
107: {
108: return Abs() >= Z.Abs();
109: }
110:
111: int Complex::operator < (Complex Z)
112: {
113: return Abs() < Z.Abs();
114: }
115:
116: int Complex::operator <= (Complex Z)
117: {
118: return Abs() <= Z.Abs();
119: }
120:
121: double Complex::Abs()
122: {
123: return sqrt(Real*Real+Imaginary*Imaginary);
124: }
125:
126: int main()
127: {
128: Complex X, Y(4.3,8.2), Z(3.3,1.1), T;
129
130: cout<<"X: ";
131: X.Print();
132: cout<<endl<<"Y: ";
133: Y.Print();
134: cout<<endl<<"Z: ";
135: Z.Print();
136: cout<<endl<<"T: ";
137: T.Print();
138: T=5.3;// Gọi constructor chuyển kiểu
139: cout<<endl<<endl<<"T = 5.3"<<endl;
140: cout<<"T: ";
141: T.Print();
142: X = Y + Z;
143: cout<<endl<<endl<<"X = Y + Z: ";
144: X.Print();
145: cout<<" = ";
146: Y.Print();
147: cout<<" + ";
148: Z.Print();
149: X = Y - Z;
150: cout<<endl<<"X = Y - Z: ";
151: X.Print();
152: cout<<" = ";
153: Y.Print();
154: cout<<" - ";
155: Z.Print();
156: cout<<endl<<endl<<"Y += T i.e ";
157: Y.Print();
158: cout<<" += ";
159: T.Print();
160: Y += T;
161: cout<<endl<<"Y: ";
162: Y.Print();
163: cout<<endl<<"Z -= T i.e ";
164: Z.Print();
165: cout<<" -= ";
166: T.Print();
167: Z -= T;
168: cout<<endl<<"Z: ";
169: Z.Print();
170: Complex U(X);// Gọi constructor sao chép
171: cout<<endl<<endl<<"U: ";
172: U.Print();
173: cout<<endl<<endl<<"Evaluating: X==U"<<endl;
174: if (X==U)
175: cout<<"They are equal"<<endl;
176: cout<<"Evaluating: Y!=Z"<<endl;
177: if (Y!=Z)
178: cout<<"They are not equal => ";
179: if (Y>Z)
180: cout<<"Y>Z";
181: else
182: cout<<"Y<Z";
183: return 0;
184: }
Dòng thứ 10 của chương trình ở ví dụ 4.2: Complex(const Complex &Z);
là một constructor sao chép (copy constructor). Nó khởi động một đối tượng lớp bằng cách tạo một sao chép của một đối tượng lớp đó. Constructor sao chép thực hiện công việc giống như toán tử sao chép nhưng nó có một vai trò đặc biệt. Constructor sao chép chỉ nhận tham số là một tham chiếu chỉ đến đối tượng thuộc chính lớp mà nó được định nghĩa. Các constructor sao chép được dùng mỗi khi một sự sao chép của một đối tượng cần thiết như khi có sự truyền tham số bằng trị, khi trả về một đối tượng từ hàm, hoặc khi khởi động một đối tượng mà được sao chép từ đối tượng khác của cùng lớp. Chẳng hạn:
Complex A(3.5, 4.5);
Complex B(A); // Gọi constructor sao chép
Complex C = B; // Gọi constructor sao chép
…………………
Complex MyFunc(Complex Z) // Gọi constructor sao chép
{ rZ; // Gọi constructor sao chép }
Hình 4.6: Kết quả của ví dụ 4.2
Chúng ta chú ý rằng, dấu = trong câu lệnh trên ứng với constructor sao chép chứ không phải là toán tử gán . Nếu chúng ta không định nghĩa constructor sao chép, trình biên dịch tạo ra một constructor sao chép mặc định sẽ sao chép từng thành viên một.
Ở dòng 12 của chương trình ở ví dụ 4.2:
Complex(double R);
là một constructor chuyển đổi (conversion constructor). Constructor này lấy một tham số double và khởi tạo đối tượng Complex mà phần thực bằng giá trị tham số truyền vào và phần ảo bằng 0.0 (từ dòng 48 đến 52). Bất kỳ một constructor nào có tham số đơn có thể được nghĩ như một constructor chuyển đổi. Constructor chuyển đổi sẽ đổi một số thực thành một đối tượng Complex rồi gán cho đối tượng đích Complex. Chẳng hạn:
T = 3.5; // Ngầm định: T = Complex(3.5)
Trình biên dịch tự động dùng constructor chuyển đổi để tạo một đối tượng tạm thời Complex, rồi dùng toán tử gán để gán đối tượng tạm thời này cho đối tượng khác của Complex. Chẳng hạn câu lệnh sau vẫn đúng:
X = Y + 3.5; // Ngầm định: X = Y + Complex(3.5);
Như vậy một constructor chuyển đổi được sử dụng để thực hiện một sự chuyển đổi ngầm định.
Ví dụ 4.3: Lấy lại ví dụ 4.2 nhưng các hàm toán tử +, - và các hàm toán tử so sánh là hàm không thành viên.
#include <iostream.h>
#include <math.h>
class Complex
{
private:
double Real,Imaginary;
public:
Complex();//Constructor mac dinh
Complex(double R,double I);
Complex (const Complex & Z);//Constructor sao chep
Complex (double R);//Constructor chuyen doi
void Print();//Hien thi so phuc
//Cac toan tu tinh toan
friend Complex operator + (Complex Z1,Complex Z2);
friend Complex operator - (Complex Z1,Complex Z2);
Complex operator += (Complex Z);
Complex operator -= (Complex Z);
//Cac toan tu so sanh
friend int operator == (Complex Z1,Complex Z2);
friend int operator != (Complex Z1,Complex Z2);
friend int operator > (Complex Z1,Complex Z2);
friend int operator >= (Complex Z1,Complex Z2);
friend int operator < (Complex Z1,Complex Z2);
friend int operator <= (Complex Z1,Complex Z2);
private:
double Abs();//Gia tri tuyet doi cua so phuc
};
Complex::Complex()
{
Real = 0.0;
Imaginary = 0.0;
}
Complex::Complex(double R,double I)
{
Real = R;
Imaginary = I;
}
Complex::Complex(const Complex & Z)
{
Real = Z.Real;
Imaginary = Z.Imaginary;
}
Complex::Complex(double R)
{
Real = R;
Imaginary = 0.0;
}
void Complex::Print()
{
cout<<'('<<Real<<','<<Imaginary<<')';
}
Complex operator + (Complex Z1,Complex Z2)
{
Complex Tmp;
Tmp.Real = Z1.Real + Z2.Real;
Tmp.Imaginary = Z1.Imaginary + Z2.Imaginary;
return Tmp;
}
Complex operator - (Complex Z1,Complex Z2)
{
Complex Tmp;
Tmp.Real = Z1.Real - Z2.Real;
Tmp.Imaginary = Z1.Imaginary - Z2.Imaginary;
return Tmp;
}
Complex Complex::operator += (Complex Z)
{
Real += Z.Real;
Imaginary += Z.Imaginary;
return *this;
}
Complex Complex::operator -= (Complex Z)
{
Real -= Z.Real;
Imaginary -= Z.Imaginary;
return *this;
}
int operator == (Complex Z1,Complex Z2)
{
return (Z1.Real == Z2.Real) && (Z1.Imaginary == Z2.Imaginary);
}
int operator != (Complex Z1,Complex Z2)
{
return (Z1.Real != Z2.Real) || (Z1.Imaginary != Z2.Imaginary);
}
int operator > (Complex Z1,Complex Z2)
{
return Z1.Abs() > Z2.Abs();
}
int operator >= (Complex Z1,Complex Z2)
{
return Z1.Abs() >= Z2.Abs();
}
int operator < (Complex Z1,Complex Z2)
{
return Z1.Abs() < Z2.Abs();
}
int operator <= (Complex Z1,Complex Z2)
{
return Z1.Abs() <= Z2.Abs();
}
double Complex::Abs()
{
return sqrt(Real*Real+Imaginary*Imaginary);
}
int main()
{
Complex X,Y(4.3,8.2),Z(3.3,1.1);
cout<<"X: "; X.Print();
cout<<endl<<"Y: "; Y.Print();
cout<<endl<<"Z: "; Z.Print();
X = Y + 3.6;
cout<<endl<<endl<<"X = Y + 3.6: ";
X.Print(); cout<<" = ";
Y.Print(); cout<<" + 3.6 ";
X = 3.6 + Y;cout<<endl<<"X = 3.6 + Y: ";
X.Print(); cout<<" = 3.6 + ";
Y.Print(); X = 3.8 - Z;
cout<<endl<<"X = 3.8 - Z: ";
X.Print(); cout<<" = 3.8 - ";
Z.Print(); X = Z - 3.8;
cout<<endl<<"X = Z - 3.8: ";
X.Print(); cout<<" = ";
Z.Print(); cout<<" - 3.8 ";
return 0;
}
Hình 4.7: Kết quả của ví dụ 4.3
ĐA NĂNG HÓA CÁC TOÁN TỬ MỘT NGÔI
Các toán tử một ngôi được đa năng hóa trong hình 4.8 sau:
| Toán tử |
Ví dụ |
Toán tử |
Ví dụ |
| + |
+c |
~ |
~c |
| - |
-c |
! |
!a |
| * |
*c |
++ |
++c, c++ |
| & |
&c |
-- |
--c, c-- |
| -> |
c-> |
|
|
Hình 4.8: Các toán tử một ngôi được đa năng hóa
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: }
Hình 4.9: Kết quả của ví dụ 4.4
ĐA NĂNG HÓA MỘT SỐ TOÁN TỬ ĐẶC BIỆT
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: }
Hình 4.10: Kết quả của ví dụ 4.5
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: }
Hình 4.11: Kết quả của ví dụ 4.6
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.width(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: }
Hình 4.12: Kết quả của ví dụ 4.7
TOÁN TỬ CHUYỂN ĐỔI KIỂU
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> ();
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: }
Hình 4.19: Kết quả của ví dụ 4.14
TOÁN TỬ NEW VÀ DELETE
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: }
Hình 4.20: Kết quả của ví dụ 4.15
Đ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: }
Hình 4.21: Kết quả của ví dụ 4.16
ĐA NĂNG HÓA CÁC TOÁN TỬ CHÈN DÒNG << VÀ TRÍCH DÒNG >>
Chúng ta có thể đa năng hóa các toán tử chèn dòng << (stream insertion) và trích dòng >> (stream extraction). Hàm toán tử của toán tử << được đa năng hóa có prototype như sau:
ostream & operator << (ostream & stream, ClassName Object);
Hàm toán tử << trả về tham chiếu chỉ đến dòng xuất ostream. Tham số thứ nhất của hàm toán tử << là một tham chiếu chỉ đến dòng xuất ostream, tham số thứ hai là đối tượng được chèn vào dòng. Khi sử dụng, dòng trao cho toán tử << (tham số thứ nhất) là toán hạng bên trái và đối tượng được đưa vào dòng (tham số thứ hai) là toán hạng bên phải. Để bảo đảm cách dùng toán tử << luôn nhất quán, chúng ta không thể định nghĩa hàm toán tử << như là hàm thành viên của lớp đang xét, thông thường nó chính là hàm friend.
Còn hàm toán tử của toán tử >> được đa năng hóa có prototype như sau:
istream & operator >> (istream & stream, ClassName Object);
Hàm toán tử >> trả về tham chiếu chỉ đến dòng nhập istream. Tham số thứ nhất của hàm toán tử này là một tham chiếu chỉ đến dòng nhập istream, tham số thứ hai là đối tượng của lớp đang xét mà chúng ta muốn tạo dựng nhờ vào dữ liệu lấy từ dòng nhập. Khi sử dụng, dòng nhập đóng vai toán hạng bên trái, đối tượng nhận dữ liệu đóng vai toán hạng bên phải. Cũng như trường hợp toán tử <<, hàm toán tử >> không là hàm thành viên của lớp, thông thường nó chính là hàm friend.
1: #include <iostream.h>
2:
3: class Point
4: {
5: private:
6: int X,Y;
7: public:
8: Point();
9: friend ostream & operator << (ostream & Out,Point & P);
10: friend istream & operator >> (istream & In,Point & P);
11: };
12:
13: Point::Point()
14: {
15: X=Y=0;
16: }
17:
18: ostream & operator << (ostream & Out,Point & P)
19: {
20: Out<<"X="<<P.X<<",Y="<<P.Y<<endl;
21: return Out; //Cho phép cout<<a<<b<<c;
22: }
23:
24: istream & operator >> (istream &In,Point & P)
25: {
26: cout<<"X:";
27: In>>P.X;
28: cout<<"Y:";
29: In>>P.Y;
30: return In; //Cho phép cin>>a>>b>>c;
31: }
32:
33: int main()
34: {
35: Point P;
36: cin>>P;
37: cout<<"Point:"<<P;
38: return 0;
39: }
Hình 4.22: Kết quả của ví dụ 4.17
MỘT SỐ VÍ DỤ
Lớp String
Ví dụ 4.18: Chúng ta sẽ xây dựng một lớp xử lý việc tạo và thao tác trên các chuỗi (string). C++ không cài sẵn kiểu dữ liệu chuỗi. Nhưng C++ cho phép chúng ta thêm kiểu chuỗi như một lớp thông qua cơ chế đa năng hóa.
#include <iostream.h>
#include <iomanip.h>
#include <string.h>
#include <assert.h>
class String
{
private:
char *Ptr; //Con tro tro den diem bat dau cua chuoi
int Length; //Chieu dai chuoi
public:
String(const char * = ""); //Constructor chuyen doi
String(const String &); //Constructor sao chep
~String(); //Destructor
const String &operator=(const String &); //Phep gan
String &operator+=(const String &); //Phep noi
int operator!() const; //Kiem tra chuoi rong
int operator==(const String &) const;
int operator!=(const String &) const;
int operator<(const String &) const;
int operator>(const String &) const;
int operator>=(const String &) const;
int operator<=(const String &) const;
char & operator[](int); //Tra ve ky tu tham chieu
String &operator()(int, int); //Tra ve mot chuoi con
int GetLength() const;
friend ostream &operator<<(ostream &, const String &);
friend istream &operator>>(istream &, String &);
};
//Constructor sao chep: Chuyen doi char * thanh String
String::String(const char *S)
{
cout << "Conversion constructor: " << S << endl;
Length = strlen(S);
Ptr = new char[Length + 1];
assert(Ptr != 0);
strcpy(Ptr, S);
}
String::String(const String &Copy)
{
cout << "Copy constructor: " << Copy.Ptr << endl;
Length = Copy.Length;
Ptr = new char[Length + 1];
assert(Ptr != 0);
strcpy(Ptr, Copy.Ptr);
}
//Destructor
String::~String()
{
cout << "Destructor: " << Ptr << endl;
delete [] Ptr;
}
const String &String::operator=(const String &Right)
{
cout << "operator= called" << endl;
if (&Right != this)
{
delete [] Ptr;
Length = Right.Length;
Ptr = new char[Length + 1];
assert(Ptr != 0);
strcpy(Ptr, Right.Ptr);
}
else
cout << "Attempted assignment of a String to itself" << endl;
return *this;
}
String &String::operator+=(const String &Right)
{
char *TempPtr = Ptr;
Length += Right.Length;
Ptr = new char[Length + 1];
assert(Ptr != 0);
strcpy(Ptr, TempPtr);
strcat(Ptr, Right.Ptr);
delete [] TempPtr;
return *this;
}
int String::operator!() const
{
return Length == 0;
}
int String::operator==(const String &Right) const
{
return strcmp(Ptr, Right.Ptr) == 0;
}
int String::operator!=(const String &Right) const
{
return strcmp(Ptr, Right.Ptr) != 0;
}
int String::operator<(const String &Right) const
{
return strcmp(Ptr, Right.Ptr) < 0;
}
int String::operator>(const String &Right) const
{
return strcmp(Ptr, Right.Ptr) > 0;
}
int String::operator>=(const String &Right) const
{
return strcmp(Ptr, Right.Ptr) >= 0;
}
int String::operator<=(const String &Right) const
{
return strcmp(Ptr, Right.Ptr) <= 0;
}
char &String::operator[](int Subscript)
{
assert(Subscript >= 0 && Subscript < Length);
return Ptr[Subscript];
}
String &String::operator()(int Index, int SubLength)
{
assert(Index >= 0 && Index < Length && SubLength >= 0);
String *SubPtr = new String;
assert(SubPtr != 0);
if ((SubLength == 0) || (Index + SubLength > Length))
SubPtr->Length = Length - Index + 1;
else
SubPtr->Length = SubLength + 1;
delete SubPtr->Ptr;
SubPtr->Ptr = new char[SubPtr->Length];
assert(SubPtr->Ptr != 0);
strncpy(SubPtr->Ptr, &Ptr[Index], SubPtr->Length);
SubPtr->Ptr[SubPtr->Length] = '\0';
return *SubPtr;
}
int String::GetLength() const
{return Length;
}
ostream &operator<<(ostream &Output, const String &S)
{
Output << S.Ptr;
return Output;
}
istream &operator>>(istream &Input, String &S)
{
char Temp[100];
Input >> setw(100) >> Temp;
S = Temp;
return Input;
}
int main()
{
String S1("happy"), S2(" birthday"), S3;
cout << "S1 is \"" << S1 << "\"; S2 is \"" << S2
<< "\"; S3 is \"" << S3 << '\"' << endl
<< "The results of comparing S2 and S1:" << endl
<< "S2 == S1 yields " << (S2 == S1) << endl
<< "S2 != S1 yields " << (S2 != S1) << endl
<< "S2 > S1 yields " << (S2 > S1) << endl
<< "S2 < S1 yields " << (S2 < S1) << endl
<< "S2 >= S1 yields " << (S2 >= S1) << endl
<< "S2 <= S1 yields " << (S2 <= S1) << endl;
cout << "Testing !S3:" << endl;
if (!S3)
{
cout << "S3 is empty; assigning S1 to S3;" << endl;
S3 = S1;
cout << "S3 is \"" << S3 << "\"" << endl;
}
cout << "S1 += S2 yields S1 = ";
S1 += S2;
cout << S1 << endl;
cout << "S1 += \" to you\" yields" << endl;
S1 += " to you";
cout << "S1 = " << S1 << endl;
cout << "The substring of S1 starting at" << endl
<< "location 0 for 14 characters, S1(0, 14), is: "
<< S1(0, 14) << endl;
cout << "The substring of S1 starting at" << endl
<< "location 15, S1(15, 0), is: "
<< S1(15, 0) <<endl; // 0 is "to end of string"
String *S4Ptr = new String(S1);
cout << "*S4Ptr = " << *S4Ptr <<endl;
cout << "assigning *S4Ptr to *S4Ptr" << endl;
*S4Ptr = *S4Ptr;
cout << "*S4Ptr = " << *S4Ptr << endl;
delete S4Ptr;
S1[0] = 'H';
S1[6] = 'B';
cout <<"S1 after S1[0] = 'H' and S1[6] = 'B' is: "<< S1 << endl;
cout << "Attempt to assign 'd' to S1[30] yields:" << endl;
S1[30] = 'd'; //Loi: Chi so vuot khoi mien!!!
return 0;
}
Hình 4.23: Kết quả của ví dụ 4.18
Lớp Date
#include <iostream.h>
class Date
{
private:
int Month;
int Day;
int Year;
static int Days[]; //Mang chua so ngay trong thang
void HelpIncrement(); //Ham tang ngay len mot
public:
Date(int M = 1, int D = 1, int Y = 1900);
void SetDate(int, int, int);
Date operator++(); //Tien to
Date operator++(int); //Hau to
const Date &operator+=(int);
int LeapYear(int); //Kiem tra nam nhuan
int EndOfMonth(int); //Kiem tra cuoi thang
friend ostream &operator<<(ostream &, const Date &);
};
int Date::Days[] = {31, 28, 31, 30, 31, 30,31, 31, 30, 31, 30, 31};
Date::Date(int M, int D, int Y)
{
SetDate(M, D, Y);
}
void Date::SetDate(int MM, int DD, int YY)
{
Month = (MM >= 1 && MM <= 12) ? MM : 1;
Year = (YY >= 1900 && YY <= 2100) ? YY : 1900;
if (Month == 2 && LeapYear(Year))
Day = (DD >= 1 && DD <= 29) ? DD : 1;
else
Day = (DD >= 1 && DD <= Days[Month-1]) ? DD : 1;
}
Date Date::operator++()
{
HelpIncrement();
return *this;
}
Date Date::operator++(int)
{
Date Temp = *this;
HelpIncrement();
return Temp;
}
const Date &Date::operator+=(int AdditionalDays)
{
for (int I = 1; I <= AdditionalDays; I++)
HelpIncrement();
return *this;
}
int Date::LeapYear(int Y)
{
if (Y % 400 == 0 || (Y % 100 != 0 && Y % 4 == 0) )
return 1; //Nam nhuan
return 0; //Nam khong nhuan
}
int Date::EndOfMonth(int D)
{
if (Month == 2 && LeapYear(Year))
return D == 29;
return D == Days[Month-1];
}
void Date::HelpIncrement()
{
if (EndOfMonth(Day) && Month == 12) //Het nam
{
Day = 1;
Month = 1;
++Year;
}
else
if (EndOfMonth(Day)) //Het thang
{
Day = 1;
++Month;
}
else
++Day;
}
ostream &operator<<(ostream &Output, const Date &D)
{
static char*MonthName[12]={"January","February","March","April","May",
"June","July", "August","September",
"October","November", "December" };
Output << MonthName[D.Month-1] << ' '<< D.Day << ", " << D.Year;
return Output;
}
int main()
{
Date D1, D2(12, 27, 1992), D3(0, 99, 8045);
cout << "D1 is " << D1 << endl
<< "D2 is " << D2 << endl
<< "D3 is " << D3 << endl << endl;
cout << "D2 += 7 is " << (D2 += 7) << endl << endl;
D3.SetDate(2, 28, 1992);
cout << " D3 is " << D3 << endl;
cout << "++D3 is " << ++D3 << endl << endl;
Date D4(3, 18, 1969);
cout << "Testing the preincrement operator:" << endl
<< " D4 is " << D4 << endl;
cout << "++D4 is " << ++D4 << endl;
cout << " D4 is " << D4 << endl << endl;
cout << "Testing the postincrement operator:" << endl
<< " D4 is " << D4 << endl;
cout << "D4++ is " << D4++ << endl;
cout << " D4 is " << D4 << endl;
return 0;
}
Hình 4.24: Kết quả của ví dụ 4.19
BÀI TẬP

Bài 1: Xây dựng lớp Complex chứa các số phức gồm các phép toán: +, -, *, /, +=, -=, *=, /=, ==, !=, >, >=, <, <=.

Bài 2: Xây dựng lớp String để thực hiện các thao tác trên các chuỗi, trong lớp này có các phép toán:
Phép toán + để nối hai chuỗi lại với nhau.
Phép toán = để gán một chuỗi cho một chuỗi khác.
Phép toán [] truy cập đến một ký tự trong chuỗi.
Các phép toán so sánh: ==, !=, >, >=, <, <=

Bài 3: Xây dựng lớp ma trận Matrix gồm các phép toán cộng, trừ và nhân hai ma trận bất kỳ.

Bài 4: Xây dựng lớp Rational chứa các số hữu tỷ gồm các phép toán +, - , *, /, ==, !=, >, >=, <, <=.

Bài 5: Xây dựng lớp Time để lưu trữ giờ, phút, giây gồm các phép toán:
Phép cộng giữa dữ liệu thời gian và một số nguyên là số giây, kết quả là một dữ liệu thời gian.