Toán tử tải bội thân thiện

Một phần của tài liệu Lập trình hướng đối tượng (Trang 66)

- Toán tử tải bội thân thiện không thuộc lớp nơi nó đợc khai báo, do vậy toán tử tải bội thân thiện dùng nh một phép toán thông thờng mà không phải sử dụng thông qua đối tợng. Cũng chính vì lý do đó mà toán tử tải bội thân thiện có số ngôi bằng số ngôi của toán tử gốc tức là phép toán có sẵn của ngôn ngữ.

4.1. Khai báo toán tử tải bội thân thiện β

class class_name {... public:

...

friend return_type operator β (arg_list); ...};

4.2. Định nghĩa toán tử tải bội β

return_type operator β (arg_list) { // nội dung toán tử β }

Ví dụ 1 (Complex2.CPP)

Xây dựng lớp các số phức (COMPLEX) với các phép toán tải bội thân thiện cộng, trừ, nhân và chia 2 phân số với nhau. #include <iostream.h> #include <conio.h> class COMPLEX { float x,y; public: COMPLEX() {x=y=0;} COMPLEX(float a) {x=y=a;}

COMPLEX(float a,float b) {x=a;y=b;} COMPLEX(COMPLEX &Z) {x=Z.x; y=Z.y;}

friend COMPLEX operator + (COMPLEX,COMPLEX); friend COMPLEX operator - (COMPLEX,COMPLEX); friend COMPLEX operator * (COMPLEX,float);

friend COMPLEX operator * (float,COMPLEX);

friend COMPLEX operator * (COMPLEX,COMPLEX); friend COMPLEX operator / (COMPLEX,COMPLEX); friend ostream& operator << (ostream&, COMPLEX &); friend istream& operator >> (istream&, COMPLEX &);

};

COMPLEX operator +(COMPLEX z1,COMPLEX z2) {COMPLEX z;

z.x = z1.x + z2.x; z.y = z1.y + z2.y; return z;}

COMPLEX operator -(COMPLEX z1,COMPLEX z2) {COMPLEX z;

z.x = z1.x - z2.x; z.y = z1.y - z2.y; return z;} COMPLEX operator *(COMPLEX z1,float r) {COMPLEX z;

z.x = z1.x * r; z.y = z1.y * r;return z;}

COMPLEX operator *(float r,COMPLEX z1) {COMPLEX z;

z.x = z1.x * r; z.y = z1.y * r;return z;}

COMPLEX operator *(COMPLEX z1,COMPLEX z2) {COMPLEX z;

z.x = z1.x * z2.x; z.y = z1.y * z2.y;return z;}

COMPLEX operator /(COMPLEX z1,COMPLEX z2) {COMPLEX z;

z.x = z1.x / z2.x; z.y = z1.y / z2.y;return z;}

ostream& operator << (ostream &dout, COMPLEX &z) { dout<<z.x<<" + i * "<<z.y

return dout;}

istream& operator >> (istream& din, COMPLEX& z) { float x1,y1;

cout<<"x= "; din >> x1; z.x=x1; cout<<"y= "; din >> y1; z.y=y1; return din;}

int main() { clrscr(); COMPLEX m,n;

cout<<"nhap so phuc m: \n"; cin>>m; cout<<"nhap so phuc n: \n"; cin>>n; cout<<"m ="<<m; cout<<"n ="<<n; cout<<"m+n ="<<m+n; cout<<"m-n ="<<m-n; cout<<"m*n ="<<m*n; cout<<"m/n ="<<m/n; cout<<"2*(m+n)/(m-n) ="<<2*(m+n)/(m-n); getche(); return 0; } * Một so sánh quan trọng: Chúng ta cần so sánh về cách sử dụng phép toán tải bội (nh một hàm thành phần của lớp) và phép toán tải bội thân thiện. Phép toán ở đây có thể là toán tử gốc của ngôn ngữ hoặc hàm ngời dùng định nghĩa.

