25/05/2018, 14:11

Hàm, hàm bạn

+ Phạm vi của hàm là toàn bộ chương trình, vì vậy hàm có thể được gọi tới từ bất kỳ chỗ nào. Như vây trong các phương thức có thể sử dụng hàm. + Đối của hàm có thể là các đối tượng, tuy nhiên có một hạn chế là trong thân hàm không cho ...

+ Phạm vi của hàm là toàn bộ chương trình, vì vậy hàm có thể được gọi tới từ bất kỳ chỗ nào. Như vây trong các phương thức có thể sử dụng hàm.

+ Đối của hàm có thể là các đối tượng, tuy nhiên có một hạn chế là trong thân hàm không cho phép truy nhập tới thuộc tính của các đối này. Ví dụ giả sử đã định nghĩa lớp:

class DIEM
    {
    private:
    double x,y; // Toa do cua diem
    public:
    void nhapsl()
    {
    cout << " Toa do x, y: " ;
    cin >> x >> y ;
    }
    void in()
    {
    cout << " x = " << x << " y = " << y;
    }
    };
    

Dùng lớp DIEM, ta xây dựng hàm tính độ dài của đoạn thẳng đi qua 2 điểm như sau:

double do_dai(DIEM d1, DIEM d2)
    {
    return sqrt(pow(d1.x-d2.x,2) + pow(d1.y-d2.y,2));
    }
    

Hàm này sẽ bị báo lỗi khi dịch, vì trong thân hàm không cho phép sử dụng các thuộc tính d1.x, d1.y, d2.x, d2.y của các đối tượng d1 và d2 thuộc lớp DIEM.

+ Phạm vi sử dụng của các phương thức (public) là toàn chương trình, vì vậy trong thân hàm có thể gọi tới các phương thức. Ví dụ giả sử đã định nghĩa lớp:

class DIEM
    {
    private:
    double x,y; // Toa do cua diem
    public:
    void nhapsl()
    {
    cout << " Toa do x, y: " ;
    cin >> x >> y ;
    }
    void in()
    {
    cout << " x = " << x << " y = " << y;
    }
    double do_dai(DIEM d2)
    {
    return sqrt(pow(x-d2.x,2) + pow(y-d2.y,2) );
    }
    } ;
    

Khi đó bằng cách dùng phương thức do_dai, ta có thể viết hàm tính diện tích tam giác có đỉnh là các đối tượng d1, d2, d3 của lớp DIEM như sau:

double dt_tg(DIEM d1, DIEM d2, DIEM d3)
    {
    double a,b,c,p,s;
    a=d1.do_dai(d2);
    b=d2.do_dai(d3);
    c=d3.do_dai(d1);
    p=(a+b+c)/2;
    return sqrt(p*(p-a)*(p-b)*(p-c));
    }
    

Bằng cách dùng hàm dt_tg, có thể tổ chức lại chương trình tìm tam giác có diện tích lớn nhất (ở mục trên) một cách đơn giản hơn( bỏ đi lớp TAM_GIAC) như ví dụ sau.

#include <conio.h>

#include <iostream.h>

#include <math.h>

class DIEM

{

private:

double x,y; // Toa do cua diem

public:

void nhapsl()

{

cout << " Toa do x, y: " ;

cin >> x >> y ;

}

void in()

{

cout << " x = " << x << " y = " << y;

}

double do_dai(DIEM d2)

{

return sqrt(pow(x-d2.x,2) + pow(y-d2.y,2) );

}

} ;

double dt_tg(DIEM d1, DIEM d2, DIEM d3)

{

double a,b,c,p,s;

a=d1.do_dai(d2);

b=d2.do_dai(d3);

c=d3.do_dai(d1);

p=(a+b+c)/2;

return sqrt(p*(p-a)*(p-b)*(p-c));

}

void main()

{

DIEM d[50];

int n, i,j,k,imax,jmax,kmax ;

clrscr();

cout << "
 So diem= ";

cin >> n;

for (i=1; i<=n; ++i)

{

cout << "
Nhap diem " << i << " - " ;

d[i].nhapsl();

}

imax=1; jmax=2; kmax=3;

for (i=1;i<=n-2;++i)

for (j=i+1;j<=n-1;++j)

for (k=j+1;k<=n;++k)

if (dt_tg(d[i],d[j],d[k]) > dt_tg(d[imax],d[jmax],d[kmax]))

{

imax = i ;

jmax = j;

kmax = k;

}

cout << "

Tam giac co dien tich lon nhat: " ;

cout << "
Dinh 1 - "; d[imax].in();

cout << "
Dinh 2 - "; d[jmax].in();

cout << "
Dinh 3 - "; d[kmax].in();

cout << "
Dien tich = " << dt_tg(d[imax],d[jmax],d[kmax]) ;

getch();

}

