25/05/2018, 08:28

Hàm trong C++

Hàm là một khối lệnh được thực hiện khi nó được gọi từ một điểm khác của chương trình. Dạng thức của nó như sau: type name ( argument1 , argument2 , ...) ...

Hàm là một khối lệnh được thực hiện khi nó được gọi từ một điểm khác của chương trình. Dạng thức của nó như sau:

type name ( argument1 , argument2 , ...) statement

trong đó:type là kiểu dữ liệu được trả về của hàmname là tên gọi của hàm.arguments là các tham số (có nhiều bao nhiêu cũng được tuỳ theo nhu cầu). Một tham số bao gồm tên kiểu dữ liệu sau đó là tên của tham số giống như khi khai báo biến (ví dụ int x) và đóng vai trò bên trong hàm như bất kì biến nào khác. Chúng dùng để truyền tham số cho hàm khi nó được gọi. Các tham số khác nhau được ngăn cách bởi các dấu phẩy.statement là thân của hàm. Nó có thể là một lệnh đơn hay một khối lệnh.

Dưới đây là ví dụ đầu tiên về hàm:

Code:

// function example

#include <iostream.h>

int addition (int a, int b)

{

int r;

r=a+b;

return (r);

}

int main ()

{

int z;

z = addition (5,3);

cout << "The result is " << z;

return 0;

}

Kết quả:

The result is 8

Để có thể hiểu được đoạn mã này, trước hết hãy nhớ lại những điều đã nói ở bài đầu tiên: một chương trình C++ luôn bắt đầu thực hiện từ hàm main. Vì vậy chúng ta bắt đầu từ đây.

Chúng ta có thể thấy hàm main bắt đầu bằng việc khai báo biến z kiểu int. Ngay sau đó là một lời gọi tới hàm addition. Nếu để ý chúng ta sẽ thấy sự tương tự giữa cấu trúc của lời gọi hàm với khai báo của hàm:

Các tham số có vai trò thật rõ ràng. Bên trong hàm main chúng ta gọi hàm addition và truyền hai giá trị: 53 tương ứng với hai tham số int aint b được khai báo cho hàm addition.

Vào thời điểm hàm được gọi từ main, quyền điều khiển được chuyển sang cho hàm addition. Giá trị của c hai tham số (53) được copy sang hai biến cục bộ int aint b bên trong hàm.

Dòng lệnh sau:

return (r);

kết thúc hàm addition, và trả lại quyền điều khiển cho hàm nào đã gọi nó (main) và tiếp tục chương trình ở cái điểm mà nó bị ngắt bởi lời gọi đến addition. Nhưng thêm vào đó, giá trị được dùng với lệnh return (r) chính là giá trị được trả về của hàm.

Giá trị trả về bởi một hàm chính là giá trị của hàm khi nó được tính toán. Vì vậy biến z sẽ có có giá trị được trả về bởi addition (5, 3), đó là 8

Phạm vi hoạt động của các biến  [nhắc lại]

Bạn cần nhớ rằng phạm vi hoạt động của các biến khai báo trong một hàm hay bất kì một khối lệnh nào khác chỉ là hàm đó hay khối lệnh đó và không thể sử dụng bên ngoài chúng. Ví dụ, trong chương trình ví dụ trên, bạn không thể sử dụng trực tiếp các biến a, b hay r trong hàm main vì chúng là các biến cục bộ của hàm addition. Thêm vào đó bạn cũng không thể sử dụng biến z trực tiếp bên trong hàm addition vì nó làm biến cục bộ của hàm main.

Tuy nhiên bạn có thể khai báo các biến toàn cục để có thể sử dụng chúng ở bất kì đâu, bên trong hay bên ngoài bất kì hàm nào. Để làm việc này bạn cần khai báo chúng bên ngoài mọi hàm hay các khối lệnh, có nghĩa là ngay trong thân chương trình.

Đây là một ví dụ khác về hàm:

Code:

// function example

#include <iostream.h>

int subtraction (int a, int b)

{

int r;

r=a-b;

return (r);

}

