Tác giả phạm hồng thái bài giảng ngôn ngữ LẬp trình c/C++



tải về 1.98 Mb.
trang27/55
Chuyển đổi dữ liệu07.07.2016
Kích1.98 Mb.
1   ...   23   24   25   26   27   28   29   30   ...   55

Con trỏ hàm


Một hàm (tập hợp các lệnh) cũng giống như dữ liệu: có tên gọi , có địa chỉ lưu trong bộ nhớ và có thể truy nhập đến hàm thông qua tên gọi hoặc địa chỉ của nó. Để truy nhập (gọi hàm) thông qua địa chỉ chúng ta phải khai báo một con trỏ chứa địa chỉ này và sau đó gọi hàm bằng cách gọi tên con trỏ.

Khai báo


(*tên biến hàm)(d/s tham đối);

(*tên biến hàm)(d/s tham đối) = ;

Ta thấy cách khai báo con trỏ hàm cũng tương tự khai báo con trỏ biến (chỉ cần đặt dấu * trước tên), ngoài ra còn phải bao *tên hàm giữa cặp dấu ngoặc (). Ví dụ:

- float (*f)(int); // khai báo con trỏ hàm có tên là f trỏ đến hàm

// có một tham đối kiểu int và cho giá trị kiểu float.

- void (*f)(float, int); // con trỏ trỏ đến hàm với cặp đối (float, int).

hoặc phức tạp hơn:

- char* (*m[10])(int, char) // khai báo một mảng 10 con trỏ hàm trỏ đến

// các hàm có cặp tham đối (int, char), giá trị trả

// lại của các hàm này là xâu kí tự.

Chú ý: phân biệt giữa 2 khai báo: float (*f)(int) và float *f(int). Cách khai báo trước là khai báo con trỏ hàm có tên là f. Cách khai báo sau có thể viết lại thành float* f(int) là khai báo hàm f với giá trị trả lại là một con trỏ float.

Khởi tạo


Một con trỏ hàm cũng giống như các con trỏ, được phép khởi tạo trong khi khai báo hoặc gán với một địa chỉ hàm cụ thể sau khi khai báo. Cũng giống như kiểu dữ liệu mảng, tên hàm chính là một hằng địa chỉ trỏ đến bản thân nó. Do vậy cú pháp của khởi tạo cũng như phép gán là như sau:

biến con trỏ hàm = tên hàm;

trong đó f và tên hàm được trỏ phải giống nhau về kiểu trả lại và danh sách đối. Nói cách khác với mục đích sử dụng con trỏ f trỏ đến hàm (lớp hàm) nào đó thì f phải được khai báo với kiểu trả lại và danh sách đối giống như hàm đó. Ví dụ:

float luythua(float, int); // khai báo hàm luỹ thừa

float (*f)(float, int); // khai báo con trỏ f tương thích với hàm luythua

f = luythua; // cho f trỏ đến hàm luỹ thừa

Sử dụng con trỏ hàm


Để sử dụng con trỏ hàm ta phải gán nó với tên hàm cụ thể và sau đó bất kỳ nơi nào được phép xuất hiện tên hàm thì ta đều có thể thay nó bằng tên con trỏ. Ví dụ như các thao tác gọi hàm, đưa hàm vào làm tham đối hình thức cho một hàm khác … Sau đây là các ví dụ minh hoạ.

: Dùng tên con trỏ để gọi hàm

float bphuong(float x) // hàm trả lại x2

{

return x*x;



}

void main()

{

float (*f)(float);



f = bphuong;

cout << "Bình phương của 3.5 là " << f(3.5) ;

}

: Dùng hàm làm tham đối. Tham đối của hàm ngoài các kiểu dữ liệu đã biết còn có thể là một hàm. Điều này có tác dụng rất lớn trong các bài toán tính toán trên những đối tượng là hàm toán học như tìm nghiệm, tính tích phân của hàm trên một đoạn ... Hàm đóng vai trò tham đối sẽ được khai báo dưới dạng con trỏ hàm. Ví dụ sau đây trình bày hàm tìm nghiệm xấp xỉ của một hàm liên tục và đổi dấu trên đoạn [a, b]. Để hàm tìm nghiệm này sử dụng được trên nhiều hàm toán học khác nhau, trong hàm sẽ chứa một biến con trỏ hàm và hai cận a, b, cụ thể bằng khai báo float timnghiem(float (*f)(float), float a, float b). Trong lời gọi hàm f sẽ được thay thế bằng tên hàm cụ thể cần tìm nghiệm.



#define EPS 1.0e-6

float timnghiem(float (*f)(float), float a, float b);

float emu(float);

float loga(float);

void main()

{

clrscr();



cout << "Nghiệm của e mũ x - 2 trên đoạn [0,1] = ";

cout << timnghiem(emu,0,1));

cout << "Nghiệm của loga(x) - 1 trên đoạn [2,3] = ";

cout << timnghiem(loga,2,3));

getch();

}

float timnghiem(float (*f)(float), float a, float b)



