面試題 1 :什么是空指針?
在 C++ 中,空指針是一個特殊的指針值,它不指向任何有效的內存地址。空指針通常用于表示指針不指向任何對象或函數。在C++11及以后的版本中, nullptr 是表示空指針的推薦方式。
nullptr 是一個指針類型的字面量,它不能轉換為整數類型,也不能被解引用。這使得 nullptr 在類型安全性和語義上比傳統的 NULL 或 0 更有優勢。
以下是一些關于空指針的要點:
(1)定義:在 C++11 及以后的版本中, nullptr 是一個關鍵字,用于表示空指針。
(2)類型安全: nullptr 是一個指針類型的字面量,因此它不能隱式地轉換為整數。這有助于防止一些由于類型不匹配導致的錯誤。
(3)比較: nullptr 可以與任何指針類型進行比較,包括 void* 指針。比較的結果將是 true ,如果指針是空指針;否則是 false 。
(4)解引用:嘗試解引用 nullptr (即嘗試訪問 nullptr 所指向的內存地址)將導致程序崩潰或未定義行為。
(5)初始化:可以將指針變量初始化為 nullptr ,以表示它不指向任何對象。
(6)與 NULL 的區別:在 C++11 及以后的版本中, NULL 通常被定義為 0 或 (void*)0 ,它是一個整數值,可以隱式地轉換為任何指針類型。然而,由于 NULL 可以隱式地轉換為整數,這可能導致一些類型相關的錯誤。因此,在現代C++編程中,推薦使用 nullptr 代替 NULL 。
下面是一個使用 nullptr 的例子:
int* ptr = nullptr; // 指針ptr1被初始化為空指針
if (nullptr == ptr)
{// ptr1是空指針,執行這里的代碼
}// 下面的代碼將導致未定義行為,因為不能解引用空指針
// int value = *ptr;
在實際編程中,當函數返回一個指針,而該指針可能不指向任何有效對象時,通常使用 nullptr 來表示這種情況。同樣,當函數的參數是一個指針,并且該參數可能不被使用時,可以將其傳遞為 nullptr 。
面試題 2 :什么是懸垂指針?
懸垂指針是指向已經被釋放或收回的內存地址的指針。當所指向的對象被釋放或收回,但對該指針沒有作任何的修改,以至于該指針仍舊指向已經回收的內存地址,此情況下該指針便稱為懸垂指針。懸垂指針往往導致程序錯誤,而且難以檢測。若操作系統將這部分已經釋放的內存重新分配給另外一個進程,而原來的程序重新引用現在的懸垂指針,則將產生無法預料的后果,因為這些內存現在包含的可能已經是完全不同的數據。懸垂指針通常是由于不正確的內存管理造成的,比如當使用 new 分配內存后,如果忘記使用 delete 釋放內存,或者釋放了內存后仍然保留原來的指針,就可能導致懸垂指針的產生。
為了避免懸垂指針,可以采取以下一些措施:
(1)使用智能指針( std::unique_ptr 或 std::shared_ptr ),這些智能指針在超出作用域或重新賦值時會自動釋放內存。
(2)在釋放內存后將指針設為 nullptr ,這樣可以明確地表示指針不再指向任何有效的內存地址。
(3)避免返回局部變量的地址或引用,因為局部變量在函數返回后會被銷毀,其地址可能不再有效。
(4)在編寫涉及指針操作的代碼時,要特別小心,確保所有的內存分配和釋放都是正確和匹配的。
注意:懸垂指針和野指針有所不同。野指針是指從未被初始化或從未指向有效對象的指針,而懸垂指針則是曾經有效但現在失效的指針。
面試題 3 :如何理解指針的加減運算?
指針的加減運算在 C++ 中是一種重要的操作,它允許程序員直接操作內存地址。這種運算實際上是對指針所指向的內存地址進行算術運算,從而改變指針的指向。
具體來說,指針的加法運算通常是將指針向后(或向前)移動一定的內存單元數。例如,如果一個指針 p 指向數組的第 n 個元素,那么 p+1 就會指向第 n+1 個元素。這里的 1 并不表示具體的元素數量,而是表示指針 p 的類型所占用的字節數。例如,如果 p 是一個整型指針(在大多數系統上,整型占用 4 個字節),那么 p+1 就會跳過 4 個字節,指向下一個整型元素。
指針的減法運算也是類似的,它會根據指針的類型計算出兩個指針之間的內存單元數量。例如,如果 p 和 q 是兩個指針,那么 p-q 就會計算出 p 和 q 之間相隔的內存單元數,也就是 p 所指向的內存地址與 q 所指向的內存地址之間的差值,除以 p 的類型所占用的字節數。
需要注意的是,指針的加減運算只能在具有相同類型的指針之間進行,否則會導致編譯錯誤。此外,對指針進行加減運算時,必須確保結果指針仍然指向有效的內存地址,否則可能會導致程序崩潰或未定義行為。
如下為樣例代碼:
#include <iostream> int main()
{int vals[] = { 1, 2, 3, 4, 5 }; // 一個包含5個整數的數組 int *ptr = vals; // 定義一個指向數組首元素的指針p // 使用指針加減運算遍歷數組 for (int i = 0; i < 5; i++){std::cout << "vals[" << i << "] = " << *ptr << std::endl; // 輸出當前指針指向的元素值 ptr++; // 指針向后移動一個整型元素的位置(即移動 4 個字節,假設整型大小為 4 字節) }// 現在用減法運算來將指針移回數組的開始位置 ptr = vals; // 重置指針到數組開始位置 // 使用指針減法運算計算數組末尾元素的地址與數組首元素地址的差值 int offset = (vals + 5) - ptr; // 計算從數組首元素到末尾元素的指針差值 std::cout << "offset between end and start of array: " << offset << std::endl;return 0;
}
上面代碼的輸出為:
vals[0] = 1
vals[1] = 2
vals[2] = 3
vals[3] = 4
vals[4] = 5
offset between end and start of array: 5
在上面代碼中,首先定義了一個指向整型數組 vals 首元素的指針 ptr 。然后通過一個循環,每次將指針 ptr 向后移動一個整型元素的位置(通過 ptr++ ),并輸出當前指針所指向的元素值。這就是指針加法運算的應用。
在循環結束后,重置指針 ptr 到數組的開始位置,然后使用指針減法運算計算從數組首元素到末尾元素的指針差值。注意這里的差值并不是數組元素的數量,而是內存地址的差值,這個差值會被解釋成以指針類型大小(在這里是整型的大小)為單位的數量。
面試題 4 :什么是指針的數組?它與數組的指針有什么區別?
指針的數組和數組的指針是 C++ 語言中的兩種不同概念,它們在定義和使用上有顯著的區別。
指針的數組
(1)指針的數組是一個數組,其中每個元素都是一個指針。換句話說,它存儲了多個指針的地址,這些指針可以指向不同類型的數據或對象。
(2)聲明方式為 type *array[],其中 type 為指針指向的數據類型。例如,int *ptrArray[5] 表示一個包含5個指向整數類型數據的指針的數組。
(3)在指針數組中,每個元素(即每個指針)都可以單獨指向不同的內存空間,因此它可以用于存儲不同類型或不同位置的數據,提供了更大的靈活性。
數組的指針:
(1)數組的指針是一個指針,它指向一個數組的首地址。也就是說,它存放的是一個數組的首地址。
(2)聲明方式為 type (*ptr)[size],其中 type 為數組中元素的數據類型,size 為數組的大小。例如,int (*ptr)[5] 表示一個指向包含5個整數類型元素的數組的指針。
(3)數組指針解析出來的是整個數組,因此可以通過該指針遍歷并訪問數組的所有元素。數組指針通常用于處理多維數組。
區別
(1)本質:指針的數組本質上是一個數組,而數組的指針本質上是一個指針。
(2)用途:指針的數組通常用于存儲指向不同類型或不同位置的數據的指針,而數組的指針用于訪問和操作整個數組。
(3)聲明方式:指針的數組聲明時,* 緊跟在類型后面;而數組的指針聲明時,* 緊跟在變量名前面,并用括號 () 將 * 和變量名括起來。
(4)靈活性:指針的數組更加靈活,因為它允許存儲指向不同類型數據的指針;而數組的指針則更專注于對整個數組的操作。
面試題 5 :什么是函數指針?如何使用它?
函數指針是一個指針變量,它存儲了函數的內存地址,因此可以通過該指針變量來調用函數。函數指針的聲明方法一般為:返回值類型 (*指針變量名)([形參列表])。
使用函數指針的基本步驟如下:
(1)定義函數指針類型:首先,需要定義一個函數指針類型,這通常是通過定義一個指向特定類型函數的指針變量來實現的。例如,如果有一個返回 int 類型并接受兩個 int 類型參數的函數,可以這樣定義函數指針類型: int (*func)(int, int) ;
(2)初始化函數指針:然后,可以將一個函數的地址賦值給這個指針。這通常是通過將函數名(不帶括號)賦值給指針變量來實現的。例如,如果有一個名為 myFunc 的函數,可以這樣初始化函數指針:func = myFunc;
(3)通過函數指針調用函數:最后,可以通過函數指針來調用函數。這通常是通過在函數指針后加上括號,并在括號內提供函數所需的參數來實現的。例如,可以這樣通過函數指針調用函數:int res = func(a, b);
需要注意的是,函數指針的使用需要謹慎,因為錯誤的使用可能會導致程序崩潰或其他未定義行為。在使用函數指針時,應確保函數指針指向的函數具有正確的返回類型和參數類型,并且在調用函數時提供了正確的參數。
此外,函數指針也有一些特定的用途,例如可以作為函數的參數,或者用于實現回調函數等高級功能。在這些情況下,函數指針的使用可能會更加復雜,需要更深入的理解和實踐。
如下為樣例代碼:
#include<stdio.h> // 定義一個函數,它接受一個整數參數并返回一個整數
int add(int a, int b)
{return a + b;
}// 定義一個函數指針類型
typedef int(*func)(int, int);// 主函數
int main()
{// 定義一個函數指針變量并初始化 func ptr = add;// 使用函數指針調用函數 int res = ptr(1, 2);printf("the result is: %d\n", res); // 輸出:The result is: 3 return 0;
}
上面代碼的輸出為:
the result is: 3
在上面代碼中,首先定義了一個函數 add ,它接受兩個整數參數并返回它們的和。然后,我們定義了一個函數指針類型 func ,它指向一個接受兩個整數參數并返回一個整數的函數。
在 main 函數中,定義了一個 func 類型的變量 ptr ,并將 add 函數的地址賦值給它。然后,通過 ptr 調用 add 函數,并將結果存儲在 res 變量中。最后打印出這個結果。