Tất cả các hàm của lớp là bạn của lớp khác

Một phần của tài liệu Bài giảng lập trình hướng đối tượng với C++ doc (Trang 56 - 82)

Đây là trường hợp tổng quát, trong đó có thể khai báo lớp bạn bè với các hàm. Mọi vấn đề sẽ đơn giản hơn nếu ta đưa ra một khai báo tổng thể để nói rằng tất cả các hàm thành phần của lớp B là bạn của lớp A. Muốn vậy ta sẽ đặt trong khai báo lớp A chỉ thị:

friend class B; //chỉ thị B là lớp bạn

Lưu ý: Để biên dịch khai báo của lớp A, chỉ cần đặt trước nó chỉ thị: class B; kiểu khai

CHƯƠNG 4

ĐỊNH NGHĨA TOÁ N TỬ TRÊN LỚ P

SƠ LƯỢC VỀ HÀM TOÁN TỬ

Trong chương 2 chúng ta đã tìm hiểu về khái niệm Đa năng hoá toán tử trong lập trình C+ +, định nghĩa toán tử trên lớp ở đây chính là Đa năng hoá toán tử áp dụng trên lớp. Như ta đã biết, việc thực hiện Đa năng hoá toán tử được thực tiến hành bằng cách định nghĩa các hàm toán tử. Tương tự đối với lớp, chúng ta cũng có thể Đa năng hoá toán tử bằng cách định nghĩa các hàm thành phần hoặc các hàm tự do. Trong chương này, chúng ta sẽ tìm hiểu cách thức định nghĩa Hàm toán tử trên lớp để 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.

Thông thường các thao tác trên đối tượng của lớp đượ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. Phương pháp gọi hàm này thì không gọn gàng cho một số 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ẽ gọn gàng hơn nếu 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. Ví dụ khi định nghĩa một lớp sophuc (số phức) để biểu diễn các số phức, có thể viết: a+b, a-b, a*b, a/b với a,b là các đối tượng sophuc. Để có được điều này, ta định

nghĩa chồng các phép toán +, -, * và / bằng cách định nghĩa hoạt động của từng phép toán giống như định nghĩa một hàm, chỉ khác là đây là hàm toán tử (operator function).

Các nguyên tắc cơ bản

C++ không cho phép tạo ra các toán tử mới, mà cho phép đa năng hoá các toán tử đã tồn tại 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.

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. Cú pháp của nó có dạng như

sau:

<kiểu> operator <ký hiệu toán tử> (danh sách tham số);

Khi định nghĩa chồng toán tử, phải tuân theo nguyên tắc là Một trong số các toán hạng

phải là đối tượng. Nói cách khác, hàm toán tử phải :

1. Hoặc là hàm thành phần, khi đó, hàm đã có một tham số ngầm định có kiểu lớp chính là đối tượng gọi hàm. Tham số ngầm định này đóng vai trò toán hạng đầu tiên (đối với phép toán hai ngôi) hay toán hạng duy nhất (đối với phép toán một ngôi). Do vậy, nếu toán tử là một ngôi thì hàm toán tử thành phần sẽ không chứa một tham số nào khác. Ngược lại khi toán tử là hai ngôi, hàm sẽ có thêm một đối số tường minh. 2. Hoặc là một hàm tự do. Trong trường hợp này, ít nhất tham số thứ nhất hoặc tham

số thứ hai (nếu có) phải có kiểu lớp. Và khi đó hàm toán tử phải được khai báo là bạn (friend) của các lớp có các đối tượng mà hàm thao tác.

Để sử dụng một toán tử đối với các đối tượng của một lớp, cần phải đa năng hoá toán tử đó cho lớp tương ứng ngoại trừ hai trường hợp sau:

1. 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à phép gán các thành viên dư liệu của lớp. Đối với các lớp có thành phần cấp phát động thì việc sao chép thành viên mặc định sẽ rất nguy hiểm. Khi đó, 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ế. 2. 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 như các toán tử khác.

Không nên định nghĩa nhưng hàm hàm toán tử khác nhau cùng làm nhưng công việc giống nhau vì dễ xảy ra nhập nhằng.

Những giới hạn