Nhận xét: Chương trình trên làm việc trên mảng d kiểu DIEM. Bây giờ nếu ta dùng mảng ngoài thì từ số thứ tự sẽ suy ra phần tử của mảng. Như vây hàm

double dt_tg(DIEM d1, DIEM d2, DIEM d3);

có 3 đối kiểu DIEM có thể thay bằng hàm có 3 đối nguyên:

double dt_tg(int i, int j, int k);

để tính diện tích tam giác có đỉnh là d[i], d[j] và d[k] . Ý tưởng này được thể hiện trong ví dụ sau.

Chương trình dùng mảng đối tượng ngoài.

Khai báo mảng đối tượng phải đặt sau định nghĩa kiểu đối tượng (định nghĩa lớp).

#include <conio.h>

#include <iostream.h>

#include <math.h>

double dt_tg(int i, int j, int k); // Khai báo hàm dt_tg

class DIEM

{

private:

double x,y; // Toa do cua diem

public:

void nhapsl();

void in();

double do_dai(DIEM d2);

} ;

// Chú ý: Khai báo mảng kiểu DIEM phải đặt sau định nghĩa 

// lớp DIEM

DIEM d[50];

void DIEM::nhapsl()

{

cout << " Toa do x, y: " ;

cin >> x >> y ;

}

void DIEM::in()

{

cout << " x = " << x << " y = " << y;

}

double DIEM::do_dai(DIEM d2)

{

return sqrt(pow(x-d2.x,2) + pow(y-d2.y,2) );

}

double dt_tg(int i, int j, int k)

{

double a,b,c,p,s;

a=d[i].do_dai(d[j]);

b=d[j].do_dai(d[k]);

c=d[k].do_dai(d[i]);

p=(a+b+c)/2;

return sqrt(p*(p-a)*(p-b)*(p-c));

}

void main()

{

int n, i,j,k,imax,jmax,kmax ;

clrscr();

cout << "
 So diem= ";

cin >> n;

for (i=1; i<=n; ++i)

{

cout << "
Nhap diem " << i << " - " ;

d[i].nhapsl();

}

imax=1; jmax=2; kmax=3;

for (i=1;i<=n-2;++i)

for (j=i+1;j<=n-1;++j)

for (k=j+1;k<=n;++k)

if (dt_tg(i,j,k) > dt_tg(imax,jmax,kmax))

{

imax = i ;

jmax = j;

kmax = k;

}

cout << "

Tam giac co dien tich lon nhat: " ;

cout << "
Dinh 1 - "; d[imax].in();

cout << "
Dinh 2 - "; d[jmax].in();

cout << "
Dinh 3 - "; d[kmax].in();

cout << "
Dien tich = " << dt_tg(imax,jmax,kmax);

getch();

}

Để một hàm trở thành bạn của một lớp, có 2 cách viết:

Cách 1: Dùng từ khoá friend để khai báo hàm trong lớp và xây dựng hàm bên ngoài như các hàm thông thường (không dùng từ khoá friend). Mẫu viết như sau:

class A
    {
    private:
    // Khai báo các thuộc tính
    public:
    ...
    // Khai báo các hàm bạn của lớp A
    friend void f1(...);
    friend double f2(...);
    friend A f3(...) ;
    ...
    } ;
    // Xây dựng các hàm f1, f2, f3
    void f1(...)
    {
    ...
    }
    double f2(...)
    {
    ...
    }
    A f3(...)
    {
    ...
    }
    

Cách 2: Dùng từ khoá friend để xây dựng hàm trong định nghĩa lớp. Mẫu viết như sau:

class A
    {
    private:
    // Khai báo các thuộc tính
    public:
    ...
    // Xây dựng các hàm bạn của lớp A
    void f1(...)
    {
    ...
    }
    double f2(...)
    {
    ...
    }
    A f3(...)
    {
    ...
    }
    ...
    } ;
    

Tính chất của hàm bạn

Trong thân hàm bạn của một lớp có thể truy nhập tới các thuộc tính của các đối tượng thuộc lớp này. Đây là sự khác nhau duy nhất giữa hàm bạn và hàm thông thường. Chú ý rằng hàm bạn không phải là phương thức của lớp. Phương thức có một đối ẩn (ứng với con trỏ this) và lời gọi của phương thức phải gắn với một đối tượng nào đó (địa chỉ đối tượng này được truyền cho con trỏ this). Lời gọi của hàm bạn giống như lời gọi của hàm thông thường.

Ví dụ sau sẽ so sánh phương thức, hàm bạn và hàm tự do (hàm thông thường). Xét lớp SP (số phức). Hãy so sánh 3 phương án để thực hiện việc cộng 2 số phức:

Phương án 1: Dùng phương thức

class SP
    {
    private:
    double a; // Phần thực
    double b; // Phần ảo
    public:
    SP cong(SP u2)
    {
    SP u:
    u.a = this->a + u2.a ;
    u.b = this->b + u2.b ;
    return u;
    }
    } ;
    

Cách dùng

