1. 變量/函數的聲明和定義的區別?
(1)變量
????????定義不僅告知編譯器變量的類型和名字,還會分配內存空間。
int x = 10; // 定義并初始化x int x; //同樣是定義
?????????聲明只是告訴編譯器變量的名字和類型,但并不為它分配內存空間。使用extern來修飾。告訴編譯器這個變量的定義在其他地方,這里使用此變量。
extern int x; // 聲明x是一個整型變量
(2)函數
????????函數定義提供了函數的具體實現,它包括了函數的返回類型、函數名、參數類型以及函數體的內容。
int add(int a, int b) {return a + b; // 函數體 }
????????函數聲明告知編譯器函數的返回類型、函數名及其參數類型,但并不提供函數的具體實現。
int add(int a, int b); // 聲明add函數
?2. sizeof和strlen的區別?
(1)
sizeof
是一個操作符(與<、>類似),而strlen
是一個庫函數(在#inlcude <string>中)。(2)
sizeof
的參數可以是任何數據類型或變量,而strlen
?只能是以‘ \0 ’結尾的字符串作為參數。(3)編譯器在編譯時就計算出了
sizeof的結果,而strlen
函數必須要在運行時才會被計算出來,其核心是因為strlen
?是通過遍歷字符串,在遇到 ‘\0’ 就會結束(結果大小不包含‘\0’),而sizeof
則會將其包含在內計算大小。char str[] = "Hello"; printf("sizeof(str) = %zu\n", sizeof(str)); // 輸出6(包括'\0') printf("strlen(str) = %zu\n", strlen(str)); // 輸出5(不包括'\0')
?3. &的用法:引用和取地址。
(1)&
用作取地址符號:表示獲取變量的內存地址。
int x = 10;
int *ptr = &x; ?// 使用 & 獲取 x 的地址,并賦給指針 ptrprintf("x 的地址:%p\n", (void*)&x); ?// 輸出 x 的內存地址
(2)&
用作引用符號:表示一個變量的引用,即該變量的別名。創建一個引用,引用是變量的別名,它不會創建新的內存空間,而是直接使用原變量的內存。
int x = 10;
int& ref = x; // ref 是 x 的引用,兩個變量共享相同的內存地址ref = 20; // 通過引用修改 x 的值
printf("x = %d\n", x); // 輸出 x = 20,引用修改了原變量
?4. static關鍵字。
????????static關鍵字可以修飾變量和函數,對此有著不同給功能。static修飾的變量默認初始化值為0。
變量
(1)在函數內部定義的變量。
????????當
static
用于函數內部的局部變量時,它表示該變量的生命周期改變。????????局部變量通常在每次函數調用時被創建并銷毀,但使用
static
關鍵字后,局部變量在函數調用結束后不會銷毀,而是保留其值,直到下一次函數調用時繼續使用上一次的值。直到程序結束后銷毀。void count_calls() {static int count = 0; // 靜態變量,只初始化一次count++;printf("This function has been called %d times.\n", count); }int main() {count_calls(); // 輸出: This function has been called 1 times.count_calls(); // 輸出: This function has been called 2 times.return 0; }
(2)在函數外部定義的變量。
????????當
static
用于函數外部的變量或函數時,它限制了該變量或函數的作用域,使得它只能在當前文件中使用,無法被其他文件訪問。對于被static修飾全局變量來說,其他文件無法通過extern
來引用它。函數
?????????當
static
用于函數時,它限制了該函數的作用域,使得它只能在當前文件中使用,無法被其他文件訪問。(即只能在當前的C文件中使用,其他文件中無法調用該函數)????????注意:在多線程程序中,使用
static
變量時要小心,因為它們的值會在多個線程之間共享,可能會引發競態條件。為了保證線程安全,通常需要使用同步機制(如互斥鎖)來訪問這些靜態變量。
5. volatile關鍵字。
????????在 C 和 C++ 中,
volatile
是一個非常重要的關鍵字,它告訴編譯器不要優化該變量的讀取或寫入操作。例如,減少不必要的變量讀取或寫入,以提高程序的效率。但是,對于某些變量,如硬件寄存器或多線程共享變量,編譯器優化可能導致程序行為不符合預期。因此,volatile
被用來告訴編譯器:不要對該變量進行優化,每次訪問該變量時,都必須從內存中讀取最新的值。????????場景:對硬件寄存器進行訪問時,都要加上此關鍵字。
#define STATUS_REGISTER (volatile int*)0x40001000int main() {int status = *STATUS_REGISTER; // 硬件寄存器的讀取,每次都要從內存重新讀取// 其他代碼 }
6. const關鍵字。?
????????在 C 和 C++ 中,
const
關鍵字用于聲明常量或表示某個對象的值不能被修改。const
提供了一種有效的方式來增強程序的可讀性、可維護性以及避免意外的修改。常見的有限制常量、指針、數組、函數參數等的修改性。(1)常量
const int x = 10; // x 是常量,值不能被修改 x = 20; // 錯誤:無法修改常量變量 x
(2)指針
????????常量指針:指向常量數據的指針,
const
放在*
之前,表示指向的數據是常量。????????作用:即通過這個指針你不能修改它所指向的數據,但指針本身可以指向其他內存位置。
const int *ptr = &x; // ptr 是指向常量 int 的指針,不能通過 ptr 修改 x 的值 *ptr = 20; // 錯誤:不能通過 ptr 修改值 ptr = &y; // 合法:可以讓 ptr 指向其他位置
????????指針常量:指的是指針本身是常量,
const
放在*
之后,表示指針本身是常量。????????作用:即你不能改變指針指向的地址,但指針所指向的數據可以被修改。
int x = 5; int y = 10; int *const ptr = &x; // ptr 是常量指針,指向 x*ptr = 20; // 合法:可以通過 ptr 修改 x 的值 ptr = &y; // 錯誤:不能改變 ptr 的值(即不能讓 ptr 指向 y)
????????常量指針指向常量:既不允許修改指針的值(即指針常量),也不允許修改指針所指向的數據(即指向常量的數據)。
const int *const ptr; // ptr 是常量指針,指向常量 int
(3)數組
????????使用
const
可以確保數組中的元素在程序執行過程中保持不變。const int arr[] = {1, 2, 3, 4}; // arr 中的元素是常量,不能修改 arr[0] = 10; // 錯誤:不能修改 arr 中的元素
(4)函數參數
????????在函數參數中使用
const
,可以確保函數不會意外修改傳入的參數,特別是對于指針或引用類型的參數。這有助于增加代碼的可維護性和安全性。void print(const int &x) {printf("%d", x); // 不能修改 x }void foo(const int *ptr) {*ptr = 10; // 錯誤:不能修改 ptr 指向的數據 }
7. inline關鍵字。?
????????在 C/C++ 中,inline
關鍵字用于請求編譯器將函數的代碼插入到調用該函數的地方,而不是通過傳統的函數調用機制(即通過棧保存返回地址、傳遞參數等)。它的目的是提高代碼的執行效率,特別是對于那些調用頻繁且函數體較小的函數。遞歸函數不能內聯。
#include <iostream>inline int square(int x) {return x * x;
}int main() {int a = 5;int result = square(a); // 在這里會將 square(a) 展開為 a * astd::cout << result << std::endl; // 輸出 25return 0;
}
????????編譯器會盡可能地將內聯函數的代碼嵌入到調用點。但是如果內聯函數太復雜,編譯器可能不會進行內聯優化,盡管我們聲明了 inline。
編譯器有最終決定權,可能會忽略 inline
關鍵字的請求。?
8. C中的 malloc 和C++中的 new 有什么區別?
在 C 和 C++ 中,
malloc
和new
都用于動態內存分配,但它們有一些重要的區別。(1)new、delete是操作符,可以重載,只能在C++中使用。而 malloc、函數 free是函數,在C++和C中都可以使用,在stdlib頭文件中。
(2)new 在?C++ 中它不僅分配內存,還會調用類的構造函數,delete調用類的析構函數(如果是類類型的話)。而 malloc 和 free 函數僅僅是分配內存和釋放內存,并不執行構造和析構函數。
(3)new、delete返回的是某種數據類型的指針,而malloc和free返回的是void類型的指針(因此需要強制類型轉換)。
(4)malloc申請的內存要使用free來釋放,new申請的內存要使用delete來釋放,兩者不能混用,因為底層實現原理不同。
(5)malloc申請內存失敗時會返回NULL,所以判斷返回值來判斷內存是否申請成功。而new申請內存失敗時會拋出異常。
int* arr = (int*)malloc(10 * sizeof(int)); // 分配 10 個整數的空間 free(arr); // 釋放內存int* arr = new int[10]; // 分配 10 個整數的數組,自動初始化 delete[] arr; // 釋放數組,自動調用析構函數
9. 程序中的內存分配方式。
(1)棧區:對于所有的局部變量(除了局部靜態變量),都存儲在棧區中,棧內存的分配和釋放由編譯器自動管理,不需要程序員顯式調用。
(2)堆區:使用malloc/new創建的內存都存儲在堆區,需要程序員手動創建和釋放。如果分配的內存,使用完成后就必須要記得釋放,不然會造成內存泄漏的風險!
(3)靜態存儲區:用于存放全局變量和靜態變量,內存在程序編譯時就已經分配好了,這塊內存在程序整個運行期間都存在。
?10. 什么是野指針,如何避免?
????????野指針(Dangling Pointer)是指指向已經被釋放或未初始化的內存位置的指針。野指針是指針操作中常見的錯誤之一,它會導致程序崩潰、內存泄漏或者不預期的行為。平時使用時一定要避免野指針的情況。如下所示:
// 1. 使用銷毀的指針 int *ptr = new int(10); // 在堆上分配內存 delete ptr; // 釋放內存 // ptr 現在是一個野指針,因為它指向已釋放的內存//第二種情況: int* createPointer() {int x = 10; // 局部變量 xreturn &x; // 返回指向 x 的指針 } int main() {int* p = createPointer(); // p 指向局部變量 xprintf("%d\n", *p); // 試圖訪問已超出作用范圍的變量return 0; } /* 原因:局部變量在函數執行完畢后銷毀,指針p獲取到的是銷毀空間的變量,會導致崩潰*/// 2. 使用未初始化的指針 int *ptr; // 未初始化的指針 *ptr = 10; // 訪問未初始化的指針,導致未定義行為// 3. 超過作用域。
野指針的產生原因及解決辦法如下:
(1)指針變量未初始化。解決辦法:指針聲明時初始化,可以是具體位置,也可以指向NULL。
int *ptr = NULL; // C語言中使用NULL初始化
(2)使用被free或delete釋放的指針。解決辦法:指針指向的內存空間被釋放后,應該指向NULL。
int *p=(int *)malloc(sizeof(int)); free(p);p=NULL;
(3)指針越界。解決辦法:在變量的作用域結束前釋放掉變量的地址空間并且指向NULL。
?11.?什么是函數指針和指針函數?
(1)函數指針
????????是指向函數的指針。可以通過它調用函數,使得程序在運行時能夠動態地決定調用哪個函數。這在實現回調函數、函數數組等情況下非常有用。
定義一個函數指針:
返回類型 (*指針變量名)(參數類型1, 參數類型2, ...);
使用舉例:?
#include <iostream>// 定義一個普通函數 int add(int a, int b) {return a + b; }int main() {// 聲明一個指向函數的指針int (*func_ptr)(int, int);// 將指針指向 add 函數func_ptr = &add;// 通過函數指針調用函數int result = func_ptr(3, 4); // 調用 add(3, 4)std::cout << "Result: " << result << std::endl; // 輸出 7return 0; }
(2)指針函數
? ? ? ? 指返回指針的函數。它是一個普通的函數,只是返回值類型是一個指針。
返回類型 *函數名(參數類型1, 參數類型2, ...);
12. 指針的大小。
????????在 C/C++ 中,指針的大小是由編譯器和系統架構決定的。指針本身的大小與它所指向的數據類型(
int
、double
、char
等)無關,而是與計算機的位數有關。在 32 位系統中,指針通常占 4 字節(32 位),在 64 位系統中,指針通常占 8 字節(64 位)。
13. 內存對齊。
? ? ? ? 內存對齊指計算機中數據在內存中的存儲方式,確保數據結構的成員按照特定規則排列,以提高訪問效率。?
(1)為什么要進行內存對齊?
????????CPU訪問內存時,如果數據地址是對齊的(比如4字節對齊),那么訪問速度會更快。如果數據沒有對齊,可能需要多次訪問內存,甚至導致錯誤。尤其是在不同的硬件平臺上,對齊要求可能不同,所以編譯器會自動進行內存對齊優化。
(2)對齊規則。
???????通常,每個數據類型的對齊要求是其自身的大小。比如,int通常是4字節,所以它需要4字節對齊;double是8字節,需要8字節對齊。結構體的對齊要求則是其成員中最大的對齊值。結構體的總大小需要是對齊值的整數倍,所以在成員之間可能會插入填充字節。
(3)如何減少填充?
????????對于結構體而言,調整成員順序可以優化結構體大小。如下所示:
//字節大小為24 struct MyStruct {int a; // 4 字節,對齊值 4double b; // 8 字節,對齊值 8char c; // 1 字節,對齊值 1 };//字節大小為16 struct OptimizedStruct {double b; // 8 字節,對齊值 8int a; // 4 字節,對齊值 4char c; // 1 字節,對齊值 1 };
14. 結構體和聯合體中成員所占內存大小。?
(1)內存分配。
????????結構體中的每個成員都有自己的內存空間,所有成員的內存是按順序排列的。
????????聯合體中的所有成員共享同一塊內存空間。
(2)內存所占大小。
????????結構體的總大小是各個成員大小的總和(可能會有填充字節以保證字節對齊,結構體的對齊方式通常由其最大成員的對齊要求決定。)。
????????聯合體無論定義了多少個成員,內存大小總是等于其最大成員的大小。
舉例:
#include <iostream> //結構體 struct MyStruct { int a; // 4 bytesdouble b; // 8 byteschar c; // 1 byte };struct MyStruct2 { int a; // 4 byteschar c; // 1 bytedouble b; // 8 bytes };//聯合體 union MyUnion {int a; // 4 bytesdouble b; // 8 byteschar c; // 1 byte };int main() {MyStruct s1 = {1, 3.14, 'A'};MyStruct2 s2 = {1, 3.14, 'A'};MyUnion u={10,2.2,'C'};std::cout << "Size of struct: " << sizeof(s1) << " bytes" << std::endl; //大小為24字節。std::cout << "Size of struct: " << sizeof(s2) << " bytes" << std::endl; //大小為16字節。std::cout << "Size of union: " << sizeof(u) << " bytes" << std::endl; //大小為8字節。return 0; }
結構體大小分析:以使得結構體的總大小是 8最大成員所占字節的倍數。
int a
?占用 4 字節。插入 4 字節填充,使?double b
?從 8 字節邊界開始。
double b
?占用 8 字節。
char c
?占用 1 字節。插入 7 字節填充,使結構體總大小為 8 的倍數。?大小:4+4+8+1+7=24。
15. 數組和鏈表的區別。
(1)數組的地址空間是連續的,而鏈表的地址空間不是連續的。
(2)數組大小固定,而鏈表的大小不固定。
(3)數組的訪問速度更快。數組直接可以使用下標進行訪問,而鏈表則需要遍歷訪問。
(4)鏈表增刪改查的速度更快。
(5)數組適用于數據量固定或變化不大,且需要頻繁隨機訪問的場景。鏈表適合需要頻繁插入和刪除,不需要隨機訪問的場景。
16. define和typedef的區別。?
(1)
#define
?是 C/C++ 中的預處理指令,它在編譯之前由預處理器處理,進行簡單的文本替換。適合定義常量、宏或代碼片段,但不安全且難以調試。●特點:不進行類型錯誤檢查,只是簡單的文本替換。可以定義常量、函數宏或代碼片段。
#define PI 3.14159 // 定義常量 #define MAX(a, b) ((a) > (b) ? (a) : (b)) // 定義函數宏int main() {double radius = 5.0;double area = PI * radius * radius; // 替換為 3.14159 * radius * radiusint max_value = MAX(10, 20); // 替換為 ((10) > (20) ? (10) : (20))return 0; }
(2)
typedef
?是 C/C++ 中的關鍵字,用于為現有類型定義別名。類型安全且易于調試,適合提高代碼可讀性和維護性。●特點:進行類型檢查,是類型安全的。只能用于定義類型別名,不能定義常量或宏。
typedef unsigned int uint; // 定義 uint 為 unsigned int 的別名 typedef int* IntPtr; // 定義 IntPtr 為 int* 的別名int main() {uint x = 10; // 等價于 unsigned int x = 10;IntPtr p = &x; // 等價于 int* p = &x;return 0; }
???????如果需要類型安全或定義復雜類型別名,優先使用?
typedef
;如果需要定義常量或宏函數,可以使用?#define
。
17. 程序分為幾個段?
?????????通常,程序分為代碼段(text)、數據段(data)、BSS段、堆(heap)和棧。具體詳情查看本文內容第二章節。
●代碼段:存儲可執行指令(編譯后的機器碼)。
●數據段:通常包括已初始化的全局變量和靜態變量。
●BSS段:存放未初始化的全局變量和靜態變量,或者初始化為0的變量。?
●堆:動態分配的內存(如?
malloc
)。●棧:局部變量。
18. 棧和隊列的區別?
(1)核心規則
?
(2)基本操作
?(3)結構特點
(4)示例
19. c文件是如何轉為可執行文件的?
具體詳情查看文章:Linux環境下的編譯和調試。