int main ()

{

int x=5, y=3, z;

z = subtraction (7,2);

cout << "The first result is " << z << ' ';

cout << "The second result is " << subtraction (7,2) << ' ';

cout << "The third result is " << subtraction (x,y) << ' ';

z= 4 + subtraction (x,y);

cout << "The fourth result is " << z << ' ';

return 0;

}

Kết quả:

The first result is 5 The second result is 5 The third result is 2 The fourth result is 6

Trong trường hợp này chúng ta tạo ra hàm subtraction. Chức năng của hàm này là lấy hiệu của hai tham số rồi trả về kết quả.

Tuy nhiên, nếu phân tích hàm main các bạn sẽ thấy chương trình đã vài lần gọi đến hàm subtraction. Tôi đã sử dụng vài cách gọi khác nhau để các bạn thấy các cách khác nhau mà một hàm có thể được gọi.

Để có hiểu cặn kẽ ví dụ này bạn cần nhớ rằng một lời gọi đến một hàm có thể hoàn toàn được thay thế bởi giá trị của nó. Ví dụ trong lệnh gọi hàm đầu tiên :

z = subtraction (7,2); cout << "The first result is " << z;

Nếu chúng ta thay lời gọi hàm bằng giá trị của nó (đó là 5), chúng ta sẽ có:

z = 5; cout << "The first result is " << z;

Tương tự như vậy

cout << "The second result is " << subtraction (7,2);

cũng cho kết quả giống như hai dòng lệnh trên nhưng trong trường hợp này chúng ta gọi hàm subtraction trực tiếp như là một tham số của cout. Chúng ta cũng có thể viết:

cout << "The second result is " << 5;

5 là kết quả của subtraction (7,2).

Còn với lệnh

cout << "The third result is " << subtraction (x,y);

Điều mới mẻ duy nhất ở đây là các tham số của subtraction là các biến thay vì các hằng. Điều này là hoàn toàn hợp lệ. Trong trường hợp này giá trị được truyền cho hàm subtractiongiá trị của x and y.

Trường hợp thứ tư cũng hoàn toàn tương tự. Thay vì viết

z = 4 + subtraction (x,y);

chúng ta có thể viết:

z = subtraction (x,y) + 4;

cũng hoàn toàn cho kết quả tương đương. Chú ý rằng dấu chấm phẩy được đặt ở cuối biểu thức chứ không cần thiết phải đặt ngay sau lời gọi hàm.

Các hàm không kiểu. Cách sử dụng void.

Nếu bạn còn nhớ cú pháp của một lời khai báo hàm:

type name ( argument1 , argument2 ...) statement

bạn sẽ thấy rõ ràng rằng nó bắt đầu với một tên kiểu, đó là kiểu dữ liệu sẽ được hàm trả về bởi lệnh return. Nhưng nếu chúng ta không muốn trả về giá trị nào thì sao ?

Hãy tưởng tượng rằng chúng ta muốn tạo ra một hàm chỉ để hiển thị một thông báo lên màn hình. Nó không cần trả về một giá trị nào cả, hơn nữa cũng không cần nhận tham số nào hết. Vì vậy người ta đã nghĩ ra kiểu dữ liệu void trong ngôn ngữ C. Hãy xem xét chương trình sau:

Code:

// void function example

#include <iostream.h>

void dummyfunction (void)

{

cout << "I'm a function!";

}

int main ()

{

dummyfunction ();

return 0;

}

Kết quả:

I'm a function!

Từ khoá void trong phần danh sách tham số có nghĩa là hàm này không nhận một tham số nào. Tuy nhiên trong C++ không cần thiết phải sử dụng void để làm điều này. Bạn chỉ đơn giản sử dụng cặp ngoặc đơn ( ) là xong.

Bởi vì hàm của chúng ta không có một tham số nào, vì vậy lời gọi hàm dummyfunction sẽ là :

dummyfunction ();

style="BORDER-RIGHT: medium none; PADDING-RIGHT: 0in; BORDER-TOP: medium none; PADDING-LEFT: 0in; PADDING-BOTTOM: 0in; BORDER-LEFT: medium none; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 3pt solid">

