25/05/2018, 12:10

Định nghĩa chồng các toán tử

Trong C và C++ có khá nhiều các phép toán dùng để thực hiện các thao tác trên các kiểu dữ liệu chuẩn. Ví dụ các phép số học: + - * / áp dụng cho các kiểu dữ liệu nguyên, thực. Phép lấy phần dư % áp dụng đối với kiểu nguyên. ...

Trong C và C++ có khá nhiều các phép toán dùng để thực hiện các thao tác trên các kiểu dữ liệu chuẩn. Ví dụ các phép số học: + - * / áp dụng cho các kiểu dữ liệu nguyên, thực. Phép lấy phần dư % áp dụng đối với kiểu nguyên.

Việc thực hiện các phép toán trên các đối tượng tự định nghĩa (như mảng, cấu trúc) là nhu cầu bắt buộc của thực tế. Chẳng hạn cần thực hiện các phép số học trên số phức, trên phân số, trên đa thức, trên véc tơ, trên ma trận. Để đáp ứng yêu cầu này, ta sử dụng các hàm trong C. Ví dụ sau đây là một chương trình C gồm các hàm nhập phân số, in phân số và thực hiện các phép cộng trừ nhân chia phân số. Chương trình sẽ nhập 5 phân số: p, q, z, u, v và tính phân số s theo công thức:

