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.
trang24/55
Chuyển đổi dữ liệu07.07.2016
Kích1.98 Mb.
1   ...   20   21   22   23   24   25   26   27   ...   55

Lời gọi và sử dụng hàm


Lời gọi hàm được phép xuất hiện trong bất kỳ biểu thức, câu lệnh của hàm khác … Nếu lời gọi hàm lại nằm trong chính bản thân hàm đó thì ta gọi là đệ quy. Để gọi hàm ta chỉ cần viết tên hàm và danh sách các giá trị cụ thể truyền cho các đối đặt trong cặp dấu ngoặc tròn ().

tên hàm(danh sách tham đối thực sự) ;

Danh sách tham đối thực sự còn gọi là danh sách giá trị gồm các giá trị cụ thể để gán lần lượt cho các đối hình thức của hàm. Khi hàm được gọi thực hiện thì tất cả những vị trí xuất hiện của đối hình thức sẽ được gán cho giá trị cụ thể của đối thực sự tương ứng trong danh sách, sau đó hàm tiến hành thực hiện các câu lệnh của hàm (để tính kết quả).

Danh sách tham đối thực sự truyền cho tham đối hình thức có số lượng bằng với số lượng đối trong hàm và được truyền cho đối theo thứ tự tương ứng. Các tham đối thực sự có thể là các hằng, các biến hoặc biểu thức. Biến trong giá trị có thể trùng với tên đối. Ví dụ ta có hàm in n lần kí tự c với tên hàm inkitu(int n, char c); và lời gọi hàm inkitu(12, 'A'); thì n và c là các đối hình thức, 12 và 'A' là các đối thực sự hoặc giá trị. Các đối hình thức n và c sẽ lần lượt được gán bằng các giá trị tương ứng là 12 và 'A' trước khi tiến hành các câu lệnh trong phần thân hàm. Giả sử hàm in kí tự được khai báo lại thành inkitu(char c, int n); thì lời gọi hàm cũng phải được thay lại thành inkitu('A', 12).

Các giá trị tương ứng được truyền cho đối phải có kiểu cùng với kiểu đối (hoặc C++ có thể tự động chuyển kiểu được về kiểu của đối).

Khi một hàm được gọi, nơi gọi tạm thời chuyển điều khiển đến thực hiện dòng lệnh đầu tiên trong hàm được gọi. Sau khi kết thúc thực hiện hàm, điều khiển lại được trả về thực hiện tiếp câu lệnh sau lệnh gọi hàm của nơi gọi.

: Giả sử ta cần tính giá trị của biểu thức 2x3 - 5x2 - 4x + 1, thay cho việc tính trực tiếp x3 và x2, ta có thể gọi hàm luythua() trong ví dụ trên để tính các giá trị này bằng cách gọi nó trong hàm main() như sau:

#include

#include

double luythua(float x, int n) // trả lại giá trị xn

{

int i ; // biến chỉ số



double kq = 1 ; // để lưu kết quả

for (i=1; i<=n; i++) kết quả *= x ;

return kq;

}
void xmh(int n) // xoá màn hình n lần

{

int i;


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

return ;


}
main() // tính giá trị 2x3 - 5x2 - 4x + 1

{

float x ; // tên biến có thể trùng với đối của hàm



double f ; // để lưu kết quả

cout << "x = " ; cin >> x

f = 2*luythua(x,3) - 5*luythua(x,2) - 4*x + 1;

xmh(100); // xoá thật sạch màn hình 100 lần

cout << setprecision(2) << f << endl ;

}

Qua ví dụ này ta thấy lợi ích của lập trình cấu trúc, chương trình trở nên gọn hơn, chẳng hạn hàm luythua() chỉ được viết một lần nhưng có thể sử dụng nó nhiều lần (2 lần trong ví dụ này) chỉ bằng một câu lệnh gọi đơn giản cho mỗi lần sử dụng thay vì phải viết lại nhiều lần đoạn lệnh tính luỹ thừa.


Hàm với đối mặc định