Hai dấu ngoặc đơn là cần thiết để cho trình dịch hiểu đó là một lời gọi hàm chứ không phải là một tên biến hay bất kì dấu hiệu nào khác.

Truyền tham số theo tham số giá trị hay tham số biến.

Cho đến nay, trong tất cả các hàm chúng ta đã biết, tất cả các tham số truyền cho hàm đều được truyền theo giá trị. Điều này có nghĩa là khi chúng ta gọi hàm với các tham số, những gì chúng ta truyền cho hàm là các giá trị chứ không phải bản thân các biến. Ví dụ, giả sử chúng ta gọi hàm addition như sau:

int x=5, y=3, z;z = addition ( x , y );

Trong trường hợp này khi chúng ta gọi hàm addition thì các giá trị 5 and 3 được truyền cho hàm, không phải là bản thân các biến.

Đến đây các bạn có thể hỏi tôi: Như vậy thì sao, có ảnh hưởng gì đâu ? Điều đáng nói ở đây là khi các bạn thay đổi giá trị của các biến a hay b bên trong hàm thì các biến xy vẫn không thay đổi vì chúng đâu có được truyền cho hàm chỉ có giá trị của chúng được truyền mà thôi.

Hãy xét trường hợp bạn cần thao tác với một biến ngoài ở bên trong một hàm. Vì vậy bạn sẽ phải truyền tham số dưới dạng tham số biến như ở trong hàm duplicate trong ví dụ dưới đây:

Code:

// passing parameters by reference

#include <iostream.h>

void duplicate (int& a, int& b, int& c)

{

a*=2;

b*=2;

c*=2;

}

int main ()

{

int x=1, y=3, z=7;

duplicate (x, y, z);

cout << "x=" << x << ", y=" << y << ", z=" << z;

return 0;

}

Kết quả:

x=2, y=6, z=14

Điều đầu tiên làm bạn chú ý là trong khai báo của duplicate theo sau tên kiểu của mỗi tham số đều là dấu và (&), để báo hiệu rằng các tham số này được truyền theo tham số biến chứ không phải tham số giá trị.

Khi truyền tham số dưới dạng tham số biến chúng ta đang truyền bản thân biến đó và bất kì sự thay đổi nào mà chúng ta thực hiện với tham số đó bên trong hàm sẽ ảnh hưởng trực tiếp đến biến đó.

Trong ví dụ trên, chúng ta đã liên kết a, bc với các tham số khi gọi hàm (x, yz) và mọi sự thay đổi với a bên trong hàm sẽ ảnh hưởng đến giá trị của xvà hoàn toàn tương tự với by, cz.

Kiểu khai báo tham số theo dạng tham số biến sử dụng dấu và (&) chỉ có trong C++. Trong ngôn ngữ C chúng ta phải sử dụng con trỏ để làm việc tương tự như thế.

Truyền tham số dưới dạng tham số biến cho phép một hàm trả về nhiều hơn một giá trị. Ví dụ, đây là một hàm trả về số liền trước và liền sau của tham số đầu tiên.

Code:

// more than one returning value

#include <iostream.h>

void prevnext (int x, int& prev, int& next)

{

prev = x-1;

next = x+1;

}

int main ()

{

int x=100, y, z;

prevnext (x, y, z);

cout << "Previous=" << y << ", Next=" << z;

return 0;

}

Kết quả:

Previous=99, Next=101

Giá trị mặc định của tham số.

Khi định nghĩa một hàm chúng ta có thể chỉ định những giá trị mặc định sẽ được truyền cho các đối số trong trường hợp chúng bị bỏ qua khi hàm được gọi. Để làm việc này đơn giản chỉ cần gán một giá trị cho đối số khi khai báo hàm. Nếu giá trị của tham số đó vẫn được chỉ định khi gọi hàm thì giá trị mặc định sẽ bị bỏ qua. Ví dụ:

Code:

// default values in functions

#include <iostream.h>

int divide (int a, int b=2)

