HÀM 1 Giới thiệu

Một phần của tài liệu Giáo trình Cơ sở lập trình - Phan 2 (Trang 41 - 50)

Chương 4 HÀM

4.1. HÀM 1 Giới thiệu

4.1.1. Giới thiệu

Hai chương trước chúng ta đã làm quen với hàm chính của một chương trình là hàm main. Tất cả các phép toán đều được thực hiện trong hàm main. về cơ bàn, một hàm main đã đủ để cho chúng ta viết một chương trình hồn thiện. Tuy nhiên, một sổ vấn đề sau được đặt ra sẽ giải quyết thế nào nếu chỉ có một hàm main:

Thứ nhất, xét chương trình tính n!. #include <stdio.h> #include <conio.h> void main() { n; int i; intresult=l; printf("\n Nhap so n:"); scanf("%d",&n); printf("\n"); for(i=l;i<=n;i-H-) { result*=i; } printf(" %d!=%d",n,result); getch(); }

Chương hình viết như thế này sẽ chỉ thực sự tốt khi người sử dụng chì nhập một số n là số tự nhiên bất kì nào đó, ví dụ như nhập 3, chương trình tính được 3! = 6. Nhưng nếu ta muốn tính 3!, 5!, 6! thì phải làm thế nào? Với chương trình trên, có hai cách. Một là chạy chương trình, tính với n=3. Sau đó lại chạy lại chương trình với n=5 và tương tự với n= 6. Cách thứ hai là viết lại chương trình khai báo 3 biến X, y, z để lưu giá trị của 3, 5, 6. Nhưng cả hai cách này đều không tốt. Ngôn ngữ lập trình c cung cấp một chức năng giúp giải quyết vấn đề này tốt hơn đó là hàm do người dùng tự định nghĩa. Hàm này phục vụ rất tốt cho việc sử dụng lại các đoạn mã lệnh, tiết kiệm dòng lệnh và linh động trong việc sử dụng các hàm bao nhiêu tùy ý thông qua lời gọi hàm. Ví dụ như chương trình tính n! ở trên được viết lại như sau:

#include <stdio.h> #include <conio.h>

// Ham tinh n giai thua int factorialfunc (int n); int factorial firne (int n) {