Hai trích dẫn ví dụ dới đây là để nhấn mạnh việc so sánh về các phép vào/ra tải bội và vào/ra tải bội thân thiện: cách khai báo, cách định nghĩa và cách gọi thực hiện:

Ví dụ A : (tải bội thân thiện)

class COMPLEX { float x,y;

friend ostream& operator << (ostream&, COMPLEX &); friend istream& operator >> (istream&, COMPLEX &);

… };

ostream& operator << (ostream &dout, COMPLEX &z) { if (z.y=0) dout<<z.x;

else

dout<<z.x<<(z.y>0?"+":"-")<<"i"<<z.y <<endl; return dout;

}

istream& operator >> (istream& din, COMPLEX& z) { float x1,y1;

cout<<"x= "; din >> x1; z.x=x1; cout<<"y= "; din >> y1; z.y=y1; return din;

}

int main() { clrscr(); COMPLEX m,n,p;

cout<<"nhap so phuc m: \n"; cin>>m; cout<<"nhap so phuc n: \n"; cin>>n; p=m+n; cout<<m<<"+\n"<<n<<"=\n"<<p; getche(); return 0;} Ví dụ A : (tải bội bình thờng) class COMPLEX { float x,y; public: …

ostream& operator << (ostream&); istream& operator >> (istream&); …

ostream& COMPLEX::operator << (ostream &dout) { if (y=0) dout<<x; else

dout<<x<<(y>0?"+":"-")<<"i"<<y <<endl; return dout;

}

istream& COMPLEX::operator >> (istream& din) { float x1,y1;

cout<<"x= "; din >> x1; x=x1; cout<<"y= "; din >> y1; y=y1; return din;

}

int main() { clrscr(); COMPLEX m,n,p;

cout<<"nhap so phuc m: \n"; m.operator>>(cin); cout<<"nhap so phuc n: \n"; n.operator>>(cin); p=m+n; m.operator<<(cout); cout<<"+\n"; n.operator<<(cout); cout<<"=\n"; p.operator<<(cout); getche(); return 0;}

Rõ ràng trong trờng hợp này dùng toán tử vào/ra tải bội không thân thiện là không tự nhiên trong cách sử dụng.

Ví dụ 2 (FRAC.CPP)

Xây dựng lớp các phân số (FRAC) với các phép toán tải bội thân thiện cộng, trừ, nhân, chia phân số cùng với các phép toán vào/ra phân số.

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

{ int t,m; public:

FRAC() {t=m=0;}

FRAC(float a,float b) {t=a;m=b;} FRAC(FRAC &p) {t=p.t; m=p.m;} friend FRAC reduce(FRAC);

friend FRAC operator + (FRAC,FRAC); friend FRAC operator - (FRAC,FRAC); friend FRAC operator * (FRAC,FRAC); friend FRAC operator / (FRAC,FRAC);

friend ostream& operator << (ostream&, FRAC &); friend istream& operator >> (istream&, FRAC &); };

FRAC reduce(FRAC p) { int a=abs(p.t), b=abs(p.m);

while(a!=b) if (a>b) a-=b; else b-=a; p.t=p.t/a;

p.m=p.m/a; return p; }

FRAC operator +(FRAC p1,FRAC p2) {FRAC p;

p.t = p1.t*p2.m + p2.t*p1.m; p.m = p1.m*p2.m;

return reduce(p); }

FRAC operator -(FRAC p1,FRAC p2) {FRAC p;

p.t = p1.t*p2.m - p2.t*p1.m; p.m = p1.m*p2.m;

return reduce(p); }

FRAC operator *(FRAC p1,FRAC p2) {FRAC p; p.t = p1.t*p2.t; p.m = p1.m*p2.m; return reduce(p); }

FRAC operator /(FRAC p1,FRAC p2) {FRAC p;

p.t = p1.t*p2.m; p.m = p1.m*p2.t; return reduce(p); }

ostream& operator << (ostream &dout, FRAC &p) { dout<<p.t<<"/"<<p.m;

return dout; }

istream& operator >> (istream& din, FRAC& p) { int a;

cout<<"t= "; din >> a; p.t=a;

do { cout<<"m= "; din >> a; p.m=a; } while (a==0); return din;

}

int main() { clrscr(); FRAC p,q;

cout<<"nhap phan so p: \n"; cin>>p; cout<<"nhap so phuc q: \n"; cin>>q; cout<<"p ="<<reduce(p)<<endl; cout<<"q ="<<reduce(q)<<endl; cout<<"p+q ="<<p+q<<endl; cout<<"p-q ="<<p-q<<endl; cout<<"p*q ="<<p*q<<endl; cout<<"p/q ="<<p/q<<endl;

getche(); return 0; }

Ví dụ 3 (VECTOR.CPP)

Xây dựng lớp các Vector (VECTOR) với các phép toán tải bội thân thiện cộng, trừ, nhân và các phép vào/ra đối với vector.

Cài đặt vector bằng con trỏ float trỏ vào vector có số chiều n. #include <iostream.h> #include <conio.h> const maxsize=10; class VECTOR { int n; float *v; public:

VECTOR() {v =new float[maxsize];} VECTOR(VECTOR &);

~VECTOR() { delete v ;} int size() {return n;}

friend VECTOR operator + (VECTOR,VECTOR); friend VECTOR operator - (VECTOR,VECTOR); friend VECTOR operator * (VECTOR,VECTOR); friend ostream& operator << (ostream&, VECTOR &); friend istream& operator >> (istream&, VECTOR &); };

