Void trong C++: Giải mã 'Không' - Sức mạnh của sự vắng mặt
C++

Void trong C++: Giải mã 'Không' - Sức mạnh của sự vắng mặt

Author

Admin System

@root

Ngày xuất bản

21 Mar, 2026

Lượt xem

2 Lượt

"void"

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()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.

Illustration

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'

  1. 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 ngay void làm kiểu trả về. Ví dụ: void luuDuLieuVaoDatabase(), void guiEmailThongBao().
  2. void*:
    • Tránh dùng nếu có giải pháp khác: Trong C++ hiện đại, thường thì template là lựa chọn tốt hơn cho các hàm/lớp generic. template cho 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.
  3. void func() vs void func(void): Luôn dùng void 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ặc int để 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 malloc củ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à void vì 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 void cho 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ĩ đến template trước void*. template an 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é!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!