Mục này và mục sau chúng ta bàn đến một vài mở rộng thiết thực của C++ đối với C có liên quan đến hàm, đó là hàm với đối mặc định và cách tạo, sử dụng các hàm có chung tên gọi. Một mở rộng quan trọng khác là cách truyền đối theo tham chiếu sẽ được bàn chung trong mục truyền tham đối thực sự cho hàm.

Trong phần trước chúng ta đã khẳng định số lượng tham đối thực sự phải bằng số lượng tham đối hình thức khi gọi hàm. Tuy nhiên, trong thực tế rất nhiều lần hàm được gọi với các giá trị của một số tham đối hình thức được lặp đi lặp lại. Trong trường hợp như vậy lúc nào cũng phải viết một danh sách dài các tham đối thực sự giống nhau cho mỗi lần gọi là một công việc không mấy thú vị. Từ thực tế đó C++ đưa ra một cú pháp mới về hàm sao cho một danh sách tham đối thực sự trong lời gọi không nhất thiết phải viết đầy đủ nếu một số trong chúng đã có sẵn những giá trị định trước. Cú pháp này được gọi là hàm với tham đối mặc định và được khai báo với cú pháp như sau:



(đ1, …, đn, đmđ1 = gt1, …, đmđm = gtm) ;

Các đối đ1, …, đn và đối mặc định đmđ1, …, đmđm đều được khai báo như cũ nghĩa là gồm có kiểu đối và tên đối.

Riêng các đối mặc định đmđ1, …, đmđm có gán thêm các giá trị mặc định gt1, …, gtm. Một lời gọi bất kỳ khi gọi đến hàm này đều phải có đầy đủ các tham đối thực sự ứng với các đ1, …, đm nhưng có thể có hoặc không các tham đối thực sự ứng với các đối mặc định đmđ1, …, đmđm. Nếu tham đối nào không có tham đối thực sự thì nó sẽ được tự động gán giá trị mặc định đã khai báo.

:

Xét hàm xmh(int n = 100), trong đó n mặc định là 100, nghĩa là nếu gọi xmh(99) thì màn hình được xoá 99 lần, còn nếu gọi xmh(100) hoặc gọn hơn xmh() thì chương trình sẽ xoá màn hình 100 lần.



Tương tự, xét hàm int luythua(float x, int n = 2); Hàm này có một tham đối mặc định là số mũ n, nếu lời gọi hàm bỏ qua số mũ này thì chương trình hiểu là tính bình phương của x (n = 2). Ví dụ lời gọi luythua(4, 3) được hiểu là 43 còn luythua(4) được hiểu là 42.

Hàm tính tổng 4 số nguyên: int tong(int m, int n, int i = 0; int j = 0); khi đó có thể tính tổng của 5, 2, 3, 7 bằng lời gọi hàm tong(5,2,3,7) hoặc có thể chỉ tính tổng 3 số 4, 2, 1 bằng lời gọi tong(4,2,1) hoặc cũng có thể gọi tong(6,4) chỉ để tính tổng của 2 số 6 và 4.



Chú ý: Các đối ngầm định phải được khai báo liên tục và xuất hiện cuối cùng trong danh sách đối. Ví dụ:

int tong(int x, int y=2, int z, int t=1); // sai vì các đối mặc định không liên tục

void xoa(int x=0, int y) // sai vì đối mặc định không ở cuối

Khai báo hàm trùng tên


Hàm trùng tên hay còn gọi là hàm chồng (đè). Đây là một kỹ thuật cho phép sử dụng cùng một tên gọi cho các hàm "giống nhau" (cùng mục đích) nhưng xử lý trên các kiểu dữ liệu khác nhau hoặc trên số lượng dữ liệu khác nhau. Ví dụ hàm sau tìm số lớn nhất trong 2 số nguyên:

int max(int a, int b) { return (a > b) ? a: b ; }

