
Chào các bạn Gen Z mê code, lại là thầy Creyt đây! Hôm nay, chúng ta sẽ "giải mã" một từ khóa nghe có vẻ hơi... trống rỗng nhưng lại cực kỳ quyền lực trong C++: void. Nghe thì có vẻ 'không có gì', nhưng tin thầy đi, 'không có gì' này lại là chìa khóa mở ra nhiều cánh cửa thú vị đấy.
1. void - 'Ông trùm' của những hàm không cần 'phản hồi'
Tưởng tượng thế này: bạn nhờ thằng bạn đi mua hộ ly trà sữa. Nó đi mua về, bạn uống, và nó... chẳng đưa lại cho bạn cái gì ngoài ly trà sữa đó cả. Nó đã thực hiện hành động 'mua trà sữa', nhưng không 'trả về' cho bạn một thông tin hay vật phẩm gì khác để bạn tiếp tục xử lý (như hóa đơn, tiền thừa, hay thậm chí là kinh nghiệm chọn vị trà sữa ngon).
Trong lập trình cũng vậy, khi một hàm được khai báo với kiểu trả về là void, nghĩa là hàm đó sẽ thực hiện một tác vụ nào đó (in ra màn hình, sửa đổi dữ liệu, gửi một gói tin...), nhưng nó không trả về bất kỳ giá trị nào sau khi hoàn thành. Nó chỉ làm việc của nó và 'xong'.
Ví dụ Code Minh Họa:
#include <iostream> // Đừng quên thư viện này nhé các bạn!
// Hàm này chỉ đơn giản là in ra một lời chào, không cần trả về gì cả
void chaoMungGenZ() {
std::cout << "Chào mừng Gen Z đến với thế giới C++ đầy 'void'!" << std::endl;
}
// Hàm này cũng tương tự, chỉ thực hiện một tác vụ
void thucHienTacVuQuanTrong() {
// Giả sử ở đây có cả tá logic phức tạp
std::cout << "Đang thực hiện một tác vụ siêu quan trọng..." << std::endl;
// ... và sau đó kết thúc mà không 'báo cáo' gì
}
int main() {
chaoMungGenZ(); // Gọi hàm, nó tự làm việc của nó
thucHienTacVuQuanTrong(); // Gọi hàm khác, cũng tự làm việc của nó
return 0; // main() thì phải trả về 0 để báo hiệu chương trình thành công nhé!
}
Ở đây, chaoMungGenZ() và thucHienTacVuQuanTrong() không cần 'báo cáo' kết quả gì về cho main() cả. Chúng chỉ làm nhiệm vụ của mình và... thế là hết! Giống như bạn bật đèn, đèn sáng, bạn không cần đèn 'báo cáo' lại là nó đã sáng thành công đâu.
2. void trong danh sách tham số (ít dùng trong C++)
Hồi xưa, các cụ C hay dùng void func(void) để chỉ rõ một hàm không nhận bất kỳ tham số nào. Kiểu như 'hàm này không cần ai đó đưa cho nó cái gì để làm việc cả'.
Tuy nhiên, trong C++ hiện đại, chúng ta chỉ cần viết void func() là đủ rồi. Nó cũng có nghĩa tương tự: hàm này không cần 'đầu vào' gì hết. C++ thông minh hơn C ở khoản này, nó hiểu ngầm () rỗng là không có tham số.
Ví dụ Code Minh Họa (để biết thôi, chứ C++ thì dùng () nhé):
#include <iostream>
// Trong C, đây là cách khai báo hàm không nhận tham số
void chaoCacCu(void) {
std::cout << "Ngày xưa các cụ C hay dùng 'void' ở đây đó các cháu!" << std::endl;
}
// Trong C++, đây là cách hiện đại và được khuyến khích
void chaoHienDai() {
std::cout << "Còn bây giờ, '()' là đủ rồi nhé!" << std::endl;
}
int main() {
chaoCacCu();
chaoHienDai();
return 0;
}
Thấy không? C++ nó gọn gàng hơn nhiều. Đừng để bị lừa bởi cái (void) cũ kĩ nhé, trừ khi bạn đang code C thuần túy.