{

int r;

r=a/b;

return (r);

}

int main ()

{

cout << divide (12);

cout << endl;

cout << divide (20,4);

return 0;

}

Kết quả:

6 5

Nhưng chúng ta thấy trong thân chương trình, có hai lời gọi hàm divide. Trong lệnh đầu tiên:

divide (12)

chúng ta chỉ dùng một tham số nhưng hàm divide cho phép đến hai. Bởi vậy hàm divide sẽ tự cho tham số thứ hai giá trị bằng 2 vì đó là giá trị mặc định của nó (chú ý phần khai báo hàm được kết thúc bởi int b=2). Vì vậy kết quả sẽ là 6 (12/2).

Trong lệnh thứ hai:

divide (20,4)

có hai tham số, bởi vậy giá trị mặc định sẽ được bỏ qua. Kết quả của hàm sẽ là 5 (20/4).

Quá tải các hàm.

Hai hàm có thể có cũng tên nếu khai báo tham số của chúng khác nhau, điều này có nghĩa là bạn có thể đặt cùng một tên cho nhiều hàm nếu chúng có số tham số khác nhau hay kiểu dữ liệu của các tham số khác nhau (hay thậm chí là kiểu dữ liệu trả về khác nhau). Ví dụ:

Code:

// overloaded function

#include <iostream.h>

int divide (int a, int b)

{

return (a/b);

}

float divide (float a, float b)

{

return (a/b);

}

int main ()

{

int x=5,y=2;

float n=5.0,m=2.0;

cout << divide (x,y);

cout << " ";

cout << divide (n,m);

return 0;

}

Kết quả:

2 2.5

Trong ví dụ này chúng ta định nghĩa hai hàm có cùng tên nhưng một hàm dùng hai tham số kiểu int và hàm còn lại dùng kiểu float. Trình biên dịch sẽ biết cần phải gọi hàm nào bằng cách phân tích kiểu tham số khi hàm được gọi.

Để đơn giản tôi viết cả hai hàm đều có mã lệnh như nhau nhưng điều này không bắt buộc. Bạn có thể xây dựng hai hàm có cùng tên nhưng hoạt động hoàn toàn khác nhau.

Các hàm inline.

Chỉ thị inline có thể được đặt trước khao báo của một hàm để chỉ rõ rằng lời gọi hàm sẽ được thay thế bằng mã lệnh của hàm khi chương trình được dịch. Việc này tương đương với việc khai báo một macro, lợi ích của nó chỉ thể hiện với các hàm rất ngắn, tốc độ chạy chương trình sẽ được cải thiện vì nó không phải gọi một thủ tục con.

Cấu trúc của nó như sau:

inline type name ( arguments ... ) { instructions ... }lời gọi hàm cũng như bất kì một hàm nào khác. Không cần thiết phải đặt từ khoá inline trong lệnh gọi, chỉ cần trong lời khai báo hàm là đủ.

Đệ qui.

Các hàm có thể gọi chính nó. Điều này có thể có ích với một số tác vụ như là một số phương pháp sắp xếp hay tính giai thừa của một số. Ví dụ, để tính giai thừa của một số (n), công thức toán học của nó như sau:

n! = n * (n-1) * (n-2) * (n-3) ... * 1

và một hàm đệ qui để tính toán sẽ như sau:

Code:

// factorial calculator

#include <iostream.h>

long factorial (long a)

{

if (a > 1)

return (a * factorial (a-1));

else

return (1);

}

int main ()

{

long l;

cout << "Type a number: ";

cin >> l;

cout << "!" << l << " = " << factorial (l);

return 0;

}

Kết quả:

Type a number: 9!9 = 362880

Chú ý trong hàm factorial chúng ta có thể lệnh gọi chính nó nhưng chỉ khi tham số lớn hơn 1, nếu không thì hàm sẽ thực hiện một vòng lặp vô hạn vì sau khi đến 0 nó sẽ tiếp tục nhân cả những số âm.

Hàm này có một hạn chế là kiểu dữ liệu mà nó dùng (long) không cho phép tính giai thừa quá 12!.

