Hãy xem xét chương trình sau:

#include <iostream>
 
int main()
{
	// get a value from the user
	std::cout << "Enter an integer: ";
	int num{};
	std::cin >> num;
 
	// print the value doubled
	std::cout << num << " doubled is: " << num * 2 << '\n';
 
	return 0;
}

Chương trình này bao gồm hai phần khái niệm: Đầu tiên, chúng ta nhận được một giá trị từ người dùng. Sau đó, chúng ta sẽ cho cho người dùng xem giá trị gấp đôi đó.

Mặc dù chương trình này khá đơn gian mà chúng ta không cần phải chia nó thành nhiều hàm, nếu chúng ta muốn chia thì sao? Nhận giá trị số nguyên từ người dùng nhập vào là một công việc được rõ ràng mà chúng ta muốn chương trình của chúng ta thực hiện, vì vậy chúng ta có thể tạo ra một hàm để làm điều đó.

Vì vậy, hãy để Tôi viết một chương trình để làm điều này như sau:

// This program doesn't work
#include <iostream>
 
void getValueFromUser()
{
 	std::cout << "Enter an integer: ";
	int input{};
	std::cin >> input;  
}
 
int main()
{
	getValueFromUser(); // Ask user for input
 
	int num{}; // How do we get the value from getValueFromUser() and use it to initialize this variable?
 
	std::cout << num << " doubled is: " << num * 2 << '\n';
 
	return 0;
}

Mặc dù chương trình này là một giải pháp, nhưng nó không hoạt động tốt.

Khi hàm getValueFromUser được gọi, người dùng được yêu cầu nhập một số nguyên như mong đợi. Nhưng giá trị họ nhập bị mất khi getValueFromUser chấm dứt và trở về hàm main. Nên Biến num không bao giờ được khởi tạo với giá trị người dùng đã nhập và do đó chương trình luôn in ra câu trả lời là 0.

Những gì chúng ta thiếu là một cách nào đó để getValueFromUser trả lại giá trị mà người dùng đã nhập trở lại hàm main để hàm main có thể sử dụng dữ liệu đó.

1. Hàm Trả về giá trị

Khi bạn viết một hàm, bạn có thể xác định xem hàm của bạn có trả lại giá trị cho người gọi hay không. Để trả lại giá trị cho người gọi, cần có hai điều.

Đầu tiên, Hàm của bạn phải cho biết kiểu giá trị nào sẽ được trả về. Điều này được thực hiện bằng cách thiết lập kiểu trả về của hàm, đây là kiểu được định nghĩa trước tên của hàm. Trong ví dụ trên, hàm getValueFromUser có kiểu trả về void và hàm main có kiểu trả về int. Lưu ý rằng kiểu void này không xác định giá trị cụ thể nào được trả về.

Thứ hai, bên trong hàm sẽ trả về một giá trị, chúng ta sử dụng câu lệnh return để chỉ ra giá trị cụ thể được trả về cho người gọi. Giá trị cụ thể được trả về từ một hàm được gọi là giá trị trả về. Khi câu lệnh return được thực thi, giá trị trả về được sao chép từ hàm trở lại cho người gọi.

Chúng ta hãy xem một hàm đơn giản sau đây trả về một giá trị nguyên:

#include <iostream>
 
// int is the return type
// A return type of int means the function will return some integer value to the caller (the specific value is not specified here)
int returnFive()
{
    // the return statement indicates the specific value that will be returned
    return 5; // return the specific value 5 back to the caller
}
 
int main()
{
    std::cout << returnFive() << '\n'; // prints 5
    std::cout << returnFive() + 2 << '\n'; // prints 7
 
    returnFive(); // okay: the value 5 is returned, but is ignored since main() doesn't do anything with it
 
    return 0;
}

Khi chạy, chương trình này in ra:

5
7

Thực hiện bắt đầu ở đầu ở hàm main. Trong câu lệnh đầu tiên, lệnh gọi hàm returnFive được thực hiện, dẫn đến hàm returnFive được gọi. Hàm returnFive trả về giá trị cụ thể là 5 và nó được trả lại cho người gọi, sau đó được in ra console thông qua std :: cout.