3. void* - Con trỏ 'đa năng' hay 'ông trùm môi giới'
Đây mới là phần 'hack não' nhất của void này các bạn! void* được gọi là con trỏ generic (đa năng). Tưởng tượng thế này: void* giống như một anh chàng môi giới bất động sản. Anh ta biết địa chỉ của một căn nhà (địa chỉ bộ nhớ), nhưng anh ta không biết căn nhà đó là loại gì (nhà cấp 4, biệt thự, chung cư), có bao nhiêu phòng, diện tích bao nhiêu... Anh ta chỉ biết 'ở đó có một cái gì đó'.
void* có thể trỏ đến bất kỳ kiểu dữ liệu nào (int, float, char, struct, class...). Nó giống như một 'thẻ bài vạn năng' có thể mở mọi cánh cửa, nhưng để biết bên trong cánh cửa đó có gì, bạn phải 'biến hình' nó thành đúng loại cửa đó.
Điểm mấu chốt: Bạn không thể truy cập trực tiếp dữ liệu mà một void* đang trỏ tới (dereference) nếu chưa 'ép kiểu' (type cast) nó về đúng kiểu dữ liệu ban đầu. Tại sao? Vì nếu không biết nó là kiểu gì, làm sao máy tính biết phải đọc bao nhiêu byte dữ liệu từ địa chỉ đó, hay xử lý chúng như thế nào?
Ví dụ Code Minh Họa:
#include <iostream>
#include <string>
int main() {
int soNguyen = 42;
float soThuc = 3.14f;
std::string chuoiText = "Hello Creyt!";
// Khai báo con trỏ void*
void* conTroDaNang;
// Trỏ đến số nguyên
conTroDaNang = &soNguyen;
// Để đọc giá trị, phải ép kiểu về int*
std::cout << "Giá trị số nguyên: " << *(static_cast<int*>(conTroDaNang)) << std::endl;
// Trỏ đến số thực
conTroDaNang = &soThuc;
// Để đọc giá trị, phải ép kiểu về float*
std::cout << "Giá trị số thực: " << *(static_cast<float*>(conTroDaNang)) << std::endl;
// Trỏ đến chuỗi (string là một object phức tạp hơn)
conTroDaNang = &chuoiText;
// Để đọc giá trị, phải ép kiểu về std::string*
std::cout << "Giá trị chuỗi: " << *(static_cast<std::string*>(conTroDaNang)) << std::endl;
// Con trỏ void* không biết kích thước của đối tượng nó trỏ tới
// Nên bạn không thể thực hiện phép toán số học trực tiếp trên void*
// Ví dụ: conTroDaNang++; // Lỗi! Không biết tăng bao nhiêu byte
return 0;
}
Các bạn thấy không? void* rất linh hoạt, nhưng cũng đòi hỏi bạn phải 'biết mình biết ta' khi sử dụng. Nó giống như bạn có một chìa khóa vạn năng, nhưng để mở đúng cánh cửa, bạn phải biết đó là cửa nào và dùng lực thế nào cho đúng.
4. Mẹo từ thầy Creyt: Dùng void sao cho 'chất'
- Hàm
void: Cứ khi nào hàm của bạn chỉ làm nhiệm vụ 'thực thi' mà không cần 'báo cáo' kết quả, quất ngayvoidlàm kiểu trả về. Ví dụ:void luuDuLieuVaoDatabase(),void guiEmailThongBao(). void*:- Tránh dùng nếu có giải pháp khác: Trong C++ hiện đại, thường thì
templatelà lựa chọn tốt hơn cho các hàm/lớp generic.templatecho phép bạn viết code hoạt động với nhiều kiểu dữ liệu mà vẫn giữ được thông tin về kiểu, không cần ép kiểu thủ công. - Dùng khi nào? Khi bạn cần giao tiếp với các thư viện C cũ (như
malloc,memcpy), hoặc khi bạn đang làm việc ở tầng rất thấp của hệ thống, nơi bạn cần quản lý bộ nhớ một cách thật sự 'trần trụi' và linh hoạt. - Luôn ép kiểu: Đừng bao giờ dereference một
void*mà chưa ép kiểu! Nó giống như bạn cố gắng đọc một cuốn sách ngôn ngữ lạ mà không có từ điển vậy, chỉ toàn 'rác' thôi.
- Tránh dùng nếu có giải pháp khác: Trong C++ hiện đại, thường thì
void func()vsvoid func(void): Luôn dùngvoid func()trong C++ nhé! Trông nó hiện đại và đúng chuẩn hơn nhiều.
5. void trong thế giới thực: Không chỉ là 'không có gì'
void không phải là một thứ 'trên trời rơi xuống' mà nó xuất hiện khắp nơi trong các hệ thống bạn dùng hàng ngày:
- Hệ điều hành: Các hàm hệ thống như
exit()(kết thúc chương trình) thường có kiểu trả về làvoid(hoặcintđể báo mã lỗi, nhưng nhiều khi bạn chỉ muốn nó 'biến mất' thôi). - Quản lý bộ nhớ: Hàm
malloccủa C (và vẫn dùng trong C++) trả vềvoid*vì nó cấp phát một khối bộ nhớ 'trống rỗng', không biết sẽ chứa kiểu dữ liệu gì. Sau đó bạn phải ép kiểu nó. - Thư viện UI/Game: Các hàm xử lý sự kiện (event handlers) như
onClick(),onKeyPress()thường làvoidvì chúng chỉ thực hiện một hành động (cập nhật giao diện, di chuyển nhân vật) mà không cần trả về một giá trị cụ thể nào. - Lập trình nhúng: Trong các hệ thống nhúng, nhiều hàm điều khiển phần cứng chỉ cần thực hiện lệnh (bật/tắt đèn, gửi tín hiệu) và không cần trả về gì, nên chúng cũng dùng
void.
Đó, void tuy 'vô hình' nhưng lại là xương sống của rất nhiều thứ đó!
6. Thử nghiệm của Creyt và lời khuyên 'chuẩn không cần chỉnh'
Thầy đã từng 'lăn lộn' với void từ thời C còn 'sơ khai' đến C++ hiện đại. Kinh nghiệm xương máu là:
- Dùng
voidcho hàm: Khi bạn muốn hàm đó thực hiện một 'side effect' (tác động phụ) lên môi trường (in ra màn hình, ghi file, thay đổi trạng thái của object khác) và không có kết quả tính toán nào cần được trả về. Đây là trường hợp phổ biến nhất và an toàn nhất. - Dùng
void*:- Bất đắc dĩ: Chỉ khi bạn thực sự cần sự linh hoạt tối đa ở cấp độ bộ nhớ thấp, hoặc khi giao tiếp với các API C cũ.
- Thận trọng cực độ: Hãy nhớ luôn ép kiểu
void*về đúng kiểu dữ liệu trước khi truy cập. Sai một ly, đi một dặm là chuyện thường tình với con trỏ đấy! - Ưu tiên
template: Nếu bạn đang viết code C++ hiện đại và muốn hàm/lớp của mình hoạt động với nhiều kiểu dữ liệu, hãy nghĩ đếntemplatetrướcvoid*.templatean toàn hơn, cung cấp kiểm tra kiểu tại thời điểm biên dịch, và thường dẫn đến code dễ đọc, dễ bảo trì hơn.
Tóm lại, void không phải là 'không có gì', mà là 'không có kiểu' hoặc 'không có giá trị trả về'. Nó là một công cụ mạnh mẽ, nhưng như mọi công cụ mạnh mẽ khác, cần được sử dụng đúng lúc, đúng chỗ và đúng cách. Hãy làm chủ nó để code của bạn không chỉ chạy được mà còn 'chất' nữa nhé! Hẹn gặp lại trong bài học tiếp theo!
Thuộc Series: C++
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!