Phần lớn các toán tử đều có thể Đa năng hoá và chỉ có một số toán tử không thể đa năng hoá, chẳng hạn như: toán tử truy nhập thành phần cấu trúc “.”, toán tử phạm vi “::”, toán tử điều kiện “?:” (đã liệt kê chi tiết ở chương 2).

Ký hiệu đứng sau từ khoá operator phải là một trong số các ký hiệu toán tử áp dụng cho các kiểu dư liệu cơ sở, không thể dùng các ký hiệu mới. Có một số toán tử cần phải tuân theo các ràng buộc như sau:

1. phép =, [] nhất định phải được định nghĩa như hàm thành phần của lớp. 2. phép << và >> dùng với cout và cin phải được định nghĩa như hàm bạn.

3. hai phép toán ++ và -- có thể sử dụng theo hai cách khác nhau ứng với dạng tiền tố ++a, --b và dạng hậu tố a++, b--. Như vậy phải có hai hàm toán tử khác nhau.

Hàm cho dạng tiền tố Hàm cho dạng hậu tố operator++()

operator--()

operator++(int) operator--(int)

Lưu ý rằng tham số int trong dạng hậu tố chỉ mang ý nghĩa tượng trưng (dump type) Các toán tử được đa năng hoá 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 (phép đảo dấu) và hai ngôi (phép trừ số học) trên lớp tương ứng, nhưng không thể định nghĩa toán tử gán (=) một ngôi, còn ++ lại cho hai ngôi. Nếu làm vậy, chương trình dịch sẽ hiểu là tạo ra một ký hiệu phép toán mới.

Mỗi hàm toán tử chỉ có thể áp dụng với kiểu toán hạng nhất định; cần chú ý rằng các tính chất vốn có, chẳng hạn tính giao hoán của toán tử không thể áp dụng tuỳ tiện cho các toán tử được đa năng hoá. 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.

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. Trong trường hợp độ ưu tiên của toán tử không còn phù hợp, chúng ta có thể sử dụng các dấu ngoặc đơn để đặt thứ tự thự hiện của các toán tử đã đa năng hóa trong một biểu thức.

Không thể đa năng hoá để thay đổi ý nghĩa của toán tử trên các đối tượng của các kiểu có sẵ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.

Các tham số mặc định không thể sử dụng với một hàm toán tử.

CHIÊN LƯỢC SỬ DỤNG HÀM TOÁN TỬ

Việc đa năng hoá một phép toán là khá đơn giản, nhưng việc sử dụng phép toán đa năng hoá lại không đơn giản chút nào, mà cần phải được cân nhắc bởi lẽ nếu bị lạm dụng sẽ làm cho chương trình khó hiểu.

Phải làm sao để các phép toán vẫn giư được ý nghĩa trực quan nguyên thuỷ của chúng. Chẳng hạn không thể định nghĩa cộng “+” như phép trừ “-“ hai giá trị. Phải xác định trước ý nghĩa các phép toán trước khi viết định nghĩa của các hàm toán tử tương ứng.

Các phép toán một ngôi: *, &, ~, !, ++, --, sizeof,

(kiểu)

Các hàm toán tử tương ứng chỉ có một đối số và phải trả về giá trị cùng kiểu với toán hạng, riêng sizeof có giá trị trả về kiểu nguyên không dấu và toán tử (kiểu) dùng để trả về một

giá trị có kiểu như đã ghi trong dấu ngoặc.

Các phép toán hai ngôi: *, /, %, +, -, <<, >>, <, >, <=, >=,

==, !=, &, |, ^, &&, ||

Hai toán hạng tham gia các phép toán không nhất thiết phải cùng kiểu, mặc dù trong thực tế sử dụng thì thường là như vậy. Như vậy chỉ cần một trong hai đối số của hàm toán tử tương ứng là đối tượng là đủ.

Các phép gán: =, +=, -=, *=, /=, %=, >>=, <<=, &=, ^=, |=

Do các toán tử gán được định nghĩa dưới dạng hàm thành phần, nên chỉ có một tham số tường minh và không có ràng buộc gì về kiểu đối số và kiểu giá trị trả về của các phép gán.

Toán tử con trỏ cấu trúc: ->

Phép toán này được dùng để truy xuất các thành phần của đối tượng thuộc một cấu trúc hay một lớp khi mà một con trỏ trỏ vào đối tượng đó. Có thể định nghĩa phép toán -> giống