Khai báo mẫu cho hàm.

Cho đến giờ chúng ta hoàn toàn phải định nghĩa hàm trước lệnh gọi đầu tiên đến nó, mà thường là trong main, vì vậy hàm main luôn phải nằm cuối chương trình. Nếu bạn thử lặp lại một vài ví dụ về hàm trước đây nhưng thử đặt hàm main trước bất kì một hàm được gọi từ nó, bạn gần như chắc chắn sẽ nhận được thông báo lỗi. Nguyên nhân là một hàm phải được khai báo trước khi nó được gọi như nhưnggx gì chúng ta đã làm trng tất cả các ví dụ.

Nhưng có một cách khác để tránh phải viết tất cả mã chương trình trước khi chúng có thể được dùng trong main hay bất kì một hàm nào khác. Đó chính là khai báo mẫu cho hàm. Cách này bao gồm việc khai báo hàm một cách ngắn gọn nhưng đủ để cho trình dịch có thể biết các tham số và kiểu dữ liệu trả về của hàm.

Dạng của nó như sau:

type name ( argument_type1 , argument_type2 , ...);

Đây chính là phần đầu của định nghĩa hàm, ngoại trừ:

  • Nó không có bất kì lệnh nào cho hàm. Điều này có nghĩa là nó không bao gồm thân hàm với tất cả các lệnh thường được bọc trong cặp ngoặc nhọn { }.
  • Nó kết thúc bằng dấu chấm phẩy (;).
  • Trong phần liệt kê các tham số chỉ cần viết kiểu của chúng là đủ. Việc viết tên của các tham số trong phần khai báo mẫu là không bắt buộc.

Ví dụ:

Code:

// prototyping

#include <iostream.h>

void odd (int a);

void even (int a);

int main ()

{

int i;

do {

cout << "Type a number: (0 to exit)";

cin >> i;

odd (i);

} while (i!=0);

return 0;

}

void odd (int a)

{

if ((a%2)!=0) cout << "Number is odd. ";

else even (a);

}

 

void even (int a)

{

if ((a%2)==0) cout << "Number is even. ";

else odd (a);

}

Kết quả:

Type a number (0 to exit): 9Number is odd.Type a number (0 to exit): 6Number is even.Type a number (0 to exit): 1030Number is even.Type a number (0 to exit): 0Number is even.

Ví dụ này rõ ràng không phải là một ví dụ về sự hiệu quả. Tôi chắc chắn rằng các bạn có thể nhận được kết quả như trên chỉ với một nửa số dòng lệnh. Tuy nhiên nó giúp cho chúng ta thấy được việc khai báo mẫu các hàm là như thế nào. Hơn nữa, trong ví dụ này việc khai báo mẫu ít nhất một hàm là bắt buộc.

Đầu tiên chúng ta thấy khai báo mẫu của hai hàm oddeven:

void odd (int a);void even (int a);

cho phép hai hàm này có thể được sử dụng trước khi chúng được định nghĩa hoàn chỉnh. Tuy nhiên lý do đặc biệt giải thích tại sao chương trình này lại cần ít nhất một hàm phải được khi báo mẫu là trong odd có một lời gọi đến even và trong even có một lời gọi đến odd. Vì vậy nếu không có hàm nào được khai báo trước thì lỗi chắc chắn sẽ xẩy ra.

style="BORDER-RIGHT: medium none; PADDING-RIGHT: 0in; BORDER-TOP: medium none; PADDING-LEFT: 0in; PADDING-BOTTOM: 0in; BORDER-LEFT: medium none; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 3pt solid">

Rất nhiều lập trình viên kinh nghiệm khuyên rằng tất cả các hàm nên được khai báo mẫu. Đó cũng là lời khuyên của tôi, nhất là trong trường hợp có nhiều hàm hoặc chúng rất dài, khi đó việc khai báo tất cả các hàm ở cùng một chỗ cho phép chúng ta biết phải gọi các hàm như thế nào, vì vậy tiết kiệm được thời gian.

0