Hàm trong Ngôn ngữ lập trình C : Phần 1 - Bài 9

Hàm trong Ngôn ngữ lập trình C : Phần 1 - Bài 9
access_time 10/3/2019 12:00:00 AM
person quản trị viên

Hàm trong Ngôn ngữ lập trình C

1. Giới thiệu

Khi xây dựng ứng dụng, lập trình viên có thể chia ứng dụng đó ra thành nhiều các nhóm chức năng khác nhau. Tiếp theo các nhóm chức năng này lại được chia thành các chức năng nhỏ khác. Quá trình này được thực hiện lặp đi lặp lại sao cho cuối cùng sẽ được một tập hợp các chắc năng ở mức độ nhỏ nhất và dễ dàng lập trình, thì các chức năng này được gọi là các hàm.

Cách thức tiếp cận xây dựng ứng dụng như vậy được gọi là mô hình phân tích và thiết kế theo Hướng chức năng. Đây là cách thức tiếp cận truyền thống, ngày nay có nhiều cách thức tiếp cận hiện đại hơn tuy nhiên cách thức tiếp cận theo Hướng chức năng vẫn tỏ ra rất hiệu quả với các bài toàn liên quan tới việc xử lý số và các bài toán có nghiệp vụ rõ ràng và không có sự thay đổi nghiệp vụ.

Nếu các lập trình viên xét theo khía cạnh phân tích theo mô hình cây, sẽ nhìn thấy rõ thực tế các hàm ở đây chính là các nút lá, thân cây chính là chương trình, các cành cây chính là các nhóm chức năng.

Trong lập trình C, Hàm là một chương trình con trong chương trình lớn. Hàm nhận (hoặc không) các đối số và trả lại (hoặc không) một giá trị cho chương trình gọi nó. Trong trường hợp không trả lại giá trị, hàm hoạt động như một thủ tục. Một chương trình là tập các hàm, trong đó có một hàm chính với tên gọi main(), khi chạy chương trình, hàm main() sẽ được chạy đầu tiên và gọi đến hàm khác. Kết thúc hàm main() cũng là kết thúc chương trình.

2. Định nghĩa hàm

Để định nghĩa một hàm trong lập trình C, các lập trình viên làm quen với khai niệm khai báo hàm (Khai báo nguyên mẫu hàm).

Nguyên mẫu hàm chính là đặc tả của hàm cần xây dựng. Lưu ý nguyên mẫu hàm không có chứa nội dung thực thi hàm đó. Các nguyên mẫu của hàm sẽ được định nghĩa trong các file header hoặc ở đầu chương trình. Ví dụ như trong thư viện chuẩn math.h có hàm double sin(double x) trong Thư viện C trả về sin của góc x (giá trị radian). Vậy nguyên mẫu của hàm này chính là :

double sin(double x);

Với x là tham số, giá trị trả về là sin x.

Cú pháp khai báo hàm :

return-type function-name(argument declarations){

          statements

          return expresstion;

}

Trong đó :

·       function-name : Tên hàm

·       argument declarations: Danh sách các tham số truyền vào của hàm người ta còn gọi là danh sách các tham số hình thức

·       statements: Các câu lệnh thực thi của hàm.

·       return expresstion: Trả về một biểu thức nào đó. Câu lệnh này có thể nằm ở đâu đó trong dãy các câu lệnh đã được cài đặt.

Ví dụ: Viết hàm tính diện tích hình vuông, với điều kiện các cạnh hình vuông là số nguyên.


Hình số 1: Hàm tính diện tích hình vuông

Khi xây dựng hàm, thì có trường hợp hàm không trả về giá trị (trả về void) trong trường hợp này người ta gọi hàm đó là thủ tục.

Cú pháp khai báo thủ tục:

void function-name(argument declarations){

          statements

          return;

}

Trong đó :

·       function-name : Tên hàm

·       argument declarations: Danh sách các tham số truyền vào của hàm người ta còn gọi là danh sách các tham số hình thức

·       statements: Các câu lệnh thực thi của hàm.

·       return: Không trả về một giá trị cụ thể nào.

Ví dụ: Viết hàm thực hiện xóa màn hình
void xmh(){

clrscr();

return;

}

