Các toán tử có thể ñược ñịnh nghĩa chồng như một hàm thành phần phương thức của lớp hoặc như một hàm bạn của lớp.. Các toán tử sau khi ñược ñịnh nghĩa chồng cho lớp có thể sử dụng với cá
Trang 1CHƯƠNG 4
ðỊNH NGHĨA CHỒNG TOÁN TỬ
ðịnh nghĩa chồng (overloading) là một khả năng mạnh trong C++ vì nó cho phép xây dựng các toán tử cần thiết trên các lớp, giúp cho chương trình ñược viết ngắn gọn
và rõ ràng hơn Trong C++ có thể ñịnh nghĩa chồng hầu hết các toán tử Các toán tử
có thể ñược ñịnh nghĩa chồng như một hàm thành phần (phương thức) của lớp hoặc như một hàm bạn của lớp Chương này sẽ trình bày về cách ñịnh nghĩa chồng và sử dụng các hàm toán tử trong C++
§ 1 VÍ DỤ TRÊN LỚP SỐ PHỨC 1.1 Xây dựng hàm bạn cộng hai số phức
Xét một lớp số phức (SP) và xây dựng một hàm bạn ñể cộng hai số phức như sau: class SP
{
double a, b; // Phần thực, phần ảo
public:
friend SP cong(SP u1, SP u2)
{
SP u;
u.a = u1.a + u2.a ; u.b = u1.b + u2.b ; return u;
}
};
void main()
{
SP u, u1, u2;
u = cong(u1, u2); // u là tổng u1+u2
}
1.2 Xây dựng hàm toán tử cộng hai số phức
Trong ví dụ về lớp SP ở trên, chúng ta ñã xây dựng hàm cộng hai số phức bằng cách sử dụng hàm bạn Tuy vậy, trong trường hợp này, chúng ta có thể không cần sử dụng hàm bạn vì chỉ làm việc với một lớp
Trang 2Ví dụ sau có cùng mục ñích (cộng hai số phức) nhưng xây dựng hàm cong là một phương thức của lớp SP:
class SP
{
double a, b; // Phần thực, phần ảo
public:
SP cong(SP u2);
};
SP SP::cong(SP u2)
{
SP u;
u.a= a+u2.a;
u.b= b+u2.b;
return u;
}
void main()
{
SP u, v, u1, u2, u3, u4;
u= u1.cong(u2); // u là tổng u1+u2
v= u1.cong(u2.cong(u3.cong(u4))); // v là tổng u1+u2+u3+u4
}
Nhận xét:
Với cả hai cách trên (dùng hàm bạn và dùng phương thức của lớp), biểu thức sẽ phức tạp hơn nếu có nhiều phép toán số phức (như tính v trong ví dụ trên)
C++ cho ta một công cụ ñể giải quyết vấn ñề này: ñó là khả năng ñịnh nghĩa chồng các toán tử Các toán tử sau khi ñược ñịnh nghĩa chồng cho lớp có thể sử dụng với các ñối tượng của lớp ñó theo cách tương tự như với các biến thuộc kiểu dữ liệu chuẩn
Ví dụ:
class SP
{
double a, b;
public:
SP operator+(SP u2);
Trang 3};
SP SP::operator+(SP u2)
{
SP u;
u.a= a+u2.a;
u.b= b+u2.b;
return u;
}
void main()
{
SP u, v, u1, u2, u3, u4;
u= u1+u2; // u là tổng u1+u2
v= u1+u2+u3+u4; // v là tổng u1+u2+u3+u4
}
Nhận xét:
Toán tử cộng ‘+’ ñóng vai trò như hàm cong ở ví dụ trên, và thực chất chương trình dịch cũng hiểu nó như một hàm Nói cách khác chỉ thị u= u1+u2 trong trường hợp này ñược hiểu là u= u1.operator+(u2) Nhưng rõ ràng cách sử dụng toán tử làm cho các biểu thức tự nhiên và ngắn gọn hơn nhiều
§ 2 GIỚI HẠN CỦA ðỊNH NGHĨA CHỒNG TOÁN TỬ
2.1 Các toán tử có thể ñịnh nghĩa chồng
Phần lớn các toán tử trong C++ ñều có thể ñịnh nghĩa chồng, ngoại trừ các toán tử truy nhập vào các thành phần ‘.’, toán tử xác ñịnh phạm vi ‘::’, toán tử ñiều kiện ‘?:’
và toán tử sizeof
Chú ý rằng ngay trong cụm từ “ñịnh nghĩa chồng” ñã phản ánh rõ: không thể tạo
ra các toán tử mới mà chỉ ñịnh nghĩa lại các toán tử ñã có ñể có thể làm việc với những kiểu dữ liệu khác với thiết kế chuẩn của nó
2.2 Các nguyên tắc khi ñịnh nghĩa chồng toán tử
Các toán tử ñịnh nghĩa chồng phải bảo toàn số ngôi của chính toán tử ñó (theo
cách hiểu thông thường), ví dụ có thể ñịnh nghĩa toán tử ‘-‘ một ngôi (ñảo dấu) và hai ngôi nhưng không thể ñịnh nghĩa toán tử gán ‘=’ là một ngôi
Các toán tử ñịnh nghĩa chồng nên bảo toàn ý nghĩa nguyên thủy của nó, ví dụ ta
có thể ñịnh nghĩa chồng toán tử ‘-‘ ñể thực hiện phép cộng (hai ñối tượng), nhưng ñiều ñó rõ ràng không có ích lợi gì mà dễ gây ra nhầm lẫn
Các hàm toán tử có thể ñược ñịnh nghĩa như là hàm thành phần của lớp hoặc như một hàm bạn:
Trang 4+ Trong trường hợp hàm toán tử là hàm thành phần của lớp (phương thức): khi ñó hàm ñã có một ñối (tham số) ngầm ñịnh (xác ñịnh chính ñối tượng ñang gọi hàm), do vậy với toán tử một ngôi thì hàm toán tử không chứa ñối nào, còn với toán tử hai ngôi thì hàm toán tử có một ñối
+ Trong trường hợp hàm toán tử là hàm bạn: toán tử bao nhiêu ngôi thì hàm cần
có bấy nhiêu ñối
Chú ý: Vì các toán tử phải thao tác trên các dữ liệu của ñối tượng mà các dữ liệu
thường là các thành phần riêng nên hàm toán tử không thể là một hàm tự do
Vậy khi nào thì ñịnh nghĩa (hàm toán tử) là hàm thành phần, khi nào là hàm bạn? Nguyên tắc chung là: nếu toán tử chỉ làm việc với các ñối tượng của cùng một lớp thì ñịnh nghĩa là hàm thành phần hay hàm bạn ñều ñược, nhưng nếu toán tử làm việc với các ñối tượng thuộc nhiều lớp khác nhau thì nó bắt buộc phải là hàm bạn
Một số trường hợp cụ thể cần lưu ý:
+ Các toán tử = , [] phải ñược ñịnh nghĩa là hàm thành phần của lớp
+ Các toán tử << , >> phải ñược ñịnh nghĩa là hàm bạn
+ Các toán tử ++ , có thể ñược sử dụng theo hai cách khác nhau (tiền tố và hậu tố), và ñiều này ñòi hỏi hai hàm toán tử khác nhau
Trong thực tế thường hay ñịnh nghĩa các hàm toán tử là các phương thức của lớp,
vì thế chúng ta sẽ ñề cập chi tiết hơn trong § 3 Cách ñịnh nghĩa chồng một số toán tử quan trọng ñược trình bày trong § 4
§ 3 PHƯƠNG THỨC TOÁN TỬ 3.1 Cách xây dựng phương thức toán tử
Các phương thức toán tử ñược xây dựng như các phương thức thông thường, chỉ
có khác cách ñặt tên Tên của phương thức toán tử (cũng giống như hàm toán tử) ñược tạo bằng cách ghép từ khoá operator với một phép toán (toán tử) Chúng ta chỉ
có thể sử dụng các phép toán ñã ñược ñịnh nghĩa trong C++ ñể ñịnh nghĩa phương thức cho một lớp mới, nên còn ñược gọi là ñịnh nghĩa chồng toán tử (operator overloading)
Ví dụ:
operator+
operator<<
operator>>
Cũng giống như phương thức thông thường, phương thức toán tử có ñối ñầu tiên
(ñối không tường minh) là con trỏ this
3.2 Toán tử một toán hạng
Các phương thức toán tử một toán hạng (một ngôi) dùng ngay con trỏ this ñể biểu thị toán hạng duy nhất này, nên trong phương thức sẽ không có ñối tường minh Ví
dụ phương thức toán tử ‘-‘ ñể ñổi dấu một ñối tượng kiểu SP (số phức) có thể viết như sau:
Trang 5class SP
{
double a; // Phần thực
double b; // Phần ảo
public:
SP operator-();
} ;
SP SP:: operator-()
{
SP u ;
u.a = - this->a ;
u.b = - this->b ;
return u;
}
Cách dùng:
SP u, v;
u = -v;
Chú ý: Các phép toán ++ và có thể sử dụng theo hai cách khác nhau (tiền tố và
hậu tố) nên khi ñịnh nghĩa ñòi hỏi phải có hai cách khác nhau như sau:
+ Cho dạng tiền tố:
operator++()
operator ()
+ Cho dạng hậu tố sử dụng thêm ñối giả int:
operator++(int)
operator (int)
3.3 Toán tử hai toán hạng
Với các phương thức toán tử hai toán hạng thì con trỏ this ứng với toán hạng thứ nhất, nên trong phương thức chỉ cần dùng một ñối tường minh ñể biểu thị toán hạng thứ hai Ví dụ phương thức toán tử ‘+’ ñể cộng hai ñối tượng kiểu SP (số phức) có thể viết như sau:
class SP
{
double a; // Phần thực
double b; // Phần ảo
public:
SP operator+(SP u2);
} ;
Trang 6SP SP:: operator+(SP u2)
{
SP u ;
u.a = this->a + u2.a ;
u.b = this->b + u2.b ;
return u;
}
Cách dùng:
SP p, p, r;
r = p + q ;
§ 4 ðỊNH NGHĨA CHỒNG MỘT SỐ TOÁN TỬ 4.1 Toán tử gán ‘=’
Trong mỗi lớp ñều có một toán tử gán mặc ñịnh, và nếu trong thành phần dữ liệu không có con trỏ thì toán tử gán mặc ñịnh ñủ ñáp ứng yêu cầu, không cần thiết phải ñịnh nghĩa chồng toán tử gán
Khi ñịnh nghĩa chồng thì toán tử gán phải ñược ñịnh nghĩa là hàm thành phần của lớp, và do nó là toán tử hai ngôi nên sẽ chỉ cần một tham số trong hàm toán tử
Phép gán a=b thực chất ñược hiểu là a.operator=(b) và b có thể ñược truyền theo dưới dạng tham trị hoặc tham chiếu Việc truyền theo tham trị ñòi hỏi sự có mặt của hàm tạo sao chép, hơn thế nữa có thể làm chương trình chạy chậm vì mất thời gian sao chép một lượng dữ liệu lớn Vì vậy b thường ñược truyền cho hàm toán tử operator= dưới dạng tham chiếu
Giá trị trả về của hàm toán tử phụ thuộc vào mục ñích sử dụng của biểu thức gán Giả sử a, b, c là các ñối tượng kiểu X, và nếu giá trị trả về của hàm toán tử gán là kiểu void thì không thể thực hiện phép gán a=b=c
Ví dụ:
class DT
{
int n;
float *a;
public:
DT() { }
DT(int n1);
~DT() { delete a; }
void hien();
DT& operator=(DT& d);
};
DT::DT(int n1)
{
n= n1;
a= new float[n];
Trang 7for (int i=0; i<n; ++i)
{
cout<<"a["<<i<<"]= ";
cin>>a[i];
}
}
void DT::hien()
{
for (int i=0; i<n; ++i) cout<<a[i]<<" ";
}
DT& DT::operator=(DT& d)
{
n= d.n;
for (int i=0; i<n; ++i) a[i]= d.a[i];
return (*this);
}
void main()
{
DT y(3);
cout<<endl<<"y:"<<endl; y.hien();
DT x;
x=y;
cout<<endl<<"x:"<<endl; x.hien();
}
4.2 Toán tử lấy giá trị phần tử trong mảng ‘[]’
Toán tử ‘[]’ phải ñược ñịnh nghĩa là hàm thành phần của lớp Nếu một lớp có ñịnh nghĩa toán tử ‘[]’ thì có thể sử dụng các ñối tượng của lớp ñó theo cách giống như với mảng
Ví dụ:
class DT
{
int n;
float *a;
public:
float& operator[](int i) { return a[i]; }
// cac phuong thuc khac
};
void main()
{
DT y(5);
cout<<”y[2]= “<<y[2];
}
4.3 Toán tử << và >>
Ta ñã biết nếu x là một biến thuộc kiểu dữ liệu cơ sở thì cin>>x nhận giá trị gõ từ bàn phím ñưa vào biến x và cout<<x ñưa giá trị x ra màn hình Thực chất chúng ta ñã
Trang 8sử dụng toán tử >> ñể nhập và toán tử << ñể xuất dữ liệu Các toán tử này ñều là các
toán tử hai toán hạng: toán hạng thứ nhất là dòng nhập/xuất và toán hạng thứ hai là biến dữ liệu Trong chỉ thị cin>>x thì dòng nhập cin chính là thể hiện bàn phím, còn trong chỉ thị cout<<x thì dòng xuất cout chính là thể hiện màn hình
Trong C++ chúng ta có thể ñịnh nghĩa chồng các toán tử << và >> ñể làm việc với các ñối tượng của một lớp nào ñó Tuy nhiên khác với các toán tử ‘=’ và ‘[]’ ñề cập ở các mục trên, các toán tử << và >> là các toán tử hai toán hạng mà mỗi mỗi toán hạng là ñối tượng của các lớp khác nhau Vì thế các toán tử << và >> nhất thiết phải ñược ñịnh nghĩa là những hàm bạn
Ví dụ:
#include <iostream.h>
class DT
{
int n;
float *a;
public:
friend ostream& operator <<(ostream& os, DT& d);
friend istream& operator >>(istream& is, DT& d);
};
ostream& operator <<(ostream& os, DT& d)
{
for (int i=0; i<=d.n; ++i)
os<<d.a[i]<<" ";
return os;
}
istream& operator >>(istream& is, DT& d)
{
cout<<"Bac da thuc: ";
cin>>d.n;
d.a= new float[d.n +1];
for (int i=0; i<=d.n; ++i)
is>>d.a[i];
return is;
}
void main()
{
DT d1;
cout<<endl<<"Nhap da thuc:"<<endl;
cin>>d1;
cout<<endl<<"Da thuc vua nhap:"<<endl;
cout<<d1;
}
4.4 Chuyển ñổi kiểu dữ liệu
Với các kiểu dữ liệu chuẩn ta có thể thực hiện các phép chuyển kiểu ngầm ñịnh hoặc chuyển kiểu tường minh Phép chuyển kiểu ngầm ñịnh ñược thực hiện bởi Trình
Trang 9biên dịch, chẳng hạn khi gán một giá trị int vào một biến long hoặc cộng một giá trị long vào một biết float Phép chuyển kiểu tường minh xảy ra khi thực hiện một phép
ép kiểu
Các kiểu lớp không thể tự ñộng chuyển kiểu mà chúng ta cần phải tự xây dựng
C++ cung cấp cách thức ñể ñịnh nghĩa các phép chuyển kiểu ngầm ñịnh và tường minh cho lớp
Phép chuyển kiểu ngầm ñịnh cho một lớp ñược ñịnh nghĩa bằng một hàm tạo chuyển kiểu (conversion constructor), với ñối số có kiểu cần phải chuyển thành ñối tượng của lớp ñó ðối số này có thể có kiểu cơ sở hay kiểu lớp khác
Ví dụ với lớp số phức SP, ta có hàm tạo chuyển kiểu ñể chuyển (ngầm ñịnh) một
số kiểu float thành một só kiểu SP như sau:
class SP
{
int a, b; // phần thực, phần ảo
public:
SP(float r) { a=r; b=0; }
};
Khi ñó ta có thể viết các biểu thức như u+7.5 với u là một số phức SP vì 7.5 sẽ ñược tự ñộng chuyển thành số phức (với phần thực bằng 7.5 và phần ảo bằng 0) Phép chuyển kiểu tường minh ñược ñịnh nghĩa thông qua toán tử ép kiểu (cast operator) C++ qui ñịnh rằng một hàm toán tử chuyển kiểu phải là hàm thành phần (phương thức) của lớp liên quan, không có ñối số và kiểu trả về Tên của toán tử chuyển kiểu có dạng sau:
operator Kiểu();
Trong ñó Kiểu là tên kiểu dữ liệu mà ñối tượng ñược chuyển sang
Ví dụ ta có thể ñịnh nghĩa hàm toán tử ñể chuyển kiểu từ SP sang float:
class SP
{
int a, b;
public:
operator float() { return a; }
};
Khi ñó nếu u là một số phức SP, ta vẫn có thể viết: float x= u;
Ví dụ: Chương trình sau sẽ ñịnh nghĩa lớp ña thức (DT) gồm
+ Các thuộc tính:
int n ; // bậc ña thức
double *a ; // trỏ tới vùng nhớ chứa các hệ số ña thức
+ Các phương thức:
Trang 10operator+ dùng ñể cộng 2 ña thức
operator- dùng ñể trừ 2 ña thức
operator- (một toán hạng) dùng ñể ñổi dấu các hệ số ña thức operator* dùng ñể nhân 2 ña thức
operator^ dùng ñể tính giá trị ña thức
operator[] dùng ñể cho biết bậc và hệ số của ña thức
+ Các hàm bạn:
operator<< dùng ñể in các hệ số ña thức
operator>> dùng ñể nhập các hệ số ña thức
+ Hàm (tự do)
double F(DT p, double x) dùng ñể tính p(x) là giá trị ña thức tại x
+ Nói thêm về phương thức chỉ số và hàm tự do F
- Nếu p là ñối tượng của lớp DT, thì hàm chỉ số cho biết:
p[-1] = double(n)
p[i] = a[i] , i=0, 1, , n
- Hàm tự do F sẽ dùng phương thức chỉ số ñể xác ñịnh n , các hệ số ña thức và dùng chúng ñể tính giá trị ña thức
+ Trong chương trình sử dụng hàm new ñể cấp phát vùng nhớ chứa hệ số ña thức + Nội dung chương trình gồm:
- Nhập, in các ña thức p, q, r, s
- Tính ña thức: f = -(p + q)*(r - s)
- Nhập các số thực x1 và x2
- Tính f(x1) (bằng cách dùng phương thức operator^)
- Tính f(x2) (bằng cách dùng hàm F)
#include <iostream.h>
#include <math.h>
class DT
{
int n; // Bac da thuc
double *a; // Tro toi vung nho chua cac he so da thuc a0, a1, a2,
public:
friend ostream& operator<< (ostream& os,const DT &d);
friend istream& operator>> (istream& is,DT &d);
DT operator-();
DT operator+(const DT &d2);
DT operator-(DT d2);
DT operator*(const DT &d2);
Trang 11double operator^(const double &x); // Tinh gia tri da thuc
double operator[](int i)
{
if(i<0) return double(n);
else return a[i];
}
} ;
// Ham tinh gia tri da thuc
double F(DT d,double x)
{
double s=0.0 , t=1.0;
int n;
n = int(d[-1]);
for (int i=0; i<=n; ++i)
{
s += d[i]*t;
t *= x;
}
return s;
}
ostream& operator<< (ostream& os, const DT &d)
{
os << "Cac he so da thuc (a0, a1, a2, …): " ;
for (int i=0 ; i<= d.n ; ++i)
os << d.a[i] <<" " ;
return os;
}
istream& operator>> (istream& is, DT &d)
{
cout << "Bac da thuc: " ;
cin >> d.n;
d.a = new double[d.n+1];
cout << "Nhap cac he so da thuc:\n" ;
for (int i=0 ; i<= d.n ; ++i)
{
cout << "He so bac " << i << " = " ;
is >> d.a[i] ;