Nếu đặt c = max(3, 5) ta sẽ có c = 5. Tuy nhiên cũng tương tự như vậy nếu đặt c = max(3.0, 5.0) chương trình sẽ bị lỗi vì các giá trị (float) không phù hợp về kiểu (int) của đối trong hàm max. Trong trường hợp như vậy chúng ta phải viết hàm mới để tính max của 2 số thực. Mục đích, cách làm việc của hàm này hoàn toàn giống hàm trước, tuy nhiên trong C và các NNLT cổ điển khác chúng ta buộc phải sử dụng một tên mới cho hàm "mới" này. Ví dụ:

float fmax(float a, float b) { return (a > b) ? a: b ; }

Tương tự để tuận tiện ta sẽ viết thêm các hàm

char cmax(char a, char b) { return (a > b) ? a: b ; }

long lmax(long a, long b) { return (a > b) ? a: b ; }

double dmax(double a, double b) { return (a > b) ? a: b ; }

Tóm lại ta sẽ có 5 hàm: max, cmax, fmax, lmax, dmax, việc sử dụng tên như vậy sẽ gây bất lợi khi cần gọi hàm. C++ cho phép ta có thể khai báo và định nghĩa cả 5 hàm trên với cùng 1 tên gọi ví dụ là max chẳng hạn. Khi đó ta có 5 hàm:

1: int max(int a, int b) { return (a > b) ? a: b ; }

2: float max(float a, float b) { return (a > b) ? a: b ; }

3: char max(char a, char b) { return (a > b) ? a: b ; }

4: long max(long a, long b) { return (a > b) ? a: b ; }

5: double max(double a, double b) { return (a > b) ? a: b ; }

Và lời gọi hàm bất kỳ dạng nào như max(3,5), max(3.0,5), max('O', 'K') đều được đáp ứng. Chúng ta có thể đặt ra vấn đề: với cả 5 hàm cùng tên như vậy, chương trình gọi đến hàm nào. Vấn đề được giải quyết dễ dàng vì chương trình sẽ dựa vào kiểu của các đối khi gọi để quyết định chạy hàm nào. Ví dụ lời gọi max(3,5) có 2 đối đều là kiểu nguyên nên chương trình sẽ gọi hàm 1, lời gọi max(3.0,5) hướng đến hàm số 2 và tương tự chương trình sẽ chạy hàm số 3 khi gặp lời gọi max('O','K'). Như vậy một đặc điểm của các hàm trùng tên đó là trong danh sách đối của chúng phải có ít nhất một cặp đối nào đó khác kiểu nhau. Một đặc trưng khác để phân biệt thông qua các đối đó là số lượng đối trong các hàm phải khác nhau (nếu kiểu của chúng là giống nhau).

Ví dụ việc vẽ các hình: thẳng, tam giác, vuông, chữ nhật trên màn hình là giống nhau, chúng chỉ phụ thuộc vào số lượng các điểm nối và toạ độ của chúng. Do vậy ta có thể khai báo và định nghĩa 4 hàm vẽ nói trên với cùng chung tên gọi. Chẳng hạn:

void ve(Diem A, Diem B) ; // vẽ đường thẳng AB

void ve(Diem A, Diem B, Diem C) ; // vẽ tam giác ABC

void ve(Diem A, Diem B, Diem C, Diem D) ; // vẽ tứ giác ABCD

trong ví dụ trên ta giả thiết Diem là một kiểu dữ liệu lưu toạ độ của các điểm trên màn hình. Hàm ve(Diem A, Diem B, Diem C, Diem D) sẽ vẽ hình vuông, chữ nhật, thoi, bình hành hay hình thang phụ thuộc vào toạ độ của 4 điểm ABCD, nói chung nó được sử dụng để vẽ một tứ giác bất kỳ.

Tóm lại nhiều hàm có thể được định nghĩa chồng (với cùng tên gọi giống nhau) nếu chúng thoả các điều kiện sau:

Số lượng các tham đối trong hàm là khác nhau, hoặc

Kiểu của tham đối trong hàm là khác nhau.

Kỹ thuật chồng tên này còn áp dụng cả cho các toán tử. Trong phần lập trình hướng đối tượng, ta sẽ thấy NSD được phép định nghĩa các toán tử mới nhưng vẫn lấy tên cũ như +, -, *, / …


1   ...   20   21   22   23   24   25   26   27   ...   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