{

float c = (a+b)/2;

while (fabs(a-b)>EPS && f(c)!=0)

{

if (f(a)*f(c)>0) a = c ; else b = c;



c = (a+b)/2;

}

return c;



}

float emux(float x) { return (exp(x)-2); }

float logx(float x) { return (log(x)-1); }

Mảng con trỏ hàm


Tương tự như biến bình thường các con trỏ hàm giống nhau có thể được gộp lại vào trong một mảng, trong khai báo ta chỉ cần thêm [n] vào sau tên mảng với n là số lượng tối đa các con trỏ. Ví dụ sau minh hoạ cách sử dụng này. Trong ví dụ chúng ta xây dựng 4 hàm cộng, trừ, nhân, chia 2 số thực. Các hàm này giống nhau về kiểu, số lượng đối, … Chúng ta có thể sử dụng 4 con trỏ hàm riêng biệt để trỏ đến các hàm này hoặc cũng có thể dùng mảng 4 con trỏ để trỏ đến các hàm này. Chương trình sẽ in ra kết quả cộng, trừ, nhân, chia của 2 số nhập vào từ bàn phím.

:

void cong(int a, int b) { cout << a << " + " << b << " = " << a+b ; }



void tru(int a, int b) { cout << a << " - " << b << " = " << a-b ; }

void nhan(int a, int b) { cout << a << " x " << b << " = " << a*b ; }

void chia(int a, int b) { cout << a << ": " << b << " = " << a/b ; }

main()


{

clrscr();

void (*f[4])(int, int) = {cong, tru, nhan, chia}; // khai báo, khởi tạo 4 con trỏ

int m, n;

cout "Nhập m, n " ; cin >> m >> n ;

for (int i=0; i<4; i++) f[i](m,n);

getch();

}

ĐỆ QUI

Khái niệm đệ qui


Một hàm gọi đến hàm khác là bình thường, nhưng nếu hàm lại gọi đến chính nó thì ta gọi hàm là đệ qui. Khi thực hiện một hàm đệ qui, hàm sẽ phải chạy rất nhiều lần, trong mỗi lần chạy chương trình sẽ tạo nên một tập biến cục bộ mới trên ngăn xếp (các đối, các biến riêng khai báo trong hàm) độc lập với lần chạy trước đó, từ đó dễ gây tràn ngăn xếp. Vì vậy đối với những bài toán có thể giải được bằng phương pháp lặp thì không nên dùng đệ qui.

Để minh hoạ ta hãy xét hàm tính n giai thừa. Để tính n! ta có thể dùng phương pháp lặp như sau:

main()

{

int n; doule kq = 1;



cout << "n = " ; cin >> n;

for (int i=1; i<=n; i++) kq *= i;

cout << n << "! = " << kq;

}

Mặt khác, n! giai thừa cũng được tính thông qua (n-1)! bởi công thức truy hồi



n! = 1 nếu n = 0

n! = (n-1)!n nếu n > 0

do đó ta có thể xây dựng hàm đệ qui tính n! như sau:

double gt(int n)

{

if (n==0) return 1;



else return gt(n-1)*n;

}
main()

{

int n;


cout << "n = " ; cin >> n;

cout << gt(n);

}

Trong hàm main() giả sử ta nhập 3 cho n, khi đó để thực hiện câu lệnh cout << gt(3) để in 3! đầu tiên chương trình sẽ gọi chạy hàm gt(3). Do 3 ¹ 0 nên hàm gt(3) sẽ trả lại giá trị gt(2)*3, tức lại gọi hàm gt với tham đối thực sự ở bước này là n = 2. Tương tự gt(2) = gt(1)*2 và gt(1) = gt(0)*1. Khi thực hiện gt(0) ta có đối n = 0 nên hàm trả lại giá trị 1, từ đó gt(1) = 1*1 = 1 và suy ngược trở lại ta có gt(2) = gt(1)*2 = 1*2 = 2, gt(3) = gt(2)*3 = 2*3 = 6, chương trình in ra kết quả 6.



Từ ví dụ trên ta thấy hàm đệ qui có đặc điểm:

Chương trình viết rất gọn,

Việc thực hiện gọi đi gọi lại hàm rất nhiều lần phụ thuộc vào độ lớn của đầu vào. Chẳng hạn trong ví dụ trên hàm được gọi n lần, mỗi lần như vậy chương trình sẽ mất thời gian để lưu giữ các thông tin của hàm gọi trước khi chuyển điều khiển đến thực hiện hàm được gọi. Mặt khác các thông tin này được lưu trữ nhiều lần trong ngăn xếp sẽ dẫn đến tràn ngăn xếp nếu n lớn.

Tuy nhiên, đệ qui là cách viết rất gọn, dễ viết và đọc chương trình, mặt khác có nhiều bài toán hầu như tìm một thuật toán lặp cho nó là rất khó trong khi viết theo thuật toán đệ qui thì lại rất dễ dàng.


1   ...   23   24   25   26   27   28   29   30   ...   55


Cơ sở dữ liệu được bảo vệ bởi bản quyền ©hocday.com 2016
được sử dụng cho việc quản lý

    Quê hương