s = (p - q*z)/(u + v)
    #include <conio.h>
    #include <stdio.h>
    #include <math.h>
    typedef struct
    {
    int a,b;
    } PS;
    void nhap(PS *p);
    void in(PS p);
    int uscln(int x, int y);
    PS rutgon(PS p);
    PS cong(PS p1, PS p2);
    PS tru(PS p1, PS p2);
    PS nhan(PS p1, PS p2);
    PS chia(PS p1, PS p2);
    void nhap(PS *p)
    {
    int t, m;
    printf("
Tu va mau: ");
    scanf("%d%d", &t, &m);
    p->a = t; p->b = m;
    }
    void in(PS p)
    {
    printf(" %d/%d",p.a,p.b);
    }
    int uscln(int x, int y)
    {
    x=abs(x); y=abs(y);
    if (x*y==0) return 1;
    while (x!=y)
    if (x>y) x-=y;
    else y-=x;
    return x;
    }
    PS rutgon(PS p)
    {
    PS q;
    int x;
    x=uscln(p.a,p.b);
    q.a = p.a / x ;
    q.b = p.b / x ;
    return q;
    }
    PS cong(PS p1, PS p2)
    {
    PS q;
    q.a = p1.a*p2.b + p2.a*p1.b;
    q.b = p1.b * p2.b ;
    return rutgon(q);
    }
    PS tru(PS p1, PS p2)
    {
    PS q;
    q.a = p1.a*p2.b - p2.a*p1.b;
    q.b = p1.b * p2.b ;
    return rutgon(q);
    }
    PS nhan(PS p1, PS p2)
    {
    PS q;
    q.a = p1.a * p2.a ;
    q.b = p1.b * p2.b ;
    return rutgon(q);
    }
    PS chia(PS p1, PS p2)
    {
    PS q;
    q.a = p1.a * p2.b ;
    q.b = p1.b * p2.a ;
    return rutgon(q);
    }
    void main()
    {
    PS p, q, z, u, v ;
    PS tu,mau, s;
    printf("
 Nhap phan so p: "); nhap(&p);
    printf("
 Nhap phan so q: ");nhap(&q);
    printf("
 Nhap phan so z: ");nhap(&z);
    printf("
 Nhap phan so u: ");nhap(&u);
    printf("
 Nhap phan so v: ");nhap(&v);
    tu = nhan(q,z);
    tu = tru(p,tu) ;
    mau = cong(u,v) ;
    s = chia(tu,mau);
    printf("
 Phan so s = "); in(s);
    getch();
    }
    

Việc sử dụng các hàm để thực hiện các phép tính không được tự nhiên và tỏ ra dài dòng. Ví dụ để thực hiện một công thức

s = (p - q*z)/(u + v)

phải dùng 2 biến trung gian và 4 lời gọi hàm. Câu hỏi đặt ra là có cách nào để chỉ cần viết đúng công thức toán học, mà vẫn nhận được kết quả mong muốn hay không?

Trong C++ có thể đáp ứng được mong muốn này bằng cách sử dụng các phép toán chuẩn của nó cho các kiểu dữ liệu tự định nghĩa (mảng, cấu trúc, ...). Nói cách khác C++ cho phép dùng các phép toán để định nghĩa các hàm, mà ta thường gọi là định nghĩa chồng các toán tử (hay còn gọi: Sự tải bội các toán tử).

Tên hàm toán tử:

Gồm từ khoá operator và tên phép toán, ví dụ:

operator+ (định nghĩa chồng phép +)

operator- (định nghĩa chồng phép -)

Các đối của hàm toán tử:

a. Với các phép toán có 2 toán hạng, thì hàm toán tử cần có 2 đối. Đối thứ nhất ứng với toán hạng thứ nhất, đối thứ hai ứng với toán hạng thứ hai. Do vậy, với các phép toán không giao hoán (như phép-) thì thứ tự đối là rất quan trọng.

Các hàm toán tử cộng , trừ phân số được khai báo như sau:

struct PS

{

int a; // Tử số

int b; // Mẫu số

} ; 

PS operator+(PS p1, PS p2); // p1 + p2

PS operator-(PS p1, PS p2); // p1 - p2

PS operator*(PS p1, PS p2); // p1 * p2

PS operator/(PS p1, PS p2); // p1 / p2

b. Với các phép toán có một toán hạng, thì hàm toán tử có một đối. Ví dụ hàm toán tử đổi dấu ma trận (đổi dấu tất cả các phần tử của ma trận) được khai báo như sau:

struct MT
    {
    double a[20][20] ; // Mảng chứa các phần tử ma trận
    int m ; // Số hàng ma trận
    int n ; // Số cột ma trân
    } ;
    MT operator-(MT x) ;
    

Thân của hàm toán tử:

Viết như thân của hàm thông thường. Ví dụ hàm đổi dấu ma trận có thể được định nghĩa như sau:

struct MT
    {
    double a[20][20] ; // Mảng chứa các phần tử ma trận
    int m ; // Số hàng ma trận
    int n ; // Số cột ma trân
    } ;
    MT operator-(MT x) 
    {
    MT y;
    for (int i=1; i<= m ;++i)
    for (int j=1; j<= n ;++j)
    y[i][j] = - x[i][j] ;
    return y;
    }
    

Có 2 cách dùng:

Cách 1: Dùng như một hàm thông thường bằng cách viết lời gọi.

PS p, q, u, v ;

u = operator+(p, q) ; // u = p + q

v = operator-(p, q) ; // v = p - q

Cách 2: Dùng như phép toán của C++ .

PS p, q, u, v ;

u = p + q ; // u = p + q

v = p - q ; // v = p - q
Khi dùng các hàm toán tử như phép toán của C++ ta có thể kết hợp nhiều phép toán để viết các công thức phức tạp. Cũng cho phép dùng dấu ngoặc tròn để quy định thứ tự thực hiện các phép tính. Thứ tự ưu tiên của các phép tính vẫn tuân theo các quy tắc ban đầu của C++ . Chẳng hạn các phép * và / có thứ ưu tiên cao hơn so với các phép + và -

PS p, q, u, v, s1, s2 ;

s1 = p*q - u/v ; // s1 = (p*q) 

s2 = (p - q)/(u + v) ; // s2 = (p - q)/(u + v) 

Trong ví dụ này ngoài việc sử dụng các hàm toán tử để thực hiện 4 phép tính trên phân số, còn định nghĩa chồng các phép toán << và >> để xuất và nhập phân số (xem chi tiết trong chương 7).

Hàm operator<< có 2 đối kiểu ostream& và PS (Phân số). Hàm trả về giá trị kiểu ostream&. Hàm được khai báo như sau:

ostream& operator<< (ostream& os, PS p);

Tượng tự hàm operator>> được khai báo như sau:

istream& operator>> (istream& is,PS &p);

Dưới đây sẽ chỉ ra cách xây dựng và sử dụng các hàm toán tử. Chúng ta cũng sẽ thấy việc sử dụng các hàm toán tử rất tự nhiên, ngắn gọn và tiện lợi.

Chương trình dưới đây có nội dung như chương trình trong §6.2, nhưng thay các hàm bằng các hàm toán tử.

#include <conio.h>

#include <iostream.h>

#include <math.h>

typedef struct

{

int a,b;

} PS;

ostream& operator<< (ostream& os, PS p);

istream& operator>> (istream& is,PS &p);

int uscln(int x, int y);

PS rutgon(PS p);

PS operator+(PS p1, PS p2);

PS operator-(PS p1, PS p2);

PS operator*(PS p1, PS p2);

PS operator/(PS p1, PS p2);

ostream& operator<< (ostream& os, PS p)

{

os << p.a << '/' << p.b ;

return os;

}

istream& operator>> (istream& is,PS &p)

{

cout << "Nhap tu va mau: " ;

is >> p.a >> p.b ;

return is;

}

int uscln(int x, int y)

{

x=abs(x); y=abs(y);

if (x*y==0) return 1;

while (x!=y)

if (x>y) x-=y;

else y-=x;

return x;

}

PS rutgon(PS p)

{

PS q;

int x;

x=uscln(p.a,p.b);

q.a = p.a / x ;

q.b = p.b / x ;

return q;

}

PS operator+(PS p1, PS p2)

{

PS q;

q.a = p1.a*p2.b + p2.a*p1.b;

q.b = p1.b * p2.b ;

return rutgon(q);

}

PS operator-(PS p1, PS p2)

{

PS q;

q.a = p1.a*p2.b - p2.a*p1.b;

q.b = p1.b * p2.b ;

return rutgon(q);

}

PS operator*(PS p1, PS p2)

{

PS q;

q.a = p1.a * p2.a ;

q.b = p1.b * p2.b ;

return rutgon(q);

}

PS operator/(PS p1, PS p2)

{

PS q;

q.a = p1.a * p2.b ;

q.b = p1.b * p2.a ;

return rutgon(q);

}

void main()

{

PS p, q, z, u, v ;

PS s;

cout <<"
Nhap cac PS p, q, z, u, v:
 " ;

cin >> p >> q >> z >> u >> v ;

s = (p - q*z) / (u + v) ;

cout << "
 Phan so s = " << s;

getch();

}

Chương trình đưa vào các hàm toán tử:

operator-  có một đối dùng để đảo dấu một đa thức

operator+  có 2 đối dùng để cộng 2 đa thức

operator-  có 2 đối dùng để trừ 2 đa thức

operator*  có 2 đối dùng để nhân 2 đa thức

operator^  có 2 đối dùng để tính giá đa thức tại x

operator<<  có 2 đối dùng để in đa thức

operator>>  có 2 đối dùng để nhập đa thức

Chương trình sẽ nhập 4 đa thức: p, q, r, s. Sau đó tính đa thức:

f = -(p+q)*(r-s)

Cuối cùng tính giá trị f(x), với x là một số thực nhập từ bàn phím.

#include <conio.h>

#include <iostream.h>

#include <math.h>

struct DT

{

double a[20]; // Mang chua cac he so da thuc a0, a1,...

int n ; // Bac da thuc

} ;

ostream& operator<< (ostream& os, DT d);

istream& operator>> (istream& is,DT &d);

DT operator-(const DT& d);

DT operator+(DT d1, DT d2);

DT operator-(DT d1, DT d2);

DT operator*(DT d1, DT d2);

double operator^(DT d, double x); // Tinh gia tri da thuc

ostream& operator<< (ostream& os, DT d)

{

os << " - Cac he so (tu ao): " ;

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;

cout << "Nhap cac he so da thuc:
" ;

for (int i=0 ; i<= d.n ; ++i)

{

cout << "He so bac " << i << " = " ;

is >> d.a[i] ;

}

return is;

}

DT operator-(const DT& d)

{

DT p;

p.n = d.n;

for (int i=0 ; i<=d.n ; ++i)

p.a[i] = -d.a[i];

return p;

}

DT operator+(DT d1, DT d2)

{

DT d;

int k,i;

k = d1.n > d2.n ? d1.n : d2.n ;

for (i=0; i<=k ; ++i)

if (i<=d1.n && i<=d2.n)

d.a[i] = d1.a[i] + d2.a[i];

else if (i<=d1.n)

d.a[i] = d1.a[i];

else

d.a[i] = d2.a[i];

i=k;

while (i>0 && d.a[i]==0.0) --i;

d.n = i;

return d ;

}

DT operator-(DT d1, DT d2)

{

return (d1 + (-d2));

}

DT operator*(DT d1, DT d2)

{

DT d;

int k, i, j;

k = d.n = d1.n + d2.n ;

for (i=0; i<=k; ++i) d.a[i] = 0;

for (i=0 ; i<= d1.n ; ++i)

for (j=0 ; j<= d2.n ; ++j)

d.a[i+j] += d1.a[i]*d2.a[j] ;

return d;

}

double operator^(DT d, double x)

{

double s=0.0 , t=1.0;

for (int i=0 ; i<= d.n ; ++i)

{

s += d.a[i]*t;

t *= x;

}

return s;

}

void main()

{

DT p,q,r,s,f;

double x,g;

clrscr();

cout <<"
Nhap da thuc P " ; cin >> p;

cout <<"
Nhap da thuc Q " ; cin >> q;

cout <<"
Nhap da thuc R " ; cin >> r;

cout <<"
Nhap da thuc S " ; cin >> s;

cout << "
Nhap so thuc x: " ; cin >> x;

f = -(p+q)*(r-s);

g = f^x;

cout << "
Da thuc f " << f ;

cout << "
 x = " << x;

cout << "
f(x) = " << g;

getch();

}
0