Bài 8.3: Hàm void và ứng dụng trong C++

Bài 8.3: Hàm void và ứng dụng trong C++
Chào mừng các bạn quay trở lại với chuỗi bài học về C++! Ở các bài trước, chúng ta đã cùng nhau tìm hiểu về khái niệm hàm, cách khai báo, định nghĩa và gọi hàm, cũng như các loại tham số khác nhau. Chúng ta cũng đã quen thuộc với các hàm trả về một giá trị cụ thể, ví dụ như hàm trả về int
, double
, hay string
.
Hôm nay, chúng ta sẽ đi sâu vào một loại hàm đặc biệt, đóng vai trò cực kỳ quan trọng trong lập trình C++: hàm void. Vậy hàm void
là gì và tại sao chúng ta lại cần nó? Hãy cùng khám phá nhé!
Hàm void
là gì?
Trong C++, khi chúng ta khai báo một hàm, chúng ta cần chỉ định kiểu dữ liệu mà hàm đó sẽ trả về sau khi thực hiện xong công việc của mình. Ví dụ:
int congHaiSo(int a, int b) {
return a + b; // Trả về một giá trị kiểu int
}
double tinhDienTichHinhTron(double banKinh) {
return 3.14 * banKinh * banKinh; // Trả về một giá trị kiểu double
}
Tuy nhiên, không phải lúc nào hàm cũng cần trả về một giá trị. Đôi khi, chúng ta muốn hàm chỉ thực hiện một hành động hoặc một tập hợp các công việc mà không cần cung cấp một kết quả tính toán cụ thể cho nơi gọi nó. Đây chính là lúc kiểu trả về void
phát huy tác dụng.
void
trong C++ là một từ khóa đặc biệt, dùng để chỉ ra rằng một hàm không trả về bất kỳ giá trị nào. Nghĩa đen của void
là "trống rỗng" hoặc "không có gì".
Khi khai báo một hàm với kiểu trả về là void
, chúng ta thông báo cho trình biên dịch và các lập trình viên khác biết rằng hàm này sẽ thực hiện một nhiệm vụ nào đó, có thể là hiển thị thông tin ra màn hình, ghi dữ liệu vào file, thay đổi trạng thái của một đối tượng, v.v., nhưng không cung cấp một kết quả để sử dụng trong các biểu thức.
Cú pháp của Hàm void
Cú pháp khai báo và định nghĩa hàm void
cũng tương tự như các hàm thông thường, chỉ khác ở chỗ chúng ta thay kiểu dữ liệu trả về bằng từ khóa void
:
void tenHam(kieuThamSo1 thamSo1, kieuThamSo2 thamSo2, ...) {
// Các câu lệnh thực hiện hành động
// ...
}
Ví dụ đơn giản nhất về một hàm void
:
#include <iostream>
// Khai báo và định nghĩa một hàm void
void chaoMung() {
// Các câu lệnh trong hàm
cout << "Chào mừng bạn đến với thế giới hàm void!" << endl;
cout << "Đây là một hàm không trả về giá trị." << endl;
} // Hàm kết thúc tại đây
int main() {
// Gọi hàm chaoMung() để nó thực hiện công việc của mình
chaoMung();
cout << "Chương trình kết thúc." << endl;
return 0;
}
Giải thích code:
- Chúng ta khai báo hàm
chaoMung
với kiểu trả về làvoid
và không có tham số (()
). - Trong thân hàm, chúng ta sử dụng
cout
để in ra hai dòng chữ. - Trong hàm
main
, chúng ta đơn giản là gọi hàmchaoMung
bằng tên của nó theo sau bởi dấu ngoặc đơn()
. - Khi
chaoMung();
được thực thi, luồng chương trình nhảy vào thân hàmchaoMung
, thực hiện các lệnh in, sau đó quay trở lại điểm gọi (sauchaoMung();
) trongmain
và tiếp tục thực hiện lệnh tiếp theo (cout << "Chương trình kết thúc."
). - Không có giá trị nào được "nhận lại" từ lời gọi hàm
chaoMung()
.
Tại sao lại sử dụng Hàm void
?
Như đã đề cập, hàm void
được sử dụng khi mục đích chính của hàm là thực hiện một hành động (action) hoặc tạo ra một hiệu ứng phụ (side effect) thay vì tính toán và trả về một kết quả. Các hiệu ứng phụ phổ biến bao gồm:
- In thông tin ra màn hình hoặc file: Rất nhiều hàm
void
chỉ đơn giản là hiển thị dữ liệu. - Thay đổi trạng thái của chương trình: Ví dụ: cài đặt cấu hình, thay đổi giá trị của biến toàn cục (thường không được khuyến khích), hoặc thay đổi trạng thái của đối tượng (sẽ học sau).
- Thực hiện một tập hợp các thao tác: Gom nhóm nhiều lệnh thành một hàm để dễ quản lý và tái sử dụng.
- Điều khiển luồng chương trình: Mặc dù hiếm, một hàm
void
có thể chứa vòng lặp hoặc điều kiện phức tạp.
Hãy xem xét thêm các ví dụ khác để hiểu rõ hơn.
Ví dụ 1: Hàm void
với Tham số
Hàm void
vẫn có thể nhận tham số để tùy chỉnh hành động của nó.
#include <iostream>
#include <string> // Để sử dụng string
// Hàm void nhận một tên và in lời chào cá nhân
void chaoCaNhan(string tenNguoiDung) {
cout << "Xin chào, " << tenNguoiDung << "!" << endl;
cout << "Chúc bạn một ngày tốt lành!" << endl;
}
int main() {
string ten1 = "Minh";
string ten2 = "Ngoc";
chaoCaNhan(ten1); // Gọi hàm với tham số ten1
chaoCaNhan(ten2); // Gọi hàm với tham số ten2
return 0;
}
Giải thích code:
- Hàm
chaoCaNhan
có kiểu trả vềvoid
và nhận một tham số kiểustring
. - Nhiệm vụ của nó là dùng tham số nhận được để in ra một lời chào cá nhân hóa.
- Trong
main
, chúng ta gọichaoCaNhan
hai lần với hai tên khác nhau. Mỗi lần gọi, hàm sẽ thực hiện cùng một hành động in ấn nhưng với dữ liệu khác nhau dựa trên tham số truyền vào.
Ví dụ 2: Hàm void
thực hiện một tác vụ cụ thể
Một hàm void
có thể làm nhiều việc hơn là chỉ in ra màn hình. Nó có thể thực hiện các tính toán nội bộ hoặc thao tác dữ liệu (nhưng không trả về kết quả của thao tác đó).
#include <iostream>
#include <vector> // Để sử dụng vector
#include <numeric> // Để sử dụng accumulate
// Hàm void tính tổng các phần tử trong vector và in ra
void tinhVaInTongVector(const vector<int>& duLieu) {
if (duLieu.empty()) {
cout << "Vector rỗng, không có gì để tính tổng." << endl;
return; // Có thể sử dụng return; để thoát sớm khỏi hàm void
}
long long tong = 0; // Sử dụng long long để tránh tràn số với vector lớn
for (int phanTu : duLieu) {
tong += phanTu;
}
// Hoặc dùng accumulate:
// long long tong = accumulate(duLieu.begin(), duLieu.end(), 0LL); // 0LL để đảm bảo kết quả là long long
cout << "Tổng của các phần tử trong vector là: " << tong << endl;
}
int main() {
vector<int> cacSo = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
vector<int> vectorRong;
tinhVaInTongVector(cacSo); // Gọi hàm với vector có dữ liệu
tinhVaInTongVector(vectorRong); // Gọi hàm với vector rỗng
return 0;
}
Giải thích code:
- Hàm
tinhVaInTongVector
nhận một tham chiếu hằng đếnvector<int>
. - Nó thực hiện việc tính tổng các phần tử bên trong vector.
- Quan trọng: Thay vì trả về giá trị tổng (để nơi gọi có thể sử dụng), hàm này in trực tiếp giá trị tổng ra màn hình.
- Mục đích của hàm là thực hiện hành động in tổng, chứ không phải là cung cấp giá trị tổng. Do đó,
void
là kiểu trả về phù hợp. - Cũng có một ví dụ về việc sử dụng
return;
bên trong hàmvoid
để thoát sớm nếu vector rỗng.
Ví dụ 3: Hàm void
thay đổi giá trị thông qua Tham chiếu
Hàm void
có thể gây ra hiệu ứng phụ bằng cách thay đổi giá trị của các biến được truyền vào dưới dạng tham chiếu.
#include <iostream>
// Hàm void nhận một tham chiếu đến số nguyên và tăng giá trị của nó
void tangGiaTri(int& soNguyen) { // Tham số là tham chiếu (int&)
soNguyen = soNguyen + 5; // Thay đổi giá trị của biến gốc
cout << "Trong hàm: Giá trị sau khi tăng là " << soNguyen << endl;
}
int main() {
int diemSo = 95;
cout << "Trước khi gọi hàm: Điểm số là " << diemSo << endl;
tangGiaTri(diemSo); // Gọi hàm và truyền biến diemSo
cout << "Sau khi gọi hàm: Điểm số là " << diemSo << endl; // Giá trị diemSo đã thay đổi
return 0;
}
Giải thích code:
- Hàm
tangGiaTri
có kiểu trả vềvoid
. Nó không trả về một giá trị mới. - Tuy nhiên, nó nhận tham số dưới dạng tham chiếu (
int& soNguyen
). Điều này có nghĩa làsoNguyen
bên trong hàm không phải là một bản sao, mà là chính biến được truyền vào từmain
(ở đây làdiemSo
). - Khi chúng ta thực hiện
soNguyen = soNguyen + 5;
, chúng ta thực sự đang thay đổi giá trị của biếndiemSo
trong hàmmain
. - Đây là một ví dụ điển hình về hiệu ứng phụ mà hàm
void
có thể tạo ra: thay đổi trạng thái bên ngoài hàm mà không cần trả về một giá trị mới.
Ví dụ 4: Hàm void
gọi các Hàm khác
Hàm void
thường được sử dụng để tổ chức các bước thực hiện bằng cách gọi các hàm khác, kể cả các hàm void
khác hoặc hàm có trả về giá trị (nhưng nó không cần sử dụng giá trị trả về đó).
#include <iostream>
// Các hàm void thực hiện từng bước nhỏ
void buocKhoiTao() {
cout << "Đang thực hiện bước Khởi tạo..." << endl;
// Giả định có các lệnh khởi tạo phức tạp ở đây
}
void buocXuLy() {
cout << "Đang thực hiện bước Xử lý dữ liệu..." << endl;
// Giả định có các lệnh xử lý dữ liệu ở đây
}
void buocKetThuc() {
cout << "Đang thực hiện bước Kết thúc..." << endl;
// Giả định có các lệnh dọn dẹp/lưu trữ ở đây
}
// Hàm void tổng hợp các bước trên
void thucHienToanBoQuyTrinh() {
cout << "--- Bắt đầu Quy trình ---" << endl;
buocKhoiTao(); // Gọi hàm void khác
buocXuLy(); // Gọi hàm void khác
buocKetThuc(); // Gọi hàm void khác
cout << "--- Kết thúc Quy trình ---" << endl;
}
int main() {
thucHienToanBoQuyTrinh(); // Chỉ cần gọi hàm void chính
return 0;
}
Giải thích code:
- Chúng ta có ba hàm
void
nhỏ (buocKhoiTao
,buocXuLy
,buocKetThuc
), mỗi hàm thực hiện một phần công việc. - Hàm
thucHienToanBoQuyTrinh
cũng là một hàmvoid
. Nhiệm vụ của nó là điều phối, gọi lần lượt ba hàm nhỏ kia để thực hiện một quy trình hoàn chỉnh. - Trong
main
, chúng ta chỉ cần gọi duy nhất hàmthucHienToanBoQuyTrinh
. Điều này giúp làm cho code trongmain
gọn gàng, dễ đọc và hiểu được luồng chính của chương trình. Chi tiết của từng bước được ẩn bên trong các hàm con.
Gọi Hàm void
Việc gọi hàm void
rất đơn giản: bạn chỉ cần viết tên hàm và truyền các tham số (nếu có), kết thúc bằng dấu chấm phẩy ;
.
tenHamVoid(thamSo1, thamSo2, ...);
Lưu ý quan trọng: Vì hàm void
không trả về giá trị, bạn không thể sử dụng nó trong một biểu thức hoặc gán kết quả của nó cho một biến. Ví dụ, các dòng code sau sẽ gây lỗi biên dịch:
// SAI: Không thể gán kết quả của hàm void cho biến
// int ketQua = chaoMung();
// SAI: Không thể sử dụng hàm void trong biểu thức
// if (chaoMung()) { ... }
// SAI: Không thể in trực tiếp kết quả của hàm void
// cout << chaoMung();
Việc gọi hàm void
đơn giản là một lệnh độc lập để yêu cầu chương trình thực hiện công việc được định nghĩa trong hàm đó.
Câu lệnh return
trong Hàm void
Mặc dù hàm void
không trả về giá trị, bạn vẫn có thể sử dụng từ khóa return
trong thân hàm của nó. Tuy nhiên, bạn chỉ có thể sử dụng return;
một mình, không có bất kỳ giá trị nào theo sau.
Mục đích của return;
trong hàm void
là để thoát khỏi hàm ngay lập tức khi gặp điều kiện nào đó.
#include <iostream>
void kiemTraTuoiVaIn(int tuoi) {
if (tuoi < 0) {
cout << "Lỗi: Tuổi không thể là số âm." << endl;
return; // Thoát hàm ngay lập tức nếu tuổi âm
}
if (tuoi < 18) {
cout << "Bạn là trẻ vị thành niên (" << tuoi << " tuổi)." << endl;
} else {
cout << "Bạn là người trưởng thành (" << tuoi << " tuổi)." << endl;
}
// Các lệnh khác ở đây sẽ không được thực thi nếu tuổi < 0
cout << "Đã kiểm tra xong tuổi." << endl;
}
int main() {
kiemTraTuoiVaIn(25);
kiemTraTuoiVaIn(16);
kiemTraTuoiVaIn(-5); // Sẽ in lỗi và thoát hàm sớm
kiemTraTuoiVaIn(30); // Sẽ chạy bình thường
return 0;
}
Giải thích code:
- Hàm
kiemTraTuoiVaIn
kiểm tra giá trịtuoi
. - Nếu
tuoi
nhỏ hơn 0, nó in ra thông báo lỗi và sử dụngreturn;
. Lệnh này khiến hàm kết thúc ngay lập tức, bỏ qua các lệnhif
và lệnhcout << "Đã kiểm tra xong tuổi."
phía sau. - Nếu
tuoi
lớn hơn hoặc bằng 0, hàm sẽ tiếp tục thực thi các lệnh kiểm tra tuổi vị thành niên/trưởng thành và in ra dòng "Đã kiểm tra xong tuổi.".
Ứng dụng phổ biến của Hàm void
Hàm void
có mặt ở khắp mọi nơi trong lập trình C++ thực tế. Một số ứng dụng phổ biến bao gồm:
- Thiết lập chương trình (Initialization): Các hàm như
void setupGame()
hoặcvoid configureSystem()
thường không cần trả về giá trị mà chỉ thực hiện các bước cài đặt ban đầu. - Vẽ giao diện (Drawing/Rendering): Trong đồ họa, các hàm như
void drawPlayer()
,void renderScene()
chỉ có nhiệm vụ vẽ lên màn hình mà không tính toán ra giá trị cụ thể. - Xử lý sự kiện (Event Handling): Các hàm được gọi khi một sự kiện xảy ra (ví dụ: nhấn nút, di chuyển chuột) thường là
void
, vì công việc của chúng là phản ứng lại sự kiện chứ không phải trả về kết quả. - Ghi nhật ký (Logging): Hàm
void logMessage(const string& msg)
chỉ có nhiệm vụ ghi thông báo vào file hoặc console. - Các thao tác I/O (Input/Output): Nhiều hàm thao tác với file hoặc luồng dữ liệu (như
cout << ...;
) có thể được gói gọn trong hàmvoid
.
Bài tập ví dụ: C++ Bài 4.A3: Số đẹp
Hiếu là một người thích con số \(5\) nên anh ta đã tự định nghĩa một nguyên nguyên đẹp. Số tự nhiên \(N\) được gọi là số đẹp nếu cộng các chữ số của \(N\) lại ta có một số mà kết thúc bằng \(5\). Ví dụ một số số đẹp là \(14 ( 1 + 4 =5)\), \(654 (6 + 5 + 4= 15)\).
Cho một số \(N\), hãy kiểm tra xem \(N\) có phải là số đẹp hay không.
INPUT FORMAT
Dòng đầu tiên nhập vào số nguyên \(N (1 \leq N \leq 10^9)\).
OUTPUT FORMAT
Nếu \(N\) là số đẹp thì in ra YES
ngược lại in ra NO
.
Ví dụ 1:
Input
14
Ouput
YES
Ví dụ 2:
Input
653
Output
NO
Giải thích ví dụ mẫu:
Ví dụ 1:
- Input:
14
- Giải thích: Tổng các chữ số của 14 là 5, kết thúc bằng 5, nên là số đẹp.
- Input:
Ví dụ 2:
- Input:
653
- Giải thích: Tổng các chữ số của 653 là 14, không kết thúc bằng 5, nên không phải số đẹp. <br>
- Input:
Bài toán yêu cầu kiểm tra một số tự nhiên N có phải là "số đẹp" hay không, theo định nghĩa: tổng các chữ số của N phải kết thúc bằng 5.
Các bước thực hiện như sau:
Đọc dữ liệu đầu vào: Cần đọc giá trị của số nguyên N từ bàn hình. Sử dụng
cin
để làm điều này.Tính tổng các chữ số của N:
- Khởi tạo một biến để lưu tổng các chữ số, ban đầu gán bằng 0.
- Sử dụng một vòng lặp. Vòng lặp này sẽ tiếp tục thực hiện miễn là số N vẫn lớn hơn 0.
- Trong mỗi lần lặp:
- Lấy chữ số cuối cùng của N bằng cách sử dụng toán tử modulo (%). Chữ số cuối cùng là
N % 10
. - Cộng chữ số vừa lấy được vào biến tổng.
- Cập nhật lại N bằng cách chia N cho 10 và lấy phần nguyên (sử dụng toán tử chia /). Điều này loại bỏ chữ số cuối cùng khỏi N.
- Lấy chữ số cuối cùng của N bằng cách sử dụng toán tử modulo (%). Chữ số cuối cùng là
- Khi vòng lặp kết thúc (N trở thành 0), biến tổng sẽ chứa tổng tất cả các chữ số ban đầu của N.
Kiểm tra điều kiện "số đẹp":
- Sau khi tính được tổng các chữ số, bạn cần kiểm tra xem tổng này có kết thúc bằng 5 hay không.
- Một số kết thúc bằng 5 khi chia lấy dư cho 10 bằng 5.
- Thực hiện phép kiểm tra:
tổng_chữ_số % 10 == 5
.
In kết quả:
- Nếu điều kiện ở bước 3 đúng, tức là tổng các chữ số kết thúc bằng 5, in ra "YES" bằng
cout
. - Ngược lại, in ra "NO" bằng
cout
.
- Nếu điều kiện ở bước 3 đúng, tức là tổng các chữ số kết thúc bằng 5, in ra "YES" bằng
Lưu ý khi triển khai bằng C++:
- Bạn cần include thư viện
<iostream>
để sử dụngcin
vàcout
. - Sử dụng kiểu dữ liệu
int
cho N và biến tổng là phù hợp với giới hạn1 \leq N \leq 10^9
. - Cấu trúc điều kiện
if-else
được sử dụng để in ra "YES" hoặc "NO".
Hướng giải này tập trung vào logic cơ bản và sử dụng các phép toán số học cùng vòng lặp, là cách tiếp cận hiệu quả cho bài toán này.
Comments