Trong lệnh gọi hàm thứ hai, lệnh gọi hàm returnFive được thực hiện, dẫn đến hàm returnFive được gọi lại. Hàm returnFive trả về giá trị là 5 cho người gọi. Biểu thức 5 + 2 được thực thi để tạo ra kết quả 7, sau đó được in ra console thông qua std :: cout.

Trong câu lệnh thứ ba, hàm returnFive được gọi lại, dẫn đến giá trị là 5 được trả về cho người gọi. Tuy nhiên, hàm main không làm gì với giá trị trả về, vì vậy không có gì xảy ra nữa (giá trị trả về bị bỏ qua).

Lưu ý: Giá trị trả về sẽ không được in trừ khi người gọi gửi chúng đến console thông qua std :: cout. Trong trường hợp cuối cùng ở trên, giá trị trả về không được gửi đến std :: cout, vì vậy không có gì được in ra.

2. Sửa chữa chương trình sau

Với suy nghĩ này, chúng ta có thể sửa chương trình ở trên đầu bài học như sau:

#include <iostream>
 
int getValueFromUser() // this function now returns an integer value
{
 	std::cout << "Enter an integer: ";
	int input{};
	std::cin >> input;  
 
	return input; // return the value the user entered back to the caller
}
 
int main()
{
	int num { getValueFromUser() }; // initialize num with the return value of getValueFromUser()
 
	std::cout << num << " doubled is: " << num * 2 << '\n';
 
	return 0;
}

Khi chương trình này thực thi, câu lệnh đầu tiên trong main sẽ tạo ra một biến int có tên num. Khi chương trình khởi tạo num, nó sẽ thấy rằng có một lệnh gọi hàm getValueFromUser, vì vậy nó sẽ thực thi hàm đó. Hàm getValueFromUser, yêu cầu người dùng nhập một giá trị và sau đó nó trả lại giá trị đó cho người gọi (là hàm main). Giá trị trả về này được sử dụng làm giá trị khởi tạo cho num biến.

Tự biên dịch chương trình này và chạy nó một vài lần để chứng minh với chính mình rằng nó hoạt động.

3. Hàm trả về giá trị trống(Trả về kiểu Void)

Các hàm không bắt buộc phải trả về một giá trị. Để báo cho trình biên dịch rằng một hàm không trả về giá trị, một kiểu return void được sử dụng. Chúng ta hãy nhìn vào hàm doPrint() từ bài học trước:

void doPrint() // void is the return type
{
    std::cout << "In doPrint()" << '\n';
    // This function does not return a value so no return statement is needed
}

Hàm này có kiểu trả về là void, chỉ ra rằng nó không trả về giá trị cho người gọi. Bởi vì nó không trả về một giá trị, không cần câu lệnh trả về (cố gắng trả về một giá trị cụ thể từ một hàm có kiểu trả về void sẽ dẫn đến lỗi biên dịch).

Ở đây, một ví dụ khác về hàm trả về void và chương trình mẫu như sau:

#include <iostream>
 
// void means the function does not return a value to the caller
void returnNothing()
{
    std::cout << "Hi" << '\n';
    // This function does not return a value so no return statement is needed
}
 
int main()
{
    returnNothing(); // okay: function returnNothing() is called, no value is returned
 
    std::cout << returnNothing(); // error: this line will not compile.  You'll need to comment it out to continue.
 
    return 0;
}

Hàm đầu tiên được gọi là hàm returnNothing, hàm sẽ in ra Hi và sau đó trả lại không có gì cho người gọi. Trở về hàm main và chương trình tiếp tục.

Hàm thứ hai được gọi là hàm returnNothing. Hàm returnNothing có kiểu trả về void, nghĩa là nó không trả về giá trị. Tuy nhiên, câu lệnh này đang cố gắng gửi giá trị trả về của returnNothing đến std :: cout sẽ được in. std :: cout sẽ không biết cách xử lý việc này (nó sẽ tạo ra giá trị gì?). Do đó, trình biên dịch sẽ đánh dấu lỗi này. Bạn cần phải comment dòng code này để biên dịch code của bạn.

