C/C++面試題:void*通常怎么使用?——快手一面
void* 在 C 和 C++ 中被稱為“無(wú)類型指針”或“通用指針”(generic pointer)。它是一種特殊的指針類型,可以指向任何數(shù)據(jù)類型的對(duì)象(或函數(shù))的地址,但它本身不包含任何關(guān)于它所指向?qū)ο箢愋偷男畔ⅰ?/p>

void* 的主要用途和使用方式包括:
1. 通用函數(shù)接口(如內(nèi)存操作函數(shù)):
標(biāo)準(zhǔn)庫(kù)函數(shù)如 malloc, calloc, realloc 返回 void*,因?yàn)樗鼈兎峙涞氖窃純?nèi)存塊,并不知道你打算在這塊內(nèi)存中存儲(chǔ)什么類型的數(shù)據(jù)。你需要將返回的 void* 顯式轉(zhuǎn)換(cast)為你需要的具體指針類型才能使用。
memcpy, memmove, memset 等函數(shù)接受 void* 參數(shù),因?yàn)樗鼈儼醋止?jié)操作內(nèi)存,不關(guān)心實(shí)際的數(shù)據(jù)類型,只需要知道內(nèi)存地址和大小。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 1. malloc example
int *p_int = (int*)malloc(sizeof(int));
if (p_int == NULL) {
perror("Failed to allocate memory");
return 1;
}
*p_int = 100;
printf("Value via p_int: %d\n", *p_int);
free(p_int);
// 2. memcpy example
char src[] = "Hello";
char dest[10];
memcpy(dest, src, strlen(src) + 1);
printf("Copied string: %s\n", dest);
return 0;
}2.實(shí)現(xiàn)泛型數(shù)據(jù)結(jié)構(gòu)和算法 (主要在 C 語(yǔ)言中):
在 C 語(yǔ)言中,沒(méi)有模板(templates)這樣的泛型編程機(jī)制。如果想創(chuàng)建可以存儲(chǔ)任何類型數(shù)據(jù)的列表、樹(shù)、哈希表等,或者編寫可以處理任何類型數(shù)組的排序、搜索算法(如標(biāo)準(zhǔn)庫(kù)的 qsort),void* 是常用的方法。
數(shù)據(jù)結(jié)構(gòu)通常會(huì)存儲(chǔ) void* 指向?qū)嶋H數(shù)據(jù)。
像 qsort 這樣的函數(shù)接受一個(gè) void* 指向數(shù)組基地址,并需要一個(gè)比較函數(shù),該比較函數(shù)也接受兩個(gè) const void* 參數(shù),你需要在比較函數(shù)內(nèi)部將 void* 轉(zhuǎn)換回實(shí)際的數(shù)據(jù)類型指針進(jìn)行比較。
#include <stdio.h>
#include <stdlib.h>
// Comparison function for qsort (sorting integers)
int compare_ints(const void *a, const void *b) {
int int_a = *((const int*)a); // Cast void* to const int* and dereference
int int_b = *((const int*)b); // Cast void* to const int* and dereference
if (int_a < int_b) return -1;
if (int_a > int_b) return 1;
return 0;
}
int main() {
int numbers[] = {5, 2, 8, 1, 9, 4};
size_t num_count = sizeof(numbers) / sizeof(numbers[0]);
printf("Before sorting: ");
for (size_t i = 0; i < num_count; ++i) printf("%d ", numbers[i]);
printf("\n");
// Use qsort with void* base address and comparison function
qsort(numbers, num_count, sizeof(int), compare_ints);
printf("After sorting: ");
for (size_t i = 0; i < num_count; ++i) printf("%d ", numbers[i]);
printf("\n");
return 0;
}3.傳遞不透明數(shù)據(jù)指針
在庫(kù)或 API 設(shè)計(jì)中,有時(shí)會(huì)將內(nèi)部結(jié)構(gòu)的指針作為 void* 返回給用戶,用戶不能(也不應(yīng)該)直接操作它,只能將其傳遞回庫(kù)的其他函數(shù)。這隱藏了實(shí)現(xiàn)細(xì)節(jié)。
4.回調(diào)函數(shù)的用戶數(shù)據(jù)(User Data / Context)
許多 API(如圖形庫(kù)、線程庫(kù)、事件處理系統(tǒng))允許你注冊(cè)一個(gè)回調(diào)函數(shù),并在注冊(cè)時(shí)提供一個(gè) void* 參數(shù)(通常稱為 userData、context 或類似名稱)。當(dāng) API 調(diào)用你的回調(diào)函數(shù)時(shí),它會(huì)把你當(dāng)初提供的 void* 再傳回給你的回調(diào)函數(shù)。這允許你的回調(diào)函數(shù)訪問(wèn)特定的上下文信息,而 API 本身無(wú)需知道這些信息的具體類型。
假設(shè)有一個(gè)庫(kù)函數(shù) setTimer,它會(huì)在指定的毫秒數(shù)后調(diào)用你提供的回調(diào)函數(shù)。為了讓你的回調(diào)函數(shù)知道是哪個(gè)計(jì)時(shí)器觸發(fā)了(或者攜帶任何你想傳遞的信息),setTimer 允許你傳遞一個(gè) void* 用戶數(shù)據(jù)。
#include <stdio.h> // 包含標(biāo)準(zhǔn)輸入輸出頭文件
#include <stdlib.h> // 包含標(biāo)準(zhǔn)庫(kù)頭文件 (用于 NULL)
// --- 假設(shè)這是外部庫(kù)的一部分 ---
typedef void (*TimerCallback)(int timerId, void* userData);
// 模擬設(shè)置一個(gè)定時(shí)器。
void setTimer(int milliseconds, TimerCallback callback, void* userData) {
printf("定時(shí)器庫(kù):正在設(shè)置 %d 毫秒的定時(shí)器。\n", milliseconds);
// --- 想象等待 'milliseconds' 毫秒 ---
printf("定時(shí)器庫(kù):定時(shí)器到期!調(diào)用回調(diào)函數(shù)。\n");
// 庫(kù)函數(shù)不知道也不關(guān)心 userData 指向什么,
// 它只是將其原樣傳遞回給回調(diào)函數(shù)。
int assignedTimerId = 1;
callback(assignedTimerId, userData); // 調(diào)用回調(diào),傳入ID和用戶數(shù)據(jù)
printf("定時(shí)器庫(kù):回調(diào)完成。\n");
}
// --- 應(yīng)用程序代碼 ---
// 1. 定義傳遞給回調(diào)函數(shù)的數(shù)據(jù)結(jié)構(gòu)
typedef struct {
const char* message; // 消息字符串
int retryCount; // 重試次數(shù)計(jì)數(shù)器
} MyTimerInfo;
// 2. 實(shí)現(xiàn)匹配 TimerCallback 簽名的回調(diào)函數(shù)
void handleTimerExpiration(int timerId, void* userData) {
printf("我的應(yīng)用程序:收到定時(shí)器 ID %d 的回調(diào)。\n", timerId);
// 重要:將 void* 指針強(qiáng)制轉(zhuǎn)換回正確的指針類型 (MyTimerInfo*)
MyTimerInfo* info = (MyTimerInfo*)userData;
printf("消息: %s, 重試次數(shù): %d\n", info->message, info->retryCount);
info->retryCount++; // 修改數(shù)據(jù)
}
// 3. 主邏輯中,創(chuàng)建數(shù)據(jù)并注冊(cè)定時(shí)器
int main() {
MyTimerInfo myInfo;
myInfo.message = "任務(wù) A 需要處理";
myInfo.retryCount = 0;
printf("我的應(yīng)用程序:正在注冊(cè)定時(shí)器...\n");
setTimer(1000, handleTimerExpiration, &myInfo); // 傳遞 myInfo 的地址
printf("我的應(yīng)用程序:定時(shí)器注冊(cè)調(diào)用完成。\n");
// 打印修改后的重試次數(shù),驗(yàn)證回調(diào)函數(shù)確實(shí)修改了它
printf("我的應(yīng)用程序:當(dāng)前重試次數(shù) (回調(diào)之后): %d\n", myInfo.retryCount);
return 0; // 程序正常退出
}5.重要注意事項(xiàng)
- 不能直接解引用 : 你不能直接對(duì) void* 使用 * 運(yùn)算符,因?yàn)榫幾g器不知道它指向的數(shù)據(jù)類型有多大,以及如何解釋這些字節(jié)。
- 必須顯式轉(zhuǎn)換: 在使用 void* 指向的數(shù)據(jù)之前,必須將其顯式轉(zhuǎn)換(cast)為正確的具體數(shù)據(jù)類型指針。
- 類型安全: void* 的使用會(huì)繞過(guò)編譯器的類型檢查。如果你將 void* 轉(zhuǎn)換回了錯(cuò)誤的類型,會(huì)導(dǎo)致未定義行為(Undefined Behavior),通常是程序崩潰或數(shù)據(jù)損壞。這是 void* 的主要缺點(diǎn)。
- 指針運(yùn)算: 不能對(duì) void* 進(jìn)行指針?biāo)阈g(shù)運(yùn)算(如 ptr++ 或 ptr + 1),因?yàn)榫幾g器不知道每個(gè)元素的大小。 (GCC 等編譯器可能有擴(kuò)展允許,但不標(biāo)準(zhǔn)且危險(xiǎn))。
- C++ 中的替代方案: 在 C++ 中,雖然 void* 仍然可用且在與 C 庫(kù)交互時(shí)必不可少,但對(duì)于泛型編程,通常推薦使用模板(templates),它們提供了類型安全。對(duì)于類型轉(zhuǎn)換,C++ 提供了更安全的轉(zhuǎn)換運(yùn)算符,如 static_cast, dynamic_cast, 和 reinterpret_cast。reinterpret_cast<T*>(void_ptr) 常用于 void* 和其他指針類型之間的轉(zhuǎn)換,但同樣需要開(kāi)發(fā)者保證類型轉(zhuǎn)換的正確性。
void* 是一個(gè)強(qiáng)大的工具,用于實(shí)現(xiàn)通用性,但犧牲了類型安全。使用它時(shí),必須非常小心,確保在解引用或操作指針之前,總是將其轉(zhuǎn)換回正確的原始類型。
