VECTOR::VECTOR(VECTOR &b) { int i;

for(i=0;i<n;v[i]=b.v[i],i++); }

VECTOR operator +(VECTOR b,VECTOR c) {VECTOR d; d.n=b.n; int i; for(i=0;i<c.n;i++) d.v[i]=b.v[i]+c.v[i]; return d; }

VECTOR operator -(VECTOR b,VECTOR c) {VECTOR d; d.n=b.n; int i; for(i=0;i<c.n;i++) d.v[i]=b.v[i]-c.v[i]; return d; }

VECTOR operator *(VECTOR b,VECTOR c) {VECTOR d; d.n=b.n; int i; for(i=0;i<d.n;i++) d.v[i]=b.v[i]*c.v[i]; return d; }

ostream& operator << (ostream &dout, VECTOR &b) { int i;

for(i=0;i<b.n;i++) dout<<b.v[i]<<" "; dout<<endl;

return dout; }

istream& operator >> (istream &din, VECTOR &b) { int i,x;

for(i=0;i<b.n;i++) {cout<<"vector["<<i+1<<"] ="; din>>x; b.v[i]=x; } return din;} int main() { clrscr(); VECTOR a,b;

cout<<"nhap vec to a: \n"; cin>>a; cout<<"nhap vec to b: \n"; cin>>b; cout<<"a ="<<a; cout<<"n ="<<b; cout<<"a+n ="<<a+b; cout<<"a-n ="<<a-b; cout<<"a*n ="<<a*b; getch(); return 0; } 5. Bài tập 2 5.1.

Xây dựng lớp các ma trận vuông (MATRIX) với các phép toán tải bội thân thiện: vào/ra và cộng, trừ, nhân 2 ma trận với nhau.

Cài đặt ma trận bằng con trỏ float.

5.2.

Xây dựng lớp các mảng (ARRAY) nguyên, với các phép toán tải bội thân thiện: vào/ra và cộng, trừ 2 mảng với nhau.

Cài đặt mảng lớp ARRAY nh một mảng bình thờng, tức là không cần dùng con trỏ int.

5.3.

Xây dựng lớp các đa thức bậc khác nhau (MULINO) (Mulinomial) hệ số nguyên, với các phép toán tải bội thân thiện: vào/ra và cộng, trừ, nhân 2 đa thức.

6. Chuyển đổi kiểu

Có các loại chuyển đổi kiểu sau đây: 1- Chuyển từ kiểu cơ sở sang kiểu cơ sở. 2- Chuyển từ kiểu lớp sang kiểu cơ sở. 3- Chuyển từ kiểu cơ sở sang kiểu lớp. 4- Chuyển từ kiểu lớp sang kiểu lớp.