Chú ý về khai báo và định nghĩa hàm:

·       Danh sách đối trong khai báo hàm có thể chứa hoặc không chứa tên đối, thông thường ta chỉ khai báo kiểu đối chứ không cần khai báo tên đối, trong khi ở dòng đầu tiên của định nghĩa hàm phải có tên đối đầy đủ.

·       Cuối khai báo hàm phải có dấu chấm phẩy (;), trong khi cuối dòng đầu tiên của định nghĩa hàm không có dấu chấm phẩy.

·       Hàm có thể không có đối (danh sách đối rỗng), tuy nhiên cặp dấu ngoặc sau tên hàm vẫn phải được viết. Ví dụ clrscr(), message(), note(), …

·       Một hàm có thể không cần phải khai báo nếu nó được định nghĩa trước khi có hàm nào đó gọi đến nó. Ví dụ có thể viết hàm main() trước (trong văn bản chương trình), rồi sau đó mới viết đến các hàm "con". Do trong hàm main() chắc chắn sẽ gọi đến hàm con này nên danh sách của chúng phải được khai báo trước hàm main(). Trường hợp ngược lại nếu các hàm con được viết (định nghĩa) trước thì không cần phải khai báo chúng nữa (vì trong định nghĩa đã hàm ý khai báo). Nguyên tắc này áp dụng cho hai hàm A, B bất kỳ chứ không riêng cho hàm main(), nghĩa là nếu B gọi đến A thì trước đó A phải được định nghĩa hoặc ít nhất cũng có dòng khai báo về A.

3. 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 ().

funtion-name (argument) ;

+ Argument (Danh sách tham đối thực sự): 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.

+ 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.

Ví dụ :

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:

Code minh họa


Hình số 2 : Gọi hàm

4. Thực thi chương trình đối với hàm

Một chương trình được viết bởi ngôn ngữ C có thể chứa một hoặc nhiều hàm khác nhau. Điểm khởi đầu của việc chạy chương trình đó là hàm main(); Khi lập trình viên sử dụng các thư viện chứa các hàm như scanf(), printf(), khi đó chương trình sẽ thực thi cấp phát bộ nhớ để thực hiện các lời gọi hàm.

Hàm có thể gọi một hàm khác để thực hiện một số tác vụ cụ thể và sau đó tiếp tục thực thi trở lại chức năng gọi khi nhiệm vụ hoàn thành. Khi một hàm được gọi thì các đoạn mã trong hàm đó sẽ được thực thi, khi hàm được gọi kết thúc quá trình thực thi, trình biên dịch sẽ trả về điểm mà hàm đó được gọi.

Hình sau minh họa quá trình thực thi của Chương trình C bắt đầu từ hàm main().


Hình số 3 : Thực thi chương trình với hàm main

5. Biến, tham chiếu

Một biến có thể được gán cho một bí danh mới, và khi đó chỗ nào xuất hiện biến thì cũng tương đương như dùng bí danh và ngược lại. Một bí danh như vậy được gọi là một biến tham chiếu, ý nghĩa thực tế của nó là cho phép "tham chiếu" tới một biến khác cùng kiểu của nó, tức sử dụng biến khác nhưng bằng tên của biến tham chiếu.

Giống khai báo biến bình thường, tuy nhiên trước tên biến ta thêm dấu và (&). Có thể tạm phân biến thành 3 loại: biến thường với tên thường, biến con trỏ với dấu * trước tên và biến tham chiếu với dấu &.

<type> &<paramater reference > = <name variable >;

type : kiểu dữ liệu của biến tham chiếu

paramater reference: Tên biến tham chiếu

name variable: Tên của biến được tham chiếu

Cú pháp khai báo này cho phép ta tạo ra một biến tham chiếu mới và cho nó tham chiếu đến biến được tham chiếu (cùng kiểu và phải được khai báo từ trước). Khi đó biến tham chiếu còn được gọi là bí danh của biến được tham chiếu. Chú ý không có cú pháp khai báo chỉ tên biến tham chiếu mà không kèm theo khởi tạo.

Ví dụ:

int hung, dung ;                   // khai báo các biến nguyên hung, dung

int &ti = hung;                     // khai báo biến tham chiếu ti, teo tham chieu đến