như đối với các phép toán một ngôi.

Toán tử truy nhập thành phần theo chỉ số: []

Toán tử lấy thành phần theo chỉ số được dùng để xác định một thành phần cụ thể trong một khối dư liệu (cấp phát động hay tĩnh). Thông thường phép toán này được dùng với mảng, nhưng cũng có thể định nghĩa lại nó khi làm việc với các kiểu dư liệu khác. Chẳng hạn với kiểu dư liệu vector có thể định nghĩa phép lấy theo chỉ số để trả về một thành phần toạ độ nào đó vector. Khi đa năng hoá phải được định nghĩa như hàm thành viên có một đối số tường minh.

Toán tử gọi hàm: ()

Toán tử () được dùng để gọi hàm, gồm có hai toán hạng, toán hạng thứ nhất là tên hàm, toán hạng thứ hai là danh sách tham số. Toán tử này có dạng giống như toán tử [] và khi đa

ĐỊNH NGHĨA TOÁN TỬ HAI NGÔI TRÊN LỚP

Các toán tử hai ngôi được đa năng hoá cho ở bảng kèm thao sau đây:

Tốn tử Ví dụ Tốn tử Ví dụ Tố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.1: Các tốn t hai ngơi ử đượ đc a n ng hóaă

Trường hợp định nghĩa hàm toán tử là hàm thành viên, ít nhất toán hạng đầu tiên của toán hạng là đối tượng thuộc lớp đang định nghĩa và hàm chỉ có 1 tham số. Cú pháp hàm như sau:

<kiểu> operator <ký hiệu toán tử> (<toán hạng thứ hai>);

Trường hợp định nghĩa hàm toán tử là hàm tự do, hàm phải có đầy đủ hai tham số. Cú pháp hàm như sau:

<kiểu> operator <ký hiệu toán tử> (<toán hạng thứ nhất, toán hạng thứ hai>);

Ví dụ 4.1: 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 == != > >= < <= bằng cách dùng các hàm thành viên.

#include <iostream.h> #include <math.h> #include <conio.h> class Complex {private:

double Real, Imaginary; public:

Complex(); // Constructor mặc định Complex(double R,double I);

Complex (const Complex & Z); // Constructor sao chép Complex (double R); // Constructor chuyển đổi

void Print(); // Hiển thị số phức // Các toán tử tính toán

Complex operator + (Complex Z); Complex operator - (Complex Z); Complex operator += (Complex Z); Complex operator -= (Complex Z); // Các toán tử so sánh

int operator == (Complex Z); int operator != (Complex Z); int operator > (Complex Z); int operator >= (Complex Z); int operator < (Complex Z); int operator <= (Complex Z); private:

double Abs(); // Giá trị tuyệt đối của số phức }; 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 Complex::operator + (Complex Z)

{ Complex Tmp;

Tmp.Real = Real + Z.Real;

Tmp.Imaginary = Imaginary + Z.Imaginary; return Tmp; }