Một kiểu trả về là void (có nghĩa là không có gì được trả về) được sử dụng khi chúng ta muốn có một hàm mà không phải trả lại bất cứ thứ gì cho người gọi (vì nó không cần). Trong ví dụ trên, hàm returnNothing có hành vi in ra chữ nhưng nó không cần phải trả lại bất cứ thứ gì cho người gọi (trong trường hợp này là hàm main). Do đó, returnNothing được cung cấp một kiểu trả về void.

4. Trả về trong hàm main

Bây giờ bạn có các công cụ và khái niệm để hiểu cách thức hoạt động của hàm main. Khi chương trình được thực thi, hệ điều hành thực hiện một cuộc gọi hàm main. Các câu lệnh trong hàm main được thực hiện tuần tự từ đầu hàm tới cuối hàm. Cuối cùng, hàm main trả về một giá trị nguyên (thường là 0) trở lại hệ điều hành. Đây là lý do tại sao hàm main được định nghĩa là int main().

Tại sao trả lại một giá trị cho hệ điều hành? Giá trị này được gọi là code trạng thái và nó báo cho hệ điều hành (và bất kỳ chương trình nào khác của bạn) xem chương trình của bạn có thực hiện thành công hay không. Theo sự thống nhất, giá trị trả về bằng 0 có nghĩa là thành công và giá trị trả về khác không có nghĩa là thất bại.

Hàm main của bạn sẽ trả về 0 nếu chương trình chạy bình thường hoặc giá trị khác không nếu nó gặp lỗi và không thể hoàn thành như mong đợi.

Lưu ý rằng đặc tả của C ++ chỉ định rõ ràng rằng hàm chính phải trả về một int. Một số trình biên dịch sẽ không cho phép bạn chỉ định loại trả về void (chúng sẽ hoàn toàn trả về 0 nếu bạn làm điều này), nhưng bạn không nên dựa vào điều này.

Hiện tại, bạn cũng nên định nghĩa hàm main() ở cuối tệp code của mình, bên dưới các hàm khác.

5. Một vài lưu ý về giá trị trả về

Đầu tiên, nếu một hàm có kiểu trả về không rỗng, thì nó phải trả về giá trị của kiểu nào đó (sử dụng câu lệnh return). Không làm như vậy sẽ dẫn đến lỗi.

Luôn luôn cung cấp một giá trị trả về rõ ràng cho bất kỳ hàm nào có kiểu trả về không rỗng.

Việc không trả về giá trị từ một hàm có kiểu trả về không rỗng (trừ main) sẽ dẫn đến hành vi lỗi.

Thứ hai, khi một câu lệnh return được thực thi, hàm sẽ trả về cho người gọi ngay lập tức tại điểm đó. Bất kỳ code bổ sung nào trong hàm sau đều được bỏ qua.

Thứ ba, một hàm chỉ có thể trả về một giá trị duy nhất cho người gọi mỗi khi nó được gọi. Tuy nhiên, giá trị không cần phải là một chữ, nó có thể là kết quả của bất kỳ biểu thức hợp lệ nào, bao gồm một biến hoặc thậm chí là một cuộc gọi đến một hàm khác trả về một giá trị. Trong ví dụ getValueFromUser() ở trên, chúng ta đã trả về một biến giữ số mà người dùng đã nhập.

Cuối cùng, lưu ý rằng Một số hàm sử dụng giá trị trả về làm code trạng thái, để cho biết chúng thành công hay thất bại. Các hàm khác trả về giá trị được tính hoặc đã chọn. Các hàm khác không trả lại gì cả. Hàm trả về là gì và ý nghĩa của giá trị đó được xác định bởi tác giả của hàm. Do có nhiều hàm khác nhau ở đây, nên một ý tưởng tốt là ghi lại chức năng mà hàm của bạn có thể làm bằng một comment cho biết giá trị trả về có nghĩa là gì.