SP u, u1, u2;
    u = u1.cong(u2);
    

Phương án 2: Dùng hàm bạn

class SP
    {
    private:
    double a; // Phần thực
    double b; // 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;
    }
    };
    

Cách dùng

SP u, u1, u2;
    u = cong(u1, u2);
    

Phương án 3: Dùng hàm tự do

class SP
    {
    private:
    double a; // Phần thực
    double b; // Phần ảo
    public:
    ...
    } ;
    SP cong(SP u1, SP u2)
    {
    SP u:
    u.a = u1.a + u2.a ;
    u.b = u1.b + u2.b ;
    return u;
    }
    

Phương án này không được chấp nhận, Trình biên dịch sẽ báo lỗi vì trong thân hàm không được quyền truy xuất đến các thuộc tính riêng (private) a, b của các đối tượng u, u1 và u2 thuộc lớp SP.

Một hàm có thể là bạn của nhiều lớp được không?

Câu trả lời là được. Khi một hàm là bạn của nhiều lớp, thì nó có quyền truy nhập tới tất cả các thuộc tính của các đối tượng trong các lớp này. Để làm cho hàm f trở thành bạn của các lớp A, B và C ta sử dụng mẫu viết sau:

class B; // Khai báo trước lớp A 
    class B; // Khai báo trước lớp B 
    class C; // Khai báo trước lớp C 
        // Định nghĩa lớp A
        class A
        {
        // Khai báo f là bạn của A
        friend void f(...) ;
        } ;
        // Định nghĩa lớp B
        class B
        {
        // Khai báo f là bạn của B
        friend void f(...) ;
        } ;
        // Định nghĩa lớp C
        class C
        {
        // Khai báo f là bạn của C
        friend void f(...) ;
        } ;
        // Xây dụng hàm f
        void f(...)
        {
        ...
        }

Chương trình sau đây minh hoạ cách dùng hàm bạn (bạn của một lớp và bạn của nhiều lớp). Chương trình đưa vào 2 lớp VT (véc tơ), MT (ma trận) và 3 hàm bạn để thực hiện các thao tác trên 2 lớp này:

// Hàm bạn với lớp VT dùng để in một véc tơ
    friend void in(const VT &x);
    // Hàm bạn với lớp MT dùng để in một ma trận
    friend void in(const MT &a);
    // Hàm bạn với cả 2 lớp MT và VT dùng để nhân ma trận với véc tơ
    friend VT tich(const MT &a,const VT &x);
    

Nội dung chương trình là nhập một ma trận vuông cấp n và một véc tơ cấp n, sau đó thực hiện phép nhân ma trận với véc tơ vừa nhập.

// Chương trình CT3_09.CPP
    #include <conio.h>
    #include <iostream.h>
    #include <math.h>
    class VT;
    class MT ;
    class VT
    {
    private:
    int n;
    double x[20]; // Toa do cua diem
    public:
    void nhapsl();
    friend void in(const VT &x);
    friend VT tich(const MT &a,const VT &x) ;
    } ;
    class MT
    {
    private:
    int n;
    double a[20][20];
    public:
    friend VT tich(const MT &a,const VT &x);
    friend void in(const MT &a);
    void nhapsl();
    } ;
    void VT::nhapsl()
    {
    cout << "
 Cap vec to = ";
    cin >> n ;
    for (int i=1; i<=n ; ++i)
    {
    cout << "
Phan tu thu " << i << " = " ;
    cin >> x[i];
    }
    }
    void MT::nhapsl()
    {
    cout << "
 Cap ma tran = ";
    cin >> n ;
    for (int i=1; i<=n ; ++i)
    for (int j=1; j<=n; ++j)
    {
    cout << "
Phan tu thu hang "<< i << " cot " << j << "=" ;
    cin >> a[i][j];
    }
    }
    VT tich(const MT &a,const VT &x)
    {
    VT y;
    int n=a.n;
    if (n!=x.n)
    return x;
    y.n = n;
    for (int i=1; i<=n; ++i)
    {
    y.x[i]=0;
    for (int j=1; j<=n; ++j)
    y.x[i] += a.a[i][j]*x.x[j];
    }
    return y;
    }
    void in(const VT &x)
    {
    cout << "
";
    for (int i=1; i<=x.n; ++i)
    cout << x.x[i] << " ";
    }
    void in(const MT &a)
    {
    for (int i=1; i<=a.n; ++i)
    {
    cout << "
" ;
    for (int j=1; j<=a.n; ++j)
    cout << a.a[i][j] << " ";
    }
    }
    void main()
    {
    MT a; VT x,y;
    clrscr();
    a.nhapsl();
    x.nhapsl();
    y=tich(a,x);
    clrscr();
    cout << "
Ma tran A:";
    in(a);
    cout << "

Vec to x: " ;
    in(x);
    cout << "

Vec y = Ax: " ;
    in(y);
    getch();
    }
    
0