Complex Complex::operator - (Complex Z) { Complex Tmp;

Tmp.Real = Real - Z.Real;

Tmp.Imaginary = Imaginary - Z.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 Complex::operator == (Complex Z)

{ return (Real == Z.Real) && (Imaginary == Z.Imaginary); } int Complex::operator != (Complex Z)

{ return (Real != Z.Real) || (Imaginary != Z.Imaginary); } int Complex::operator > (Complex Z)

{ return Abs() > Z.Abs(); }

int Complex::operator >= (Complex Z) { return Abs() >= Z.Abs(); }

int Complex::operator < (Complex Z) { return Abs() < Z.Abs(); }

int Complex::operator <= (Complex Z) { return Abs() <= Z.Abs(); }

double Complex::Abs() { return sqrt(Real*Real+Imaginary*Imaginary); } int main() { clrscr(); Complex X, Y(4.3,8.2), Z(3.3,1.1), T; cout<<"X: ";

X.Print(); cout<<endl<<"Y: "; Y.Print(); cout<<endl<<"Z: "; Z.Print(); cout<<endl<<"T: "; T.Print();

T=5.3;// Gọi constructor chuyển đổi cout<<endl<<endl<<"T = 5.3"<<endl; cout<<"T: "; T.Print(); X = Y + Z; cout<<endl<<endl<<"X = Y + Z: "; X.Print(); cout<<" = "; Y.Print(); cout<<" + "; Z.Print(); X = Y - Z; cout<<endl<<"X = Y - Z: "; X.Print(); cout<<" = "; Y.Print(); cout<<" - "; Z.Print(); cout<<endl<<endl<<"Y += T i.e "; Y.Print(); cout<<" += "; T.Print(); Y += T; cout<<endl<<"Y: "; Y.Print(); cout<<endl<<"Z -= T i.e "; Z.Print(); cout<<" -= "; T.Print(); Z -= T;

cout<<endl<<"Z: "; Z.Print();

Complex U(X);// Gọi constructor sao chép cout<<endl<<endl<<"U: ";

U.Print();

cout<<endl<<endl<<"Evaluating: X==U"<<endl; if (X==U) cout<<"They are equal"<<endl; cout<<"Evaluating: Y!=Z"<<endl;

if (Y!=Z) cout<<"They are not equal => "; if (Y>Z) cout<<"Y>Z"; else cout<<"Y<Z"; getch(); return 0; } X: (0,0) Y: (4.3,8.2) Z: (3.3,1.1) T: (0,0) T = 5.3 T: (5.3,0) X = Y + Z: (7.6,9.3) = (4.3,8.2) + (3.3,1.1) X = Y - Z: (1,7.1) = (4.3,8.2) - (3.3,1.1) Y += T i.e (4.3,8.2) += (5.3,0) Y: (9.6,8.2) Z -= T i.e (3.3,1.1) -= (5.3,0) Z: (-2,1.1) U: (1,7.1) Evaluating: X==U They are equal Evaluating: Y!=Z

They are not equal => Y>Z

Ví dụ 4.2: 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à toán tử so sánh == bằng cách dùng các hàm tự do và được định nghĩa là hàm bạn trong khai báo lớp.

#include <iostream.h> #include <math.h> #include <conio.h> class Complex

{private:

double Real,Imaginary; public:

Complex ();

Complex (double R,double I); void Print(); // Hiển thị số phức

friend Complex operator + (Complex Z1,Complex Z2); friend int operator == (Complex Z1,Complex Z2); }; Complex::Complex() { Real = 0.0; Imaginary = 0.0; } Complex::Complex(double R,double I) { Real = R; Imaginary = I; } 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; }

int operator == (Complex Z1,Complex Z2)

{ return (Z1.Real == Z2.Real) && (Z1.Imaginary == Z2.Imaginary); } int main() { clrscr(); 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 + Z; cout<<endl<<endl<<"X = Y + Z: ";

X.Print(); cout<<" = "; Y.Print(); cout<<" + "; Z.Print(); getch(); return 0; } X: (0,0) Y: (4.3,8.2) Z: (3.3,1.1) X = Y + Z: (7.6,9.3) = (4.3,8.2) + (3.3,1.1)

ĐỊNH NGHĨA TOÁN TỬ MỘT NGÔI TRÊN LỚP

Các toán tử một ngôi được đa năng hoá cho ở bảng kèm thao sau đây:

Tốn tử Ví dụ Tốn tử Ví dụ + +c ~ ~c - -c ! !a * *c ++ ++c, c++ & &c -- --c, c-- -> c->

Hình 4.2: Các tố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 bằng một hàm thành viên không tĩnh không có tham số, cú pháp như sau:

<kiểu> operator <ký hiệu toán tử> ();

hoặc bằng một hàm tự do có một tham số, cú pháp như sau:

<kiểu> operator <ký hiệu toán tử> (<toán hạng >);

Lưu ý: 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.2: 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ử - (đổi

dấu) bằng cách dùng hàm thành phần trong khai báo lớp.

#include <math.h> #include <conio.h> class Complex { private: double Real,Imaginary; public: Complex();

Complex(double R,double I);

void Print(); // Hiiển thị số phức Complex operator -(); }; Complex::Complex() { Real = 0.0; Imaginary = 0.0; } Complex::Complex(double R,double I) { Real = R; Imaginary = I; } void Complex::Print() { cout<<'('<<Real<<','<<Imaginary<<')'; } Complex Complex::operator -()

Một phần của tài liệu Bài giảng lập trình hướng đối tượng với C++ doc (Trang 56 - 82)

Tải bản đầy đủ (DOC)

(133 trang)
w