int &teo = dung;                 // hung dung. ti, teo là bí danh của hung, dung

Từ vị trí này trở đi việc sử dụng các tên hung, ti hoặc dung, teo là như nhau.

Ví dụ:

hung = 2 ;

ti ++;                                                     // tương đương hung ++;

printf(“/d/%d”, hung, ti);                         // 3 3

teo = ti + hung ;                                       // tương đương dung = hung + hung

dung ++ ;                                               // tương đương teo ++

printf(“/d/%d”, dung, teo);                       // 7 7

 

Vậy sử dụng thêm biến tham chiếu để làm gì ?

Cách tổ chức bên trong của một biến tham chiếu khác với biến thường ở chỗ nội dung của nó là địa chỉ của biến mà nó đại diện (giống biến con trỏ), ví dụ câu lệnh

printf (“%d”, teo) ;                                   // 7

In ra giá trị 7 nhưng thực chất đây không phải là nội dung của biến teo, nội dung của teo là địa chỉ của dung, khi cần in teo, chương trình sẽ tham chiếu đến dung và in ra nội dung của dung (7).

Các hoạt động khác trên teo cũng vậy (ví dụ teo++), thực chất là tăng một đơn vị nội dung của dung (chứ không phải của teo). Từ cách tổ chức của biến tham chiếu ta thấy chúng giống con trỏ nhưng thuận lợi hơn ở chỗ khi truy cập đên giá trị của biến được tham chiếu (dung) ta chỉ cần ghi tên biến tham chiếu (teo) chứ không cần thêm toán tử (*) ở trước như trường hợp dùng con trỏ. Điểm khác biệt này có ích khi được sử dụng để truyền đối cho các hàm với mục đích làm thay đổi nội dung của biến ngoài.

Chú ý:

·       Biến tham chiếu phải được khởi tạo khi khai báo.

·       Tuy giống con trỏ nhưng không dùng được các phép toán con trỏ cho biến tham chiếu. Nói chung chỉ nên dùng trong truyền đối cho hàm.

 

6. Truyền giá trị Hàm thông qua tham trị và tham chiếu

6.1 Truyền thông qua tham trị

Khi tham số của hàm được truyền thông qua tham trị (thông qua nội dung giá trị của tham số), thì bản chất của việc truyền thông qua giá trị của tham số đó là copy giá trị của tham số và sử dụng giá trị copy đó vào thực hiện nội dung của hàm. Điều này có nghĩa giá trị thực sự của tham số khi được truyền hàm vào là không thay đổi hàm thực thi xong.

Ví dụ :

Viết hàm swap(int a, int b) thực hiện trao đổi giá trị của a và b. Kết quả thực hiện giá trị của a, b không đổi.


Hình số 4 : Truyền thông qua tham trị

6.2 Truyền thông qua tham chiếu

Thay vì thực hiện truyền thông qua tham trị thì lập trình viên có thể sử dụng thông qua tham chiếu. Khi tham số hàm được sử dụng thông qua cơ chế tham chiếu có nghĩa là Các thao tác liên quan đến đối này phải thực hiện tại nơi nó trỏ đến, tức địa chỉ cần thao tác.

Nguyên mẫu hàm khi thực hiện truyền qua tham chiếu như sau :

return-type function-name(<type>&argument1, (<type>&argument2 );

Ví dụ : Viết hàm swap(int &a, int &b) thực hiện trao đổi giá trị của a và b. Kết quả thực hiện giá trị của a, b được đổi chỗ cho nhau.

void swap(int &a, int &b)

{

int c = x; x = y; y = c;

}

Khi đó thực hiện gọi hàm giá trị truyền tham số sẽ bị, giá trị truyền vào của numberOne và numberTwo sẽ được tráo đổi cho nhau.

Tuy nhiên nếu các bạn Code thuần C thì nó sẽ báo lỗi. Ở đây khái niệm tham chiếu là của C++. Còn thuần C sẽ không có phần này.

Ngoài quá trình truyền bằng tham trị, chúng ta có thể thực hiện truyền bằng con trỏ. Phần này tôi sẽ trình bày sau.

 

 

 

 

 


vertical_align_top
share
Chat...