Bài 8.5: Bài tập thực hành hàm cơ bản trong C++

Bài 8.5: Bài tập thực hành hàm cơ bản trong C++
Chào mừng các bạn quay trở lại với chuỗi bài viết về C++!
Chúng ta đã đi qua lý thuyết về hàm (functions) trong C++ - một khái niệm cực kỳ quan trọng giúp chúng ta tổ chức code, tái sử dụng logic và làm cho chương trình dễ đọc, dễ bảo trì hơn. Tuy nhiên, lý thuyết chỉ là bước khởi đầu. Để thực sự hiểu và thành thạo, chúng ta cần phải thực hành.
Bài viết này sẽ tập trung vào việc cung cấp cho bạn một loạt các bài tập thực hành về hàm cơ bản. Mỗi bài tập sẽ đi kèm với code minh họa và giải thích ngắn gọn để bạn có thể nắm vững từng khía cạnh của việc sử dụng hàm.
Hãy cùng bắt đầu nào!
Mục tiêu của việc thực hành hàm
Trước khi đi sâu vào các bài tập, hãy nhắc lại tại sao việc thực hành lại quan trọng:
- Củng cố kiến thức: Áp dụng lý thuyết vào thực tế giúp bạn nhớ lâu hơn và hiểu sâu hơn về cách hoạt động của hàm (khai báo, định nghĩa, gọi hàm, tham số, giá trị trả về).
- Phát triển kỹ năng: Luyện tập giúp bạn viết code đúng cú pháp, logic mạch lạc khi sử dụng hàm.
- Tư duy phân rã vấn đề: Học cách nhìn một bài toán lớn và chia nhỏ nó thành các hàm nhỏ hơn, dễ quản lý hơn.
- Tự tin: Càng viết code nhiều, bạn càng tự tin hơn khi đối mặt với các bài toán lập trình thực tế.
Bây giờ, chúng ta sẽ đi qua các bài tập từ đơn giản đến phức tạp hơn một chút.
Bài tập 1: Hàm "Hello World" đơn giản nhất
Đây là bài tập kinh điển để bắt đầu. Chúng ta sẽ tạo một hàm không nhận tham số và không trả về giá trị, chỉ đơn giản là in ra một dòng chữ.
Mục tiêu: Nắm vững cấu trúc cơ bản của một hàm đơn giản nhất (void
và không có tham số).
Code:
#include <iostream>
// Khai báo và định nghĩa hàm
void print_hello() {
cout << "Xin chao tu mot ham!\n";
}
int main() {
// Goi ham
print_hello();
return 0;
}
Giải thích:
- Hàm
print_hello
được định nghĩa với từ khóavoid
ở đầu, chỉ rõ rằng nó không trả về bất kỳ giá trị nào sau khi thực thi xong. - Cặp dấu ngoặc đơn
()
sau tên hàm cho biết nó không nhận bất kỳ tham số nào khi được gọi. - Bên trong cặp dấu ngoặc nhọn
{}
là thân hàm, chứa các câu lệnh sẽ được thực hiện khi hàm được gọi. Ở đây chỉ có một câu lệnh in ra màn hình. - Trong hàm
main
, chúng ta gọi hàmprint_hello()
bằng cách viết tên hàm theo sau là cặp dấu ngoặc đơn và dấu chấm phẩy;
. Khi dòng này được thực thi, chương trình sẽ nhảy đến thân hàmprint_hello
, thực hiện các câu lệnh trong đó, và sau đó quay trở lại điểm gọi hàm trongmain
để tiếp tục.
Bài tập 2: Hàm với tham số đầu vào
Các hàm thường cần thông tin từ bên ngoài để thực hiện công việc của chúng. Thông tin này được truyền vào thông qua tham số.
Mục tiêu: Hiểu cách định nghĩa và sử dụng hàm có nhận tham số.
Bài toán: Viết hàm nhận vào tên của một người và in ra lời chào cá nhân hóa.
Code:
#include <iostream>
#include <string> // Can de su dung string
// Ham nhan mot tham so kieu string
void greet_person(string name) {
cout << "Xin chao, " << name << "!\n";
}
int main() {
string user_name = "Doc gia than men";
// Goi ham va truyen gia tri cho tham so
greet_person(user_name);
greet_person("Mickey"); // Co the truyen truc tiep chuoi ky tu
return 0;
}
Giải thích:
- Hàm
greet_person
có định nghĩavoid greet_person(string name)
. Điều này có nghĩa là:- Nó vẫn không trả về giá trị (
void
). - Nó nhận một tham số có kiểu dữ liệu là
string
, và bên trong hàm, tham số này sẽ được gọi bằng tên làname
.
- Nó vẫn không trả về giá trị (
- Khi gọi hàm
greet_person
trongmain
, chúng ta phải cung cấp một giá trị có kiểustring
bên trong cặp dấu ngoặc đơn. Giá trị này (ví dụ"Doc gia than men"
hoặc"Mickey"
) sẽ được gán vào biếnname
cục bộ bên trong hàmgreet_person
khi hàm đó được thực thi. - Giá trị truyền vào khi gọi hàm được gọi là đối số (argument). Biến nhận giá trị đó trong định nghĩa hàm là tham số (parameter).
Bài tập 3: Hàm trả về giá trị
Nhiều hàm thực hiện một phép tính hoặc xử lý dữ liệu và cần trả về kết quả cho nơi đã gọi nó.
Mục tiêu: Hiểu cách định nghĩa hàm có giá trị trả về và sử dụng từ khóa return
.
Bài toán: Viết hàm tính bình phương của một số nguyên.
Code:
#include <iostream>
// Ham nhan mot tham so kieu int va tra ve mot gia tri kieu int
int square(int number) {
int result = number * number;
return result; // Tra ve gia tri cua bien result
}
// Hoac viet ngan gon hon:
// int square(int number) {
// return number * number; // Tinh toan va tra ve truc tiep
// }
int main() {
int input_number = 5;
// Goi ham va luu gia tri tra ve vao mot bien
int squared_value = square(input_number);
cout << "Binh phuong cua " << input_number << " la: " << squared_value << "\n";
cout << "Binh phuong cua 10 la: " << square(10) << "\n"; // Co the su dung truc tiep gia tri tra ve
return 0;
}
Giải thích:
- Hàm
square
được định nghĩa với kiểu trả về làint
ở đầu (int square(...)
). Điều này có nghĩa là sau khi thực thi, hàm sẽ tính toán và trả về một giá trị có kiểuint
. - Nó nhận một tham số là
number
kiểuint
. - Bên trong thân hàm, chúng ta tính
number * number
. - Từ khóa
return
được sử dụng để kết thúc việc thực thi hàm và gửi giá trị theo sau nó trở về nơi gọi hàm. Trong trường hợp này, nó trả về giá trị củaresult
(hoặc kết quả tính toán trực tiếpnumber * number
). - Trong
main
, khi gọisquare(input_number)
, giá trị25
(vìinput_number
là5
) sẽ được trả về và gán vào biếnsquared_value
. Chúng ta cũng có thể sử dụng trực tiếp giá trị trả về của hàm trong các biểu thức hoặc câu lệnh khác, nhưcout << square(10)
.
Bài tập 4: Hàm có nhiều tham số
Hàm có thể nhận nhiều tham số để thực hiện các tác vụ phức tạp hơn.
Mục tiêu: Hiểu cách định nghĩa và gọi hàm với nhiều tham số.
Bài toán: Viết hàm tính tổng của hai số thực.
Code:
#include <iostream>
// Ham nhan hai tham so kieu double va tra ve mot gia tri kieu double
double sum(double a, double b) {
return a + b;
}
int main() {
double number1 = 15.5;
double number2 = 20.3;
// Goi ham voi hai doi so
double total = sum(number1, number2);
cout << "Tong cua " << number1 << " va " << number2 << " la: " << total << "\n";
cout << "Tong cua 100 va 200 la: " << sum(100.0, 200.0) << "\n";
return 0;
}
Giải thích:
- Hàm
sum
có định nghĩadouble sum(double a, double b)
.- Kiểu trả về là
double
. - Nó nhận hai tham số được phân cách bởi dấu phẩy:
a
kiểudouble
vàb
kiểudouble
.
- Kiểu trả về là
- Khi gọi hàm
sum
trongmain
, chúng ta phải cung cấp hai đối số có kiểu dữ liệu tương thích vớidouble
. Thứ tự của đối số khi gọi hàm phải khớp với thứ tự của tham số trong định nghĩa hàm. Giá trị của đối số thứ nhất (number1
) sẽ được gán vào tham sốa
, và giá trị của đối số thứ hai (number2
) sẽ được gán vào tham sốb
. - Hàm thực hiện phép cộng và trả về kết quả.
Bài tập 5: Hàm phân loại số
Kết hợp kiến thức về tham số, giá trị trả về và câu lệnh điều kiện (if
).
Mục tiêu: Luyện tập sử dụng logic điều kiện bên trong hàm và trả về các giá trị khác nhau dựa trên điều kiện.
Bài toán: Viết hàm kiểm tra một số nguyên là số dương, số âm hay số 0. Hàm sẽ trả về một chuỗi ký tự mô tả kết quả.
Code:
#include <iostream>
#include <string>
// Ham nhan mot so nguyen, tra ve chuoi mo ta
string classify_number(int num) {
if (num > 0) {
return "So duong";
} else if (num < 0) {
return "So am";
} else {
return "So 0";
}
}
int main() {
int number_to_check;
cout << "Nhap mot so nguyen: ";
cin >> number_to_check;
// Goi ham va in ket qua
string result = classify_number(number_to_check);
cout << "So ban vua nhap la: " << result << "\n";
cout << "-5 la: " << classify_number(-5) << "\n";
cout << "0 la: " << classify_number(0) << "\n";
return 0;
}
Giải thích:
- Hàm
classify_number
nhận một tham sốnum
kiểuint
và được khai báo sẽ trả về một giá trị kiểustring
. - Bên trong hàm, chúng ta sử dụng cấu trúc
if-else if-else
để kiểm tra giá trị củanum
. - Dựa trên kết quả kiểm tra, hàm sẽ trả về một trong ba chuỗi ký tự khác nhau (
"So duong"
,"So am"
, hoặc"So 0"
) bằng cách sử dụng từ khóareturn
. Ngay khi một câu lệnhreturn
được thực thi, hàm sẽ kết thúc. - Trong
main
, chúng ta lấy input từ người dùng, sau đó gọi hàmclassify_number
với input đó và lưu kết quả chuỗi trả về vào biếnresult
để in ra.
Bài tập 6: Hàm gọi hàm khác
Một hàm có thể gọi (sử dụng) một hàm khác để thực hiện một phần công việc của mình. Điều này giúp chúng ta phân rã các tác vụ phức tạp thành các hàm nhỏ hơn, dễ quản lý và tái sử dụng hơn.
Mục tiêu: Hiểu cách các hàm có thể tương tác và sử dụng lại logic từ hàm khác.
Bài toán: Viết hàm kiểm tra xem một năm có phải là năm nhuận hay không. Sử dụng một hàm phụ trợ để kiểm tra tính chia hết.
Code:
#include <iostream>
// Ham phu tro: kiem tra a co chia het cho b khong
bool is_divisible(int a, int b) {
// Tranh chia cho 0
if (b == 0) return false;
return (a % b == 0);
}
// Ham chinh: kiem tra nam nhuan
// Mot nam la nam nhuan neu:
// (1) No chia het cho 400
// HOAC
// (2) No chia het cho 4 nhung khong chia het cho 100
bool is_leap_year(int year) {
// Goi ham phu tro is_divisible de kiem tra cac dieu kien
if (is_divisible(year, 400)) {
return true; // Dieu kien (1)
} else if (is_divisible(year, 4) && !is_divisible(year, 100)) {
return true; // Dieu kien (2)
} else {
return false; // Khong thoa man dieu kien nao
}
}
int main() {
int year1 = 2000;
int year2 = 1900;
int year3 = 2024;
int year4 = 2023;
cout << year1 << " co phai nam nhuan khong? " << (is_leap_year(year1) ? "Co" : "Khong") << "\n";
cout << year2 << " co phai nam nhuan khong? " << (is_leap_year(year2) ? "Co" : "Khong") << "\n";
cout << year3 << " co phai nam nhuan khong? " << (is_leap_year(year3) ? "Co" : "Khong") << "\n";
cout << year4 << " co phai nam nhuan khong? " << (is_leap_year(year4) ? "Co" : "Khong") << "\n";
return 0;
}
Giải thích:
- Chúng ta có hai hàm:
is_divisible
vàis_leap_year
. - Hàm
is_divisible
là một hàm phụ trợ đơn giản, có nhiệm vụ kiểm tra xem sốa
có chia hết cho sốb
hay không và trả về giá trị kiểubool
(true
hoặcfalse
). Lưu ý việc xử lý trường hợpb
bằng 0. - Hàm
is_leap_year
nhận vào một năm và kiểm tra các điều kiện của năm nhuận. Quan trọng là, bên trong hàmis_leap_year
, chúng ta gọi hàmis_divisible
để thực hiện việc kiểm tra chia hết, thay vì viết lại logic% b == 0
nhiều lần. - Điều này minh họa cách chúng ta có thể xây dựng các hàm phức tạp hơn bằng cách kết hợp các hàm đơn giản hơn, tăng tính mô-đun và khả năng tái sử dụng của code.
Bài tập 7: Sử dụng tham chiếu (Pass by Reference)
Mặc định, khi truyền đối số vào hàm trong C++, giá trị của đối số được sao chép vào tham số của hàm (pass by value). Điều này có nghĩa là mọi thay đổi đối với tham số bên trong hàm không ảnh hưởng đến biến gốc ở bên ngoài.
Tuy nhiên, đôi khi chúng ta muốn hàm thay đổi trực tiếp giá trị của biến gốc. Lúc này, chúng ta sử dụng tham chiếu (reference) - hay còn gọi là truyền tham chiếu (pass by reference).
Mục tiêu: Hiểu sự khác biệt giữa truyền giá trị và truyền tham chiếu, và cách sử dụng tham chiếu để hàm thay đổi biến gốc.
Bài toán: Viết hàm hoán đổi giá trị của hai số nguyên sử dụng truyền tham chiếu.
Code:
#include <iostream>
// Ham hoan doi gia tri su dung TRUYEN THAM CHIEU
void swap_values(int& a, int& b) {
cout << " (Inside swap_values) Truoc hoan doi: a=" << a << ", b=" << b << "\n";
int temp = a; // Luu gia tri cua a vao bien tam
a = b; // Gan gia tri cua b cho a (thay doi bien goc)
b = temp; // Gan gia tri tam cho b (thay doi bien goc)
cout << " (Inside swap_values) Sau hoan doi: a=" << a << ", b=" << b << "\n";
}
// Ham hoan doi gia tri su dung TRUYEN GIA TRI (chi de so sanh - KHONG THAY DOI BIEN GOC)
void swap_values_by_value(int a, int b) {
cout << " (Inside swap_values_by_value) Truoc hoan doi: a=" << a << ", b=" << b << "\n";
int temp = a;
a = b; // Thay doi ban sao cua a
b = temp; // Thay doi ban sao cua b
cout << " (Inside swap_values_by_value) Sau hoan doi: a=" << a << ", b=" << b << "\n";
}
int main() {
int x = 10;
int y = 20;
cout << "Truoc khi goi swap_values: x=" << x << ", y=" << y << "\n";
swap_values(x, y); // Goi ham truyen tham chieu
cout << "Sau khi goi swap_values: x=" << x << ", y=" << y << "\n";
cout << "-------------------------------------\n";
int p = 30;
int q = 40;
cout << "Truoc khi goi swap_values_by_value: p=" << p << ", q=" << q << "\n";
swap_values_by_value(p, q); // Goi ham truyen gia tri
cout << "Sau khi goi swap_values_by_value: p=" << p << ", q=" << q << "\n";
return 0;
}
Giải thích:
- Trong hàm
swap_values
, các tham số được khai báo làint& a
vàint& b
. Dấu&
sau kiểu dữ liệu chỉ ra rằnga
vàb
là tham chiếu đến các biến gốc được truyền vào khi gọi hàm. - Khi bạn gọi
swap_values(x, y)
,a
trở thành một bí danh cho biếnx
vàb
trở thành một bí danh cho biếny
. Mọi thao tác thay đổi giá trị củaa
hoặcb
bên trong hàm thực sự đang thay đổi giá trị củax
hoặcy
bên ngoài hàm. - Trong hàm
swap_values_by_value
(chỉ để so sánh), các tham số làint a
vàint b
thông thường (truyền giá trị). Khi gọiswap_values_by_value(p, q)
, các giá trịp
vàq
được sao chép vào các biếna
vàb
cục bộ bên trong hàm. Mọi thay đổi đối vớia
vàb
chỉ xảy ra trên các bản sao này và không ảnh hưởng đếnp
vàq
ở bên ngoài. - Kết quả chạy chương trình sẽ minh họa rõ sự khác biệt này:
x
vày
sẽ bị hoán đổi, trong khip
vàq
thì không.
Bài tập 8: Tính giai thừa sử dụng đệ quy (Recursion)
Đệ quy là một kỹ thuật mà một hàm gọi lại chính nó để giải quyết một bài toán. Đây là một khái niệm nâng cao hơn một chút nhưng là một ứng dụng quan trọng của hàm.
Mục tiêu: Làm quen với khái niệm đệ quy thông qua một ví dụ kinh điển (tính giai thừa).
Bài toán: Viết hàm tính giai thừa của một số nguyên không âm n
(n!
). Giai thừa của 0 là 1. Với n > 0
, n! = n * (n-1)!
.
Code:
#include <iostream>
// Ham tinh giai thua su dung de quy
long long factorial(int n) {
// Truong hop co so (Base Case): Diem dung cua de quy
if (n == 0) {
return 1;
}
// Buoc de quy (Recursive Step): Goi lai chinh ham voi tham so nho hon
else {
return (long long)n * factorial(n - 1);
}
}
int main() {
int number;
cout << "Nhap mot so nguyen khong am: ";
cin >> number;
if (number < 0) {
cout << "Giai thua chi tinh cho so nguyen khong am.\n";
} else {
// Goi ham de quy
long long result = factorial(number);
cout << "Giai thua cua " << number << " (" << number << "!) la: " << result << "\n";
}
// Voi cac so nho hon
cout << "5! = " << factorial(5) << "\n";
cout << "0! = " << factorial(0) << "\n";
return 0;
}
Giải thích:
- Hàm
factorial
được định nghĩa nhận một số nguyênn
và trả về một giá trị kiểulong long
(để chứa kết quả giai thừa có thể rất lớn). - Trường hợp cơ sở (Base Case): Điều quan trọng nhất trong đệ quy là phải có một hoặc nhiều điểm dừng để tránh việc hàm gọi vô hạn. Ở đây, khi
n
bằng 0, hàm trả về1
và không gọi lại chính nó nữa. Đây là điểm dừng. - Bước đệ quy (Recursive Step): Nếu
n
lớn hơn 0, hàm trả về kết quả củan
nhân với giai thừa củan-1
(bằng cách gọi lại chính hàmfactorial
với đối sốn-1
). - Quá trình này tiếp diễn:
factorial(n)
gọifactorial(n-1)
,factorial(n-1)
gọifactorial(n-2)
, ..., cho đến khi gọifactorial(0)
, lúc này nó trả về1
và quá trình gọi ngược bắt đầu tính toán kết quả cuối cùng:... * 3 * 2 * 1
. - Đệ quy là một kỹ thuật mạnh mẽ nhưng cần cẩn thận với trường hợp cơ sở và đảm bảo rằng mỗi lần gọi đệ quy đều tiến gần hơn đến trường hợp cơ sở.
Lời khuyên khi thực hành
- Gõ lại code: Đừng chỉ sao chép và dán. Việc tự tay gõ code giúp bạn làm quen với cú pháp và dễ dàng phát hiện lỗi.
- Thay đổi code: Sau khi chạy thử ví dụ, hãy thử thay đổi tham số, kiểu dữ liệu, hoặc logic bên trong hàm để xem kết quả thay đổi như thế nào.
- Tự nghĩ ra bài tập nhỏ: Hãy nhìn xung quanh hoặc các bài toán đơn giản (ví dụ: tính chu vi/diện tích hình chữ nhật, chuyển đổi nhiệt độ từ Celsius sang Fahrenheit, kiểm tra số chẵn/lẻ) và thử viết hàm để giải quyết chúng.
- Sử dụng trình gỡ lỗi (debugger): Nếu gặp khó khăn trong việc hiểu luồng thực thi của hàm (đặc biệt là với đệ quy hoặc truyền tham chiếu), hãy sử dụng debugger để theo dõi từng bước.
Bài tập ví dụ: C++ Bài 4.A5: Tổng chính phương
Hãy viết chương trình tình tổng của \(N\) số chính phương đầu tiên. Ví dụ tổng \(5\) số chính phương đầu tiên sẽ là \(1 + 4 + 9 + 16 + 25 = 55\).
INPUT FORMAT
Dòng đầu tiên chứa giá trị \(N (1 \leq N \leq 10^3)\)
OUTPUT FORMAT
In ra giá trị tổng của N số chính phương đầu tiên.
Ví dụ 1:
Input
5
Ouput
55
Giải thích ví dụ mẫu:
- Ví dụ 1: Tổng 5 số chính phương đầu tiên là \(1 + 4 + 9 + 16 + 25 = 55\) <br>
Mục tiêu: Tính tổng của N số chính phương đầu tiên (1² + 2² + 3² + ... + N²).
Hướng dẫn:
Đọc input: Bạn cần đọc giá trị số nguyên
N
từ đầu vào chuẩn. Để làm được điều này trong C++, bạn sẽ cần bao gồm thư việniostream
và sử dụng đối tượngcin
.Khởi tạo biến tổng: Tạo một biến để lưu trữ tổng các số chính phương. Vì tổng có thể khá lớn khi N = 1000, bạn nên sử dụng kiểu dữ liệu có kích thước lớn hơn
int
, ví dụ nhưlong long
, để tránh tràn số. Khởi tạo biến này với giá trị ban đầu là 0.Lặp và tính tổng: Sử dụng một vòng lặp để duyệt qua các số nguyên từ 1 đến
N
.- Trong mỗi lần lặp với chỉ số
i
(từ 1 đếnN
), bạn cần tính giá trị bình phương củai
, tức lài * i
. - Sau đó, cộng giá trị bình phương vừa tính được vào biến tổng đã khởi tạo ở bước 2.
- Trong mỗi lần lặp với chỉ số
In kết quả: Sau khi vòng lặp kết thúc (đã duyệt qua hết các số từ 1 đến
N
), giá trị cuối cùng của biến tổng chính là kết quả cần tìm. Sử dụngcout
(từ thư việniostream
) để in giá trị này ra màn hình. Đừng quên xuống dòng sau khi in kết quả bằngendl
hoặc'\n'
.
Tóm lại các bước chính cần thực hiện trong code C++:
- Include
iostream
. - Viết hàm
main
. - Khai báo biến
N
(int). - Khai báo biến
sum
(long long), gán giá trị ban đầu là 0. - Dùng vòng lặp (ví dụ:
for
loop) với biến đếmi
chạy từ 1 đếnN
. - Bên trong vòng lặp: tính
i * i
và cộng vàosum
. - Sau vòng lặp: in
sum
ra màn hình. - Return 0 từ hàm
main
.
Comments