6.1. Chuyển từ kiểu cơ sở sang kiểu cơ sở.

- Riêng đối với việc chuyển từ kiểu cơ sở sang kiểu cơ sở là do ngôn ngữ tự động hoặc do ép kiểu.

+ Tự động chuyển kiểu theo nguyên tắc: char  int  long  float  double

+ Chuyển kiểu đợc sử dụng trong lời gọi hàm để ép kiểu của biến truyền vào hàm cùng kiểu với tham số hình thức trong hàm.

6.2. Chuyển từ kiểu lớp sang kiểu cơ sở. Nguyên tắc

Trong lớp có toán tử quy hồi kiểu: là toán tử tải bội, bổ sung định nghĩa toán tử kiểu cơ sở.

Khai báo

class class_name; {...

opearator type_name() ...

};

Định nghĩa

type_name class_name::type_name() { // nội dung toán tử}

Ví dụ 1 Chuyển từ kiểu VECTOR sang kiểu double

class VECTOR { private: int n; float *v; public: ... operator double() ... }; double VECTOR::double() { int i; double s=0;

for(i=0;i<n;s += v[i]*v[i], i++); return s;} Cụ thể ta có chơng trình sau: #include <iostream.h> #include <conio.h> const maxsize=10; class VECTOR { int n; float *v; public:

VECTOR() {v =new float[maxsize];} ~VECTOR() { delete v ;}

friend ostream& operator << (ostream&, VECTOR &); friend istream& operator >> (istream&, VECTOR &); operator double();

double VECTOR::operator double() { double s=0; int i;

for(i=0;i<n;s+=v[i]*v[i],i++); return s;}

ostream& operator << (ostream &dout, VECTOR &b) { int i;

for(i=0;i<b.n;i++) dout<<b.v[i]<<" "; dout<<endl;

return dout;}

istream& operator >> (istream &din, VECTOR &b) { int i,x; cout<<"n= "; cin>>x; b.n=x; for(i=0;i<b.n;i++) {cout<<"vector["<<i+1<<"] ="; din>>x; b.v[i]=x; } return din;} int main() { clrscr(); VECTOR a; cin>>a; cout<<a<<endl; double d=a; cout<<d<<endl; getch(); return 0; }

6.3. Chuyển từ kiểu cơ sở sang kiểu lớp.

Nguyên tắc

Trong lớp sử dụng constructor khởi tạo mà tham số của constructor là kiểu cơ sở.

Ví dụ 2: Chuyển từ char* sang kiểu String thông qua constructor String(char *S)

String::String(char *S) { len=strlen(S);

text = new char[len+1]; strcpy(text,S);

}

6.4. Chuyển từ kiểu lớp sang kiểu lớp

Nguyên tắc

Để chuyển từ lớp X sang lớp Y, trong lớp Y phải có constructor khởi tạo đặc biệt nhận đối là đối tợng của lớp X. class X {// nội dung lớp X }; class Y {... Y(X x); // chuyển từ lớp X  lớp Y ... }; void main() { X a; Y b=a; ... } Ví dụ 3 (chkieu2.cpp)

Cho lớp MH1: Mã hàng, Số lợng và Đơn giá

và lớp MH2: Mã hàng, Tổng giá trị (Tổng giá trị = Số lợng * Đơn giá)

#include <iostream.h> #include <conio.h> #include <stdio.h> class MH1 { int code; int num; float price; public: MH1() {code=num=price=0;}

MH1(int c, int n, float p) { code=c; num=n; price=p; } void get_data()

{ cout << " code = "; scanf("%d",&code); cout << " num = "; scanf("%d",&num); cout << " price = "; scanf("%f",&price); }

void show(void)

{ cout << " code: " << code << endl << " num : " << num << endl << " price : " << price << endl; }

int get_code() { return code ;} int get_num() { return num ;} float get_price() { return price ;}

operator double() { return num*price ;} }; class MH2 { int code; double total; public: MH2() {code=total=0;}

MH2(int c, double t) { code=c; total=t; } MH2(MH1 p)

{ code = p.get_code();

total= p.get_num()*p.get_price(); }

{ cout << " code: " << code << endl << " total : " << total << endl; } }; int main() { MH1 m(4,20,35.6); MH2 n=m; m.show(); n.show(); MH1 a[3]; int i; for(i=0;i<3;i++) a[i].get_data(); double s=0; for(i=0;i<3;i++) s+=a[i]; cout<<"s = " <<s<<endl; getche(); return 0;}

Để chuyển từ lớp X sang lớp Y có trờng hợp phải sử dụng cách 2:

- Trong lớp X phải có toán tử tải bội để mở rộng định nghĩa kiểuY. (Giống nh chuyển từ kiểu lớp sang kiểu cơ sở, nhng lúc này kiểu cơ sở chính là kiểu lớp Y đã định nghĩa.)

- Trong lớp Y đồng thời phải nhận hàm - toán tử này (của lớp X) là một hàm thân thiện - tức là hàm này của X là bạn của Y , nếu nó nhu cầu có sử dụng dữ liệu private của Y.

class Y; // khai báo trớc lớp Y //---

class X {...

operator Y(); //Y đợc coi là kiểu dữ liệu mới , chuyển từ // kiểu X sang kiểu Y

... }; class Y { ...

friend X::operator Y(); ...

};

X::operator Y()

{ // nội dung toán tử Y(), sử dụng đợc dữ liệu riêng của Y }

Ví dụ 4 (chkieu3.cpp)

Hãy chuyển từ lớp điểm (POINT) sang lớp số phức (COMPLEX) #include <iostream.h> #include <conio.h> #include <math.h> class COMPLEX; class POINT { int x,y; public:

POINT(int ox=0,int oy=0) { x=ox;y=oy;} operator COMPLEX();

};

class COMPLEX { float a,b; public:

COMPLEX(float a1=0,float b1=0) {a=a1;b=b1;} friend POINT::operator COMPLEX();

void display() {cout<<a<<" + i * "<<b<<endl;} };

POINT::operator COMPLEX() { COMPLEX C(x,y); return C; } int main() { clrscr(); POINT a(2,5); COMPLEX C; C=(COMPLEX)a; C.display(); getche(); return 0;}

Bài 4. Vài vấn đề về sử dụng từ khoá const

1. Sử dụng biến kiểu const

- Trong C và C++ các biến kiểu const không đợc phép thay đổi trong suốt quá trình chơng trình thực hiện.

- Phạm vi của các giá trị const trong C và C++ khác nhau:

+ Trong C, các giá trị const có phạm vi toàn cục, nghĩa là chúng có thể nhìn thấy ở các files khác, trừ khi nó đợc khai báo là static

Ví dụ 1: (trong C)

const int i=5; /* cac files khac deu nhan biet duoc i=5 */

static const int i = 5; /*cac file khac khong nhan biet duoc i=5 */

+ Trong C++, các giá trị const có phạm vi hoạt động cục bộ trong file chúng đợc khai báo. Để các file khác có thể nhìn thấy giá trị const của file hiện tại này thì nó phải đợc khai báo sau extern và các file đó phải nhắc lại khai báo extern const này, nhng không đợc viết lại giá trị hằng.

Ví dụ 2: (trong C++)

const int i=5; /* cac files khac khong nhan biet duoc i=5 */

extern const int i = 5;

/* cac files khac deu nhan biet duoc i = 5 neu các file khac do phai nhac lai const i, nhung khong chi ra gia tri cua i */

extern const int i;

- Điều lu ý: các biến kiểu const trong hàm trong C+ + luôn luôn có phạm vi hoạt động cục bộ trong hàm đó.

Một cách logic suy ra là nó không đợc khai báo extern trong hàm, nghĩa là mọi const là extern phải đợc khai báo trớc tất cả các hàm.

- Tiện lợi của khai báo const: Một biến khai báo kiểu const tiện lợi hơn so với việc sử dụng chỉ thị #define để khai báo hằng. Bởi vì khi khai báo một biến kiểu const thì ngoài việc giá trị của nó không thay đổi trong quá trình thực hiện chơng trình, trình biên dịch còn có thể thực hiện việc kiểm tra kiểu của biến và tuy thế vẫn không cấp phát bộ nhớ cho các biến này khi không cần thiết.

Ví dụ 3:

const int TRUE = 1 const int FALSE = 0

while (TRUE) {... int a = FALSE ; }

các biến TRUE và FALSE sẽ đợc thay thế bằng các giá trị khi biên dịch mà không cần phải phân bố bộ nhớ cho chúng.

- Để có thể phân bố bộ nhớ cho một biến int i kiểu const ta cần phải khai báo nh sau:

const int& i = 9;

Theo cách này, i đợc xem nh tất cả các biến tham chiếu khác chỉ có một điều khác biệt là giá trị của nó không đợc phép thay đổi khi thực hiện chơng trình.

2. Truyền tham số kiểu const cho hàm2.1. ý nghĩa chung của từ khoá const 2.1. ý nghĩa chung của từ khoá const

Trong C cũng nh trong C++, việc khai báo một biến kiểu const sẽ quy định biến đó chỉ đợc phép đọc trong ch- ơng trình. Một cách tổng quát, từ khoá const để quy định một tên gọi nào đó trong chơng trình nh: biến, tham số của hàm, hàm, và đối tợng không đợc phép thay đổi giá trị trong chơng trình.

2.2. Truyền tham số kiểu const cho hàm

Từ khoá là const đặt phía trớc khai báo đối của hàm có tác dụng quy định: trong thân hàm không đợc phép thay đổi giá trị của đối này. Và, do đó giá trị của tham số truyền cho hàm đợc giữ nguyên khi ra khỏi hàm.

Xét 2 trờng hợp sau đây:

- Trờng hợp truyền tham số cho hàm kiểu truyền theo giá trị: Nếu ta chỉ mong muốn giá trị của tham số truyền cho hàm đợc giữ nguyên khi ra khỏi hàm thì với ý định này việc sử dụng từ khoá const là không cần thiết vì bản thân kiểu truyền theo tham trị đã đảm bảo đợc điều đó. Vậy ta sẽ chỉ dùng từ khóa const cho tham số giá trị nếu ta còn muốn quy định trong thân hàm không đợc phép viết các lệnh làm thay đổi giá trị của các tham trị đó.

- Trờng hợp đối truyền cho hàm có kiểu con trỏ hoặc kiểu tham chiếu thì ta sẽ dùng từ khoá const đặt trớc khai báo đối nếu ta muốn đảm bảo chắc chắn hoặc nhấn mạnh rằng không đợc thay đổi giá trị của đối truyền cho hàm trong thân hàm (và do đó không thay đổi giá trị khi ra khỏi hàm). Ví dụ 1 #include <iostream.h> #include <conio.h> #include <stdio.h> #include <string.h> class ABC { private: char *name; public: void read_name(char *s); void get_name(const char *s); // s chi duoc du`ng, khong duoc sua void put_name();

void ABC::put_name()

{ cout <<"name: "<<name<<endl; } void ABC::read_name(char *s) { cout<<"Enter name: "; gets(s); int k=strlen(s);

name=new char[k]; strcpy(name,s);}

void ABC::get_name(const char *s) { int k=strlen(s); name=new char[k]; strcpy(name,s); } int main() { ABC a; char *s; a.read_name(s); a.put_name();

a.get_name("Nguyen Mai Lan"); a.put_name();

getche(); return 0;}

3. Đối tợng hằng của lớp

- Cũng nh các biến nói chung, biến là đối tợng cũng có thể khai báo là hằng và gọi là đối tợng hằng.

Một phần của tài liệu Lập trình hướng đối tượng (Trang 66)

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

(174 trang)
w