int result = 1; int i = 1; for (; i<=n; i++) { result *= i; } return result; } void main o { int n, kq;

printf(“\n Nhap vao mot so n: ”); scanf (“%d”,&n);

kq=factorial_fimction(n); // loi goi ham tinh n giai thua o tren printf(“\n %d!=%d ”,n,kq);

Với chương trình được viết lại như trên, hàm main muốn tính giai thừa của bao nhiêu sổ thì chỉ việc khai báo bấy nhiêu biến và gọi tương ứng bấy nhiêu lần hàm tính n giai thừa. Và một lý do nữa để xây dựng hàm, đó là ta có thể tổ chức một chương trình thành các hàm. Điều này rất có lợi đối với các vấn đề lớn và phức tạp. Ta có thể chia nhỏ vấn đề phức tạp thành các vấn đề nhỏ hơn để giải quyết và sau đó ghép kết quả lại. Việc chia nhỏ này có thể ví như ngun tắc 'chìa để trị'. Đó cũng là ý tưởng cơ bản của khái niệm lập chương trình có cấu trúc.

Ví dụ để tính tổ hợp chập k của n dựa vào cơng thức tính:

nỉ k!(n-k)!

Ta xây dựng hai hàm như sau:

- Hàm gt: dùng để tính giai thừa của một số nguyên n. - Hàm th: dùng để tính tổ hợp chập k của n.

#include <conio.h> #include <stdio.h> #include <stdlib.h> unsigned long gt(int n); unsigned long th(int k, int n); int k,n;

void main() {

printf("\n nhap k =");scanf("%d",&k); printf("\n nhap n =");scanf("%d",&n);

printf("\n to hop chap %d cua %d la:%lu",k,n,th(k,n)); getch();

}

{

unsigned long tg=l; if (n=0 II n=l) tg=l; else

for (int i=2;i<=n;i++) tg=tg*i; return tg;

}

unsigned long th(int k, int n) {

return gt(n)/(gt(k)*gt(n-k)); }

Với chương trình frên nếu ta nhập k=3 và n=5 thì kết quả chạy chương trình sẽ là dịng:

“to hop chap 3 cua 5 la: 10”

Thực tế, các hàm như scanf, printf cũng là các hàm đã được viết sẵn từ trước, đặt trong file stdỉo.h, và người dùng chỉ việc gọi các hàm đó ra sử dụng. Như vậy viết riêng các phép toán phục vụ cho một thuật toán nào đó thành các hàm riêng cịn có một giá trị rất lớn trong việc sử dụng lại cho nhiều chương trình khác nhau. Để tìm hiểu cách viết một hàm, chúng ta hãy xem xét hàm trong c có đặc điểm gì. Ngơn ngữ lập trình c cho phép trong một chương trình có thể có nhiều hàm. Tuy c là một ngơn ngữ lập trình khơng cho phép trong một hàm có thể định nghĩa một hàm khác nhưng cho phép trong một hàm có thể có nhiều lời gọi hàm đến các hàm khác đã được định nghĩa. Một hàm bao giờ cũng phải quan tâm đến ba yếu tố đó là: khai báo nguyên mẫu hàm (prototype), định

nghĩa hàm (declaration) và lời gọi hàm (call). 4.1.2. Cấu trúc tổng quát của hàm

Cú pháp khai báo nguyên mẫu hàm:

<kiểu_dự_liệu><tên_hàm>([<kiểul>[tham_sốl],<kiểu2>[tham_ SỐ2],...]);

Vị trí khai báo nguyên mẫu hàm bao giờ cũng được đặt ở đầu chương trình, sau chỉ dẫn tiền xử lý. Khi khai báo nguyên mẫu hàm, chương trình dịch cần quan tâm đến các thơng tin sau:

kiểu_dữ_liệu: kiểu giá trị trả về cùa hàm. Kiểu này chính là một

trong các kiểu dữ liệu cơ sở mà độc giả đã được giới thiệu ở trên. Các kiểu dữ liệu có cấu trúc chúng tơi sẽ giới thiệu sau.

tên hàm: Tên của hàm. Tên hàm được đật theo qui tắc đật tên của c. kiểul, kiểu2,...: Đây là danh sách các kiểu của đối số sẽ truyền vào

cho hàm.

*thamsốl, tham_số2..: là tên các đối số. Thực sự các tên này không thamsốl, tham_số2..: là tên các đối số. Thực sự các tên này khơng

cần đặt vì khi khai báo nguyên mẫu hàm, trình biên dịch chỉ quan tâm đến kiểu hàm, tên hàm, kiểu và thứ tự kiểu của đối số. Tuy nhiên, đặt tên cũng không sao.

Sau khi khai báo nguyên mẫu hàm, hàm được định nghĩa theo cú pháp sau.

Cú pháp:

<kiểu_dữ_liệu><tên_hàm> (<kiểul> <tham_sốl>,<kiểu2> <tham_số2>,...)

{

// thân hàm return (giá trị); }

Với định nghĩa hàm, người viết phải chú ý viết đúng kiểu, đúng tên, đúng thứ tự và tên các kiểu các đối số ở trong cặp 0 tương ứng với frong khai báo nguyên mẫu. Định nghĩa hàm bắt buộc phải có tên đối số trong danh sách các đổi số của hàm. Nếu kiểu hàm là void hàm khơng cần có giá trị trả về. Nhưng kiểu hàm là các kiểu khác thì bắt buộc phải có giá trị trả về. Lệnh trả về giá trị của hàm được đặt ở dòng lệnh cuối cùng của thân hàm return (giá trị);

Như trên đã nói, frong nguyên mẫu hàm không cần phải đặt tên cho các đối số của hàm ngay. Tuy nhiên, chúng ta nên đặt ngay tên các đối số ở phần khai báo nguyên mẫu để khi viết định nghĩa hàm tránh nhầm lẫn về thứ tự hay thiếu đối số. Như vậy, có ba cách viết nguyên mẫu hàm và định nghĩa hàm như ba ví dụ dưới đây là hồn tồn hợp lệ.

Ví dụ 4.1.1:

Chương trình tính USCLN của hai số m và n. //include <stdio.h>

//include <conio.h>

unsigned USCLN (unsigned, unsigned); unsigned USCLN (unsigned n, unsigned m)

{ while (n != 0 && m != 0) if (n>m) n -= m; else m-=n; if (n = 0) return m; else return n; } int main() { unsigned n, m;

printf("\nNhap hai vao so nguyên duong : "); scanf("%u%u", &n, &m);

printf("\nUSCLN cua %u va %u = %u", n, m, USCLN(n,m)); getcheO;

return 1; }

Ví dụ 4.1.2: Chương trình tính USCLN của hai số m và n

//include <conio.h>

unsigned USCLN (unsigned n, unsigned m); unsigned USCLN (unsigned n, unsigned m)

{ while (n != 0 && m != 0) if (n>m) n -= m; else m -= n; if (n = 0) return m; else return n; } int main() { unsigned n, m;

printf("\nNhap hai vao so nguyen duong :"); scanf("%u%u", &n, &m);

printf("\nUSCLN cua %u va %u = %u", n, m, USCLN(n,m)); getcheO;

return 1; }

Ví dụ 4.1.3:

Chương trình tính USCLN của hai sổ m và n //include <stdio.h>

//include <conio.h>

unsigned USCLN (unsigned a, unsigned b); unsigned USCLN (unsigned n, unsigned m)

{

while (n != 0 && m != 0) if(n>m) n-=m;

if (n = 0) return m; else return n; } int main() { unsigned n, m;

printf("\nNhap hai vao so nguyen duong :"); scanf("%u%u", &n, &m);

printf("\nUSCLN cua %u va %u = %u", n, m, USCLN(n,m)); getche();

return 1; }

Với cà ba chương trình này, nếu nhập giá trị cho n và m lần lượt là 12 và 18 thì kết quả khi chạy chương trình đều sẽ in ra trên màn hình là dịng “USCLN cua 12 va 18 =6”.

Ba ví dụ trên minh họa ba cách viết nguyên mẫu hàm và định nghĩa hàm hợp lệ trong c, nhưng cách 1 và cách 2 nên viết hơn cả, cách thứ 3 vừa rắc rối vừa không cần thiết.

Cú pháp của lời gọi hàm như sau:

[biến =]<tên_hàm>([<danh sách tham số thực sự>]);

Lời gọi hàm được thực hiện bên trong một thân hàm bất kì nào đó: hàm main, hàm khác hay chính trong bản thân hàm <tên_hàm> ở trên. Neu hàm <tên_hàm> có kiểu hàm khác void thì bắt buộc phải có biến lấy giá frị trả về này cùa hàm trong lòi gọi hàm.

Ở trên, chúng ta đã được biết một cách khai báo nguyên mẫu hàm và định nghĩa hàm tách ra làm hai phần riêng. Ngôn ngữ c còn cho phép một cách viết khác gộp chung khai báo nguyên mẫu hàm và định nghĩa hàm như sau:

<kiểu> <tên_hàm>([<kỉểii 1> <tham_sốl>, <kiểu2> <tham_sổ2>,....])

{

// thân hàm

return <giá trị>; }

Với cách viết kiểu này các hàm bắt buộc phải viết trước khi có lời gọi hàm.

Ví dụ 4.1.4: Chương trình tính USCLN, BSCNN của hai số a và b

#include <stdio.h> //include <conio.h>

unsigned USCLN (unsigned a, unsigned b); unsigned BSCNN (unsigned a , unsigned b)

{ return(a*b/USCLN(a,b)); } int main() { unsigned n, m;

printf("\nNhap hai vao so nguyen duong :"); scanf("%u%u", &n, &m);

printf("\nUSCLN cua %u va %u = %u", n, m, USCLN(n,m)); printf("\nBSCNN cua %u va %u = %u", n, m, BSCNN(n,m)); getcheO;

return 1; }

unsigned ƯSCLN (unsigned n, unsigned m) {

while (n != 0 && m != 0) if(n>m) n-=m; else

m-= n; if (n == 0) return m; else

return n; }

Với chương trình này, nếu nhập vào giá trị của n và m lần lượt là 18 và 12, thì kết quả khi chạy chương trình sẽ in ra hên màn hình lần lượt hai dòng:

“USCLN cua 18 va 12 =6” “BSCNN cua 18 va 12 =36”

Các hàm printf, scanf là các hàm đã được khai báo trong thư viện stdio.h. Cho nên khi viết các hàm scanf, printf như ví dụ trên, thực sự chúng là các lời gọi hàm của hàm scanf và printf. Các hàm như vậy gọi là các hàm đã định nghĩa hay các hàm thư viện. Muốn sử dụng các hàm như vậy, đàu chương trình phải có các khai báo chèn tệp thư viện:

#include <ten_fĩle.h>. Lệnh chèn tệp có thể làm việc với các file có đi

mở rộng khác như .c, .cpp hay bất kì một đi mở rộng nào khác. Nhưng file header .h vẫn phổ biến nhất. Trong chương trình, lời gọi hàm phải đúng cú pháp của hàm thư viện.

Một phần của tài liệu Giáo trình Cơ sở lập trình - Phan 2 (Trang 41 - 50)

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

(114 trang)