Ví dụ:

// Function asks user to enter a value
// Return value is the integer entered by the user from the keyboard
int getValueFromUser()
{
 	std::cout << "Enter an integer: ";
	int input{};
	std::cin >> input;  
 
	return input; // return the value the user entered back to the caller
}

6. Sử dụng lại các hàm

Bây giờ chúng ta có thể minh họa một trường hợp tốt để tái sử dụng hàm. Hãy xem xét chương trình sau:

#include <iostream>
 
int main()
{
	int x{};
	std::cout << "Enter an integer: ";
	std::cin >> x; 
 
	int y{};
	std::cout << "Enter an integer: ";
	std::cin >> y; 
 
	std::cout << x << " + " << y << " = " << x + y << '\n';
 
	return 0;
}

Trong khi chương trình này hoạt động, nó có một chút dư thừa. Trên thực tế, chương trình này vi phạm một trong những nguyên lý trọng tâm của lập trình tốt đó là: Bạn không nên lặp lại code giống nhau (thường được viết tắt là DRY).

Tại sao code lặp lại là xấu? Nếu chúng ta muốn thay đổi văn bản, Nhập một số nguyên thành nhập một số khác, chúng ta phải cập nhật nó ở hai vị trí. Và nếu chúng ta muốn khởi tạo 10 biến thay vì 2 thì sao? Đó sẽ tốn rất nhiều code (làm cho các chương trình của chúng ta dài hơn và khó hiểu hơn), và rất nhiều chỗ cho lỗi chính tả xuất hiện.

Hãy cập nhật chương trình này bằng cách sử dụng hàm getValueFromUser mà chúng ta đã phát triển ở trên:

#include <iostream>
 
int getValueFromUser()
{
 	std::cout << "Enter an integer: ";
	int input{};
	std::cin >> input;  
 
	return input;
}
 
int main()
{
    int x{ getValueFromUser() }; // first call to getValueFromUser
    int y{ getValueFromUser() }; // second call to getValueFromUser
 
    std::cout << x << " + " << y << " = " << x + y << '\n';
 
    return 0;
}

Chương trình này tạo ra đầu ra như sau:

Enter an integer: 5
Enter an integer: 7
5 + 7 = 12

Trong chương trình này, chúng ta gọi getValueFromUser hai lần, một lần để khởi tạo biến x và một lần để khởi tạo biến y. Điều đó tiết kiệm cho chúng ta khỏi việc sao chép code để có được đầu vào của người dùng và giảm tỷ lệ mắc lỗi.

Đây là bản chất của lập trình mô-đun: khả năng viết một hàm, kiểm tra nó, đảm bảo rằng nó hoạt động và sau đó biết rằng chúng ta có thể tái sử dụng nó nhiều lần như chúng ta muốn và nó sẽ tiếp tục hoạt động (miễn là chúng ta không sửa đổi hàm – tại thời điểm đó chúng ta sẽ phải kiểm tra lại nó).

Thực hiện theo cách thực hành của DRY: Nếu bạn cần làm nhiều thứ hơn một lần, hãy xem xét cách sửa đổi code của bạn để loại bỏ càng nhiều dư thừa càng tốt. Các biến có thể được sử dụng để lưu trữ kết quả của các phép tính cần được sử dụng nhiều lần (vì vậy chúng ta không thể lặp lại phép tính). Các hàm có thể được sử dụng để định nghĩa một chuỗi các câu lệnh mà chúng ta muốn thực thi nhiều lần. Và các vòng lặp (mà chúng ta sẽ trình bày trong chương sau) có thể được sử dụng để thực thi một câu lệnh nhiều lần.

7. Phần kết luận

Giá trị trả về cung cấp một cách để các hàm trả về một giá trị duy nhất cho nơi gọi.

Các hàm cung cấp một cách để giảm thiểu sự dư thừa trong các chương trình của chúng ta.

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!

1 BÌNH LUẬN

Bình luận bị đóng.