高性能計算面經
- C++八股文
- 真景一面涼經
- 自我介紹,介紹一下你做過的加速的模塊(疊噪,噪聲跟原圖有什么關系?)
- OpenGL和OpenCL有什么區別?
- **1. 核心用途**
- **2. 編程模型**
- **3. 硬件抽象**
- **4. API設計**
- **5. 典型應用場景**
- **6. 互操作性**
- **總結表**
- **選擇依據**
- 為什么要用OpenCL?
- OpenGL和OpenCL同時走會有GPU搶占問題,怎么解決?為什么要內存拷貝?
- OpenCL為什么對功耗有優化?
- 你們是怎么做優化的(性能拆解和分析)?
- 性能本地跑很快,部署到手機很慢怎么辦?跟別的算法一起跑,性能有影響怎么辦(并行,調度)?
- OpenCL的img和buf有什么區別?
- **1. 數據結構和存儲方式**
- **2. 訪問方式**
- **3. 性能特點**
- **4. 典型應用場景**
- **5. 創建方式**
- **6. 內核代碼中的訪問**
- **7. 關鍵區別總結**
- **何時選擇?**
- **互操作示例**
- 說一下內存對齊,為什么要做字節對齊
- 你們做加速有多少人?怎么劃分的?哪些模塊是你獨立完整實現的?
- Neon是如何做的?
- 穩定性和效果問題是怎么排查的?
- 如果只能靠壓測且低概率出現的問題,怎么解決?
- 為什么想離職?期望是多少?
- 反問:部門做服務端AI加速,有4-5個人
- 影石一面
- 什么是8bit量化?量化是怎么實現的?
- **1. 什么是8bit量化?**
- **2. 量化的核心步驟**
- **具體實現方法**
- **3. 量化的類型**
- **4. 實現示例**
- **5. 量化的優勢與挑戰**
- **總結**
- 量化之后出現精度損失怎么辦?
- 1. **校準過程優化**
- 2. **量化策略調整**
- 3. **模型結構調整**
- 4. **后處理與微調**
- 5. **工具與位寬選擇**
- 6. **高級技術融合**
- 7. **驗證與監控**
- 實施示例(PyTorch QAT):
- 關鍵點總結:
- C和C++有什么區別?
- 1. **編程范式**
- 2. **核心特性**
- 3. **內存管理**
- 4. **標準庫**
- 5. **兼容性**
- 6. **應用場景**
- 7. **語法細節差異**
- 8. **性能**
- 總結
- C++如何調用C函數?反過來呢?
- **1. C++調用C函數**
- **關鍵步驟**:使用 `extern "C"` 聲明C函數,避免C++的名稱修飾。
- **示例**:
- **2. C調用C++函數**
- **關鍵步驟**:在C++代碼中,用 `extern "C"` 導出C兼容的函數接口,并在C中聲明。
- **示例**:
- **注意事項**
- **總結**
- C++中如何new和delete一個數組?delete沒有加[]會怎么樣?
- **1. 如何正確分配和釋放數組**
- **分配數組**
- **釋放數組**
- **2. 錯誤使用 `delete` 而非 `delete[]` 的后果**
- **(1) 內存泄漏或資源泄漏**
- **(2) 堆內存損壞**
- **(3) 行為不可預測**
- **3. 對基本類型數組的影響**
- **4. 總結與最佳實踐**
- **建議**:
- **示例驗證**
- C++的多態是怎么實現的?
- **1. 虛函數表(vtable)**
- **2. 虛函數指針(vptr)**
- **3. 動態綁定過程**
- **4. 關鍵特性與注意事項**
- **5. 總結**
- 類中有虛函數,析構函數應該注意什么?什么情況下會觸發虛函數?
- **一、虛函數與析構函數的注意事項**
- **1. 虛析構函數必須存在**
- **2. 構造函數和析構函數中避免調用虛函數**
- **二、虛函數的觸發條件**
- **1. 通過指針或引用調用虛函數**
- **2. 虛函數未被隱藏或覆蓋**
- **3. 虛函數未被`final`禁止重寫**
- **三、總結**
- **關鍵點**
- **示例驗證**
- 指針和引用的區別?
- **1. 定義與本質**
- **示例代碼**
- **2. 操作與功能**
- **示例代碼**
- **3. 參數傳遞與語義**
- **示例代碼**
- **4. 內存管理**
- **示例代碼**
- **5. 底層實現**
- **總結:選擇指針還是引用?**
- **核心原則**
- 什么是深拷貝和淺拷貝?
- **1. 淺拷貝(Shallow Copy)**
- **示例問題**
- **2. 深拷貝(Deep Copy)**
- **正確使用**
- **3. 關鍵區別總結**
- **4. 何時需要深拷貝?**
- **5. 其他語言的實現**
- **6. 總結**
- C++中opencv的mat是怎么深拷貝和淺拷貝的?
- 說一下快排,二分查找
- 快速排序(Quick Sort)
- 二分查找(Binary Search)
- 對比總結
- char a = -1,在計算機內存中是怎么存儲的?
- **1. 符號性決定存儲形式**
- **2. 存儲過程**
- **情況1:有符號char(signed char)**
- **情況2:無符號char(unsigned char)**
- **3. 驗證代碼**
- **4. 總結**
- **5. 擴展:補碼的優勢**
- C++實現sharedPtr,手寫C++的string類
- 對cache的理解,如何提高緩存命中率?
- 數據結構中順序存儲和鏈式存儲的優缺點
- 大疆車載面經
- 自我介紹+介紹簡歷項目
- 圍繞項目問涉及的知識點
- 問了很多DSP的內容(項目相關)
- 回顧你做的項目,你覺得有哪些地方可以改進?有哪些挑戰?難忘的經歷
- 說說你對大疆車載的了解,反問
- 優化手段有哪些
- 模型推理加速是怎么整的
- 量化部署過程
- gpu加速有什么好處,原理是什么
- cuda相關
- 多線程如何使用,痛點是什么
- 如何做同步,同步的手段有哪些?區別是什么?分別用在什么場景
- 內存優化有哪些手段?dma相關的
- cache cpu 內存之間的聯系
- 高性能面試問題
C++八股文
真景一面涼經
自我介紹,介紹一下你做過的加速的模塊(疊噪,噪聲跟原圖有什么關系?)
OpenGL和OpenCL有什么區別?
OpenGL(Open Graphics Library)和OpenCL(Open Computing Language)是兩種不同的開放標準,由Khronos Group維護,但它們的應用場景和設計目標有顯著區別。以下是兩者的主要區別:
1. 核心用途
-
OpenGL
專注于圖形渲染,用于在GPU上高效渲染2D/3D圖形。它提供了一套API,允許開發者利用硬件加速繪制復雜的圖形場景(如游戲、CAD、可視化等)。 -
OpenCL
專注于通用并行計算,允許開發者利用GPU、CPU或其他計算設備(如FPGA)進行高性能計算。它適用于科學計算、物理模擬、圖像處理、機器學習等需要大規模并行計算的場景。
2. 編程模型
-
OpenGL
- 基于圖形管線(Graphics Pipeline),包含頂點處理、光柵化、片段處理等固定或可編程階段。
- 使用著色器語言(GLSL)編寫頂點著色器、片段著色器等程序。
- 以圖形對象(如紋理、緩沖區、幀緩存)為中心,操作圍繞繪制圖形展開。
-
OpenCL
- 基于數據并行或任務并行模型,強調將計算任務分解為多個并行執行的工作項(Work Items)。
- 使用C語言擴展(OpenCL C)編寫內核(Kernel)函數,直接在計算設備上運行。
- 以數據緩沖區和計算任務為中心,操作圍繞數據處理展開。
3. 硬件抽象
-
OpenGL
- 主要面向GPU的圖形功能(如光柵化、紋理映射、深度測試)。
- 設計目標是最大化圖形渲染性能,對圖形硬件的細節進行抽象。
-
OpenCL
- 面向異構計算(支持CPU、GPU、DSP等多種設備)。
- 提供對硬件資源的更底層控制(如內存模型、工作組劃分),適合優化計算密集型任務。
4. API設計
-
OpenGL
- 狀態機(State Machine)驅動,通過設置全局狀態(如顏色、紋理)控制渲染流程。
- 強調圖形管線的配置(如綁定著色器、設置頂點數據)。
-
OpenCL
- 面向任務的API,需要顯式管理計算設備、上下文、命令隊列等。
- 更接近通用編程模型,支持異步任務提交和內存傳輸。
5. 典型應用場景
-
OpenGL
- 游戲開發、3D建模軟件(如Blender)、虛擬現實(VR)、圖形界面渲染。
- 例如:通過OpenGL繪制游戲中的角色和場景。
-
OpenCL
- 科學計算(如流體模擬)、圖像處理(如濾鏡加速)、密碼學、深度學習推理加速。
- 例如:用OpenCL加速Photoshop中的圖像處理算法。
6. 互操作性
- 兩者可以結合使用:
- OpenCL處理計算任務(如物理模擬、圖像生成),結果通過OpenGL渲染。
- 通過共享內存對象(如OpenCL緩沖區與OpenGL紋理互操作),減少數據傳輸開銷。
總結表
特性 | OpenGL | OpenCL |
---|---|---|
目標 | 圖形渲染 | 通用并行計算 |
硬件 | 主要GPU圖形單元 | 支持多設備(GPU/CPU等) |
編程模型 | 圖形管線 + 著色器 | 數據/任務并行 + 內核函數 |
主要API | 狀態機驅動(glBind*) | 顯式任務管理(clEnqueue*) |
適用領域 | 游戲、可視化、UI | 科學計算、圖像處理、AI |
選擇依據
- 需要渲染圖形(如3D模型、特效) → OpenGL 或現代替代品(Vulkan、DirectX)。
- 需要加速數值計算(如矩陣運算、模擬) → OpenCL 或類似框架(CUDA、SYCL)。
- 混合場景 → 結合兩者(如用OpenCL生成數據,OpenGL渲染)。
為什么要用OpenCL?
OpenGL和OpenCL同時走會有GPU搶占問題,怎么解決?為什么要內存拷貝?
OpenCL為什么對功耗有優化?
你們是怎么做優化的(性能拆解和分析)?
性能本地跑很快,部署到手機很慢怎么辦?跟別的算法一起跑,性能有影響怎么辦(并行,調度)?
OpenCL的img和buf有什么區別?
在OpenCL中,image
對象(圖像內存對象)和buffer
對象(緩沖區內存對象)是兩種不同的內存類型,設計目標和適用場景有明顯區別。以下是它們的核心差異:
1. 數據結構和存儲方式
特性 | Buffer (cl_mem ) | Image (cl_mem ) |
---|---|---|
數據結構 | 一維線性內存,存儲任意數據 | 多維結構化數據(2D/3D),類似紋理 |
數據格式 | 無格式要求(原始字節流) | 需明確指定像素格式(如CL_RGBA , CL_FLOAT 等) |
存儲優化 | 普通全局內存 | 可能存儲在GPU的紋理內存中(硬件緩存優化) |
2. 訪問方式
特性 | Buffer | Image |
---|---|---|
讀寫接口 | 直接通過指針(__global float* ) | 必須通過采樣器(Sampler)和坐標(如read_imagef ) |
坐標訪問 | 線性索引(buffer[offset] ) | 多維坐標(如(x, y, z) ) |
自動濾波 | 不支持 | 支持(如雙線性插值、邊界處理) |
數據類型轉換 | 無自動轉換 | 自動轉換為規范化值(如[0,1] ) |
3. 性能特點
特性 | Buffer | Image |
---|---|---|
緩存效率 | 依賴訪問模式(適合隨機訪問) | 利用空間局部性(適合相鄰像素訪問) |
硬件加速 | 無特殊優化 | 可能使用紋理硬件單元(高速緩存、濾波) |
內存帶寬 | 普通全局內存帶寬 | 可能更高(紋理內存的合并訪問優化) |
4. 典型應用場景
場景 | Buffer | Image |
---|---|---|
適合任務 | 通用數據(數組、結構體等) | 圖像/紋理處理(濾波、插值等) |
示例 | 矩陣運算、物理模擬、數值計算 | 圖像卷積、光線追蹤采樣、體積渲染 |
5. 創建方式
// Buffer的創建(一維數據)
cl_mem buffer = clCreateBuffer(context, CL_MEM_READ_WRITE, size_in_bytes, NULL, &err
);// Image的創建(需指定格式和維度)
cl_image_format format;
format.image_channel_order = CL_RGBA; // 通道順序
format.image_channel_data_type = CL_FLOAT; // 數據類型cl_mem image = clCreateImage2D(context, CL_MEM_READ_ONLY, &format, width, height, 0, // row_pitch(0表示連續存儲)NULL, &err
);
6. 內核代碼中的訪問
// Buffer的訪問(直接指針操作)
__kernel void buffer_kernel(__global float* buffer) {int idx = get_global_id(0);buffer[idx] = ...;
}// Image的訪問(需采樣器和坐標)
__constant sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST;__kernel void image_kernel(__read_only image2d_t input, __write_only image2d_t output) {int2 coord = (int2)(get_global_id(0), get_global_id(1));float4 pixel = read_imagef(input, sampler, coord); // 讀取像素write_imagef(output, coord, pixel * 2.0f); // 寫入像素
}
7. 關鍵區別總結
維度 | Buffer | Image |
---|---|---|
數據布局 | 一維線性 | 多維結構化 |
硬件支持 | 普通內存 | 紋理內存+硬件濾波 |
適用場景 | 通用計算 | 圖像/空間數據 |
訪問靈活性 | 高(直接指針) | 低(需采樣器+坐標) |
性能優化 | 依賴算法 | 空間局部性+緩存優化 |
何時選擇?
-
用Buffer:
需要處理任意結構的原始數據(如數值數組、自定義結構體)或需要直接內存操作(如原子操作)。 -
用Image:
處理圖像/紋理數據,且需要利用硬件加速的濾波(如雙線性插值)、規范化坐標(如[0,1]
范圍)或空間局部性優化(如卷積核計算)。
互操作示例
兩者可以結合使用:
- 用Buffer存儲中間計算結果,再轉換為Image進行渲染。
- 從Image讀取數據后,用Buffer進行非圖像類計算(如統計像素值)。
說一下內存對齊,為什么要做字節對齊
你們做加速有多少人?怎么劃分的?哪些模塊是你獨立完整實現的?
Neon是如何做的?
穩定性和效果問題是怎么排查的?
如果只能靠壓測且低概率出現的問題,怎么解決?
為什么想離職?期望是多少?
反問:部門做服務端AI加速,有4-5個人
影石一面
網上的參考:
什么是8bit量化?量化是怎么實現的?
8bit量化是一種通過降低數據精度來減少存儲和計算開銷的技術,常用于優化深度學習模型。以下是詳細的解釋和實現步驟:
1. 什么是8bit量化?
- 核心概念:將高精度(如32位浮點數)數據映射到8位整數(范圍通常為0-255或無符號整數,或-128-127有符號整數),以減少內存占用和加速計算。
- 應用場景:主要用于模型推理階段的優化,適用于移動端、嵌入式設備等資源受限環境。
2. 量化的核心步驟
量化通過以下公式實現:
q = round ( x S + Z ) 和 x ′ = S ? ( q ? Z ) q = \text{round}\left(\frac{x}{S} + Z\right) \quad \text{和} \quad x' = S \cdot (q - Z) q=round(Sx?+Z)和x′=S?(q?Z)
其中:
- (x):原始浮點數值。
- (q):量化后的8位整數。
- (S)(縮放因子):浮點范圍與整數范圍的比值。
- (Z)(零點):將浮點零點對齊到整數的偏移量。
具體實現方法
-
確定浮點范圍:
- 非對稱量化:統計張量中的最小值( x min x_{\text{min}} xmin?)和最大值( x max x_{\text{max}} xmax?)。
- 對稱量化:取絕對值最大的值( x max = max ? ( ∣ x min ∣ , ∣ x max ∣ ) x_{\text{max}} = \max(|x_{\text{min}}|, |x_{\text{max}}|) xmax?=max(∣xmin?∣,∣xmax?∣)),范圍變為 [ ? x max , x max ] [-x_{\text{max}}, x_{\text{max}}] [?xmax?,xmax?]。
-
計算縮放因子((S)):
- 非對稱量化:
S = x max ? x min 2 8 ? 1 ( 如用無符號8位整數 ) S = \frac{x_{\text{max}} - x_{\text{min}}}{2^8 - 1} \quad (\text{如用無符號8位整數}) S=28?1xmax??xmin??(如用無符號8位整數) - 對稱量化:
S = x max 2 7 ? 1 ( 如用有符號8位整數,范圍-128?127 ) S = \frac{x_{\text{max}}}{2^{7} - 1} \quad (\text{如用有符號8位整數,范圍-128~127}) S=27?1xmax??(如用有符號8位整數,范圍-128?127)
- 非對稱量化:
-
計算零點((Z))(僅非對稱量化需要):
Z = round ( 0 ? x min S ) Z = \text{round}\left(0 - \frac{x_{\text{min}}}{S}\right) Z=round(0?Sxmin??)- 用于對齊浮點數0與整數零點(例如,處理負數)。
-
量化與反量化:
- 量化:將浮點數轉換為整數:
q = clip ( round ( x S + Z ) , 0 , 255 ) q = \text{clip}\left(\text{round}\left(\frac{x}{S} + Z\right), 0, 255\right) q=clip(round(Sx?+Z),0,255) - 反量化:恢復近似浮點值:
x ′ = S ? ( q ? Z ) x' = S \cdot (q - Z) x′=S?(q?Z)
- 量化:將浮點數轉換為整數:
3. 量化的類型
- 訓練后量化(Post-Training Quantization, PTQ):
- 直接對訓練好的模型進行校準(統計各層輸入/輸出的范圍),無需重新訓練。
- 速度快但可能損失精度。
- 量化感知訓練(Quantization-Aware Training, QAT):
- 在訓練時模擬量化過程,讓模型適應低精度誤差。
- 精度更高但需要額外訓練時間。
4. 實現示例
以非對稱量化為例:
- 輸入浮點范圍:[-3.0, 5.0]
- 計算縮放因子:
S = 5.0 ? ( ? 3.0 ) 255 ≈ 0.03137 S = \frac{5.0 - (-3.0)}{255} \approx 0.03137 S=2555.0?(?3.0)?≈0.03137 - 計算零點:
Z = round ( 0 ? ? 3.0 0.03137 ) ≈ 96 Z = \text{round}\left(0 - \frac{-3.0}{0.03137}\right) \approx 96 Z=round(0?0.03137?3.0?)≈96 - 量化值:
q = round ( ? 3.0 0.03137 + 96 ) = 0 ( 下限 ) q = \text{round}\left(\frac{-3.0}{0.03137} + 96\right) = 0 \quad (\text{下限}) q=round(0.03137?3.0?+96)=0(下限)
q = round ( 5.0 0.03137 + 96 ) = 255 ( 上限 ) q = \text{round}\left(\frac{5.0}{0.03137} + 96\right) = 255 \quad (\text{上限}) q=round(0.031375.0?+96)=255(上限)
5. 量化的優勢與挑戰
- 優勢:
- 模型大小減少約75%(32位→8位)。
- 內存帶寬需求降低,推理速度提升(支持整數運算硬件)。
- 挑戰:
- 精度損失需通過校準或QAT緩解。
- 某些層(如殘差連接)對量化敏感,需特殊處理。
總結
8bit量化通過縮放因子和零點將浮點數映射到8位整數,顯著壓縮模型并加速推理。其實現需權衡精度與效率,常用方法包括訓練后量化和量化感知訓練。實際應用中,TensorFlow Lite、PyTorch等框架已提供自動化工具簡化流程。
量化之后出現精度損失怎么辦?
模型量化后出現精度損失是常見問題,但可通過多種方法緩解。以下是分步解決方案:
1. 校準過程優化
- 使用代表性數據:確保校準數據集覆蓋真實場景的數據分布,避免偏差。
- 動態范圍調整:采用百分位數校準(如99.99%)而非最大值,避免異常值影響范圍估計。
- 逐通道量化(Per-Channel):對卷積層按通道單獨量化,減少權重分布差異帶來的誤差。
2. 量化策略調整
- 混合精度量化:對敏感層(如首尾層、注意力機制)保留FP16,其他層量化到INT8。
- 對稱與非對稱量化:嘗試激活層用非對稱量化(處理ReLU后的非負值),權重用對稱量化。
- 量化感知訓練(QAT):在訓練中模擬量化噪聲,微調模型參數以適應低精度。
3. 模型結構調整
- 插入歸一化層:在激活函數前添加BatchNorm或LayerNorm,穩定激活值分布。
- 避免敏感操作:減少通道間差異大的結構(如深度可分離卷積),或用量化友好型結構(如MobileNet)。
4. 后處理與微調
- 量化后微調:使用少量數據對量化模型微調,調整參數補償量化誤差。
- 逐層誤差分析:對比量化前后各層輸出,針對高誤差層進行優化(如提升其量化位寬)。
5. 工具與位寬選擇
- 更換量化工具:嘗試TensorRT、TFLite等不同框架,利用其優化特性。
- 調整位寬:對關鍵層嘗試INT16,或在支持的情況下使用混合位寬(如8/4位混合)。
6. 高級技術融合
- 知識蒸餾:用原模型指導量化模型訓練,提升小模型精度。
- 自適應量化:動態調整不同輸入的量化參數,平衡精度與速度。
7. 驗證與監控
- 分階段量化:逐步量化模型部分層,監控精度變化,定位敏感模塊。
- 魯棒性測試:在邊緣案例(Corner Cases)上驗證量化模型表現,針對性增強。
實施示例(PyTorch QAT):
import torch
from torch.quantization import QuantStub, DeQuantStub, prepare_qat, convertclass QATModel(torch.nn.Module):def __init__(self, original_model):super().__init__()self.quant = QuantStub()self.model = original_modelself.dequant = DeQuantStub()def forward(self, x):x = self.quant(x)x = self.model(x)x = self.dequant(x)return x# 加載預訓練模型
model = ... # 原始模型
qat_model = QATModel(model)# 配置量化參數
qat_model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')# 準備并訓練
prepare_qat(qat_model, inplace=True)
# 微調訓練循環(模擬量化噪聲)
train(qat_model, train_loader, epochs=5)# 轉換為量化模型
quantized_model = convert(qat_model.eval())
關鍵點總結:
- 校準數據質量和量化策略是影響精度的核心因素。
- QAT通常比后訓練量化(PTQ)效果更好,但需訓練資源。
- 模型本身對量化的友好性(如結構設計)決定上限,必要時需結構調整。
通過上述方法逐步調試,通常可將精度損失控制在1%以內,甚至無損量化。若仍不達標,需權衡性能與精度,考慮硬件支持的極限位寬或異構計算方案。
C和C++有什么區別?
C和C++是兩種廣泛使用的編程語言,雖然C++最初是作為C的擴展開發的,但兩者在設計和用途上有顯著區別。以下是它們的主要差異:
1. 編程范式
- C:過程式編程語言,強調函數和步驟的分解,通過函數模塊化解決問題。
- C++:多范式語言,支持面向對象編程(類、繼承、多態)、泛型編程(模板)、過程式編程,甚至函數式編程。
2. 核心特性
-
C:
- 無類和對象的概念。
- 不支持函數重載(同名函數)和運算符重載。
- 沒有引用(僅用指針),無異常處理機制。
- 結構體(
struct
)不能包含函數。
-
C++:
- 面向對象特性:類(
class
)、繼承、多態(虛函數)、封裝。 - 函數重載:允許同名函數根據參數不同實現不同功能。
- 運算符重載:例如自定義
+
操作符的行為。 - 引用類型:通過
int& ref = a
直接操作變量。 - 模板:支持泛型編程(如
vector<T>
)。 - 異常處理:
try/catch/throw
機制。 - 命名空間:避免命名沖突(
namespace
)。
- 面向對象特性:類(
3. 內存管理
- C:手動管理內存,僅通過
malloc
/calloc
和free
操作堆內存。 - C++:引入
new
和delete
運算符,支持構造/析構函數自動管理資源(如RAII技術)。
4. 標準庫
- C:標準庫較小,提供基礎功能(如
stdio.h
,stdlib.h
, 字符串處理等)。 - C++:包含C的大部分庫,并擴展了面向對象和泛型的庫:
- STL(標準模板庫):容器(
vector
,map
)、算法(sort
,find
)、迭代器。 - 輸入輸出流:
cout
/cin
代替printf
/scanf
。
- STL(標準模板庫):容器(
5. 兼容性
- C++兼容C:大多數C代碼可在C++中編譯(需注意少數語法差異,如強制類型轉換)。
- C不兼容C++:C無法直接使用C++的類、引用等特性。
6. 應用場景
- C:適合底層開發(操作系統、嵌入式系統、驅動程序),追求極致性能和硬件控制。
- C++:適合大型軟件、游戲引擎、GUI應用、高頻交易等,兼顧性能與抽象需求。
7. 語法細節差異
特性 | C | C++ |
---|---|---|
空指針 | NULL | nullptr (更安全) |
默認返回值 | 函數不寫返回值默認int | 必須顯式聲明返回值類型 |
結構體聲明 | 定義變量需加struct 關鍵字 | 可直接使用類型名(如MyStruct s; ) |
常量定義 | const 變量可能不是真正的常量 | const 為真正常量,支持常量表達式 |
8. 性能
- 兩者性能接近,但C++的某些高級特性(如虛函數、RTTI)可能帶來微小開銷,可通過謹慎編碼規避。
- C更貼近硬件,適合對性能極度敏感的場景。
總結
- C:簡單、高效,適合底層和資源受限環境。
- C++:功能豐富,適合復雜項目,通過抽象提高代碼可維護性,同時保持高性能。
選擇時需根據項目需求:若需要面向對象或高級抽象,選C++;若追求極簡和直接硬件控制,選C。許多開發者會同時掌握兩者以應對不同場景。
C++如何調用C函數?反過來呢?
在C++和C之間相互調用函數時,由于兩種語言的編譯機制和符號命名規則不同,需要特別注意名稱修飾(Name Mangling)和鏈接規范(Linkage Specification)。以下是具體實現方法:
1. C++調用C函數
關鍵步驟:使用 extern "C"
聲明C函數,避免C++的名稱修飾。
示例:
-
C代碼(
example.c
):#include <stdio.h>void c_function() {printf("This is a C function.\n"); }
-
C頭文件(
example.h
):聲明時添加extern "C"
(僅在C++中生效):#ifdef __cplusplus extern "C" { // 告訴C++編譯器:以下函數按C的規則編譯和鏈接 #endifvoid c_function();#ifdef __cplusplus } #endif
-
C++代碼(
main.cpp
):#include "example.h"int main() {c_function(); // 正確調用C函數return 0; }
-
編譯命令:
gcc -c example.c -o example.o # 編譯C代碼 g++ main.cpp example.o -o main # 編譯C++并鏈接C的目標文件
2. C調用C++函數
關鍵步驟:在C++代碼中,用 extern "C"
導出C兼容的函數接口,并在C中聲明。
示例:
-
C++代碼(
cpp_code.cpp
):#include <iostream>// 導出C兼容的函數(禁止名稱修飾) extern "C" void cpp_function() {std::cout << "This is a C++ function called from C.\n"; }
-
C頭文件(
cpp_header.h
):#ifdef __cplusplus extern "C" { // C++需要extern "C",C編譯器會忽略這段代碼 #endifvoid cpp_function(); // 在C中聲明為普通函數#ifdef __cplusplus } #endif
-
C代碼(
main.c
):#include "cpp_header.h"int main() {cpp_function(); // 調用C++函數return 0; }
-
編譯命令:
g++ -c cpp_code.cpp -o cpp_code.o # 編譯C++代碼 gcc main.c cpp_code.o -o main -lstdc++ # 編譯C并鏈接C++的目標文件(需C++標準庫)
注意事項
-
類型兼容性:
- 被調用的函數參數和返回值必須是C兼容的類型(如基本類型、指針、
struct
等)。 - 避免傳遞C++特有類型(如類、模板、引用等)。
- 被調用的函數參數和返回值必須是C兼容的類型(如基本類型、指針、
-
全局函數限制:
- C無法直接調用C++的類成員函數或操作對象,需通過封裝全局函數間接實現:
extern "C" void wrapper_for_cpp_class_method() {MyClass obj;obj.method(); }
- C無法直接調用C++的類成員函數或操作對象,需通過封裝全局函數間接實現:
-
名稱修飾問題:
- C++函數若未用
extern "C"
,其符號名會被編譯器修飾(如_Z12func_namev
),導致C代碼無法找到該函數。
- C++函數若未用
-
編譯器和鏈接器:
- 確保C和C++代碼分別用對應編譯器編譯(如
gcc
和g++
)。 - 鏈接時可能需要指定C++標準庫(如
-lstdc++
)。
- 確保C和C++代碼分別用對應編譯器編譯(如
總結
場景 | 關鍵方法 | 示例 |
---|---|---|
C++調用C函數 | 在C頭文件中用 extern "C" 包裹聲明 | extern "C" { void c_func(); } |
C調用C++函數 | 在C++代碼中用 extern "C" 導出函數 | extern "C" void cpp_func() { ... } |
通過合理使用 extern "C"
和類型兼容性約束,可以無縫實現C與C++的互操作。
C++中如何new和delete一個數組?delete沒有加[]會怎么樣?
在C++中,動態分配和釋放數組需要嚴格遵循new[]
和delete[]
的配對使用規則。以下是具體說明及錯誤使用的后果:
1. 如何正確分配和釋放數組
分配數組
使用 new[]
運算符為數組動態分配內存,并初始化元素(若為類類型,調用構造函數):
// 分配一個包含10個int的數組
int* intArray = new int[10]; // 分配一個包含5個對象的數組(假設MyClass有默認構造函數)
MyClass* objArray = new MyClass[5];
釋放數組
必須使用 delete[]
釋放數組內存(若為類類型,調用每個元素的析構函數):
delete[] intArray; // 正確釋放int數組
delete[] objArray; // 正確釋放對象數組
2. 錯誤使用 delete
而非 delete[]
的后果
如果誤用 delete
釋放數組,會導致未定義行為(Undefined Behavior, UB),具體表現可能包括:
(1) 內存泄漏或資源泄漏
- 類類型數組:
delete
只會調用第一個元素的析構函數,其余元素的析構函數不會被調用。
若類持有動態資源(如堆內存、文件句柄),這些資源將泄漏。class ResourceHolder { public:ResourceHolder() { data = new int[100]; } // 分配資源~ResourceHolder() { delete[] data; } // 應在析構函數釋放資源 private:int* data; };ResourceHolder* array = new ResourceHolder[3]; delete array; // 錯誤!只有第一個元素調用析構函數,剩余2個資源泄漏!
(2) 堆內存損壞
new[]
和delete[]
的實現通常會在分配的內存塊頭部記錄數組長度。
若用delete
釋放數組,內存管理器可能無法正確識別分配塊的大小,導致堆結構破壞,引發程序崩潰或難以調試的錯誤。
(3) 行為不可預測
- 未定義行為的具體表現取決于編譯器和運行時環境,可能包括:
- 程序直接崩潰。
- 內存泄漏但程序看似正常運行。
- 后續內存操作出現詭異錯誤。
3. 對基本類型數組的影響
即使數組元素是基本類型(如int
、float
),仍需使用 delete[]
:
int* arr = new int[10];
delete arr; // 錯誤!應為 delete[] arr;
- 雖然基本類型沒有析構函數,但
new[]
可能仍會記錄數組長度信息。
使用delete
會導致內存管理器錯誤解析分配塊,可能引發堆損壞。
4. 總結與最佳實踐
操作 | 正確用法 | 錯誤用法 | 后果 |
---|---|---|---|
分配數組 | new T[n] | - | - |
釋放數組 | delete[] ptr | delete ptr | 未定義行為(內存泄漏、崩潰等) |
分配單個對象 | new T | - | - |
釋放單個對象 | delete ptr | delete[] ptr | 未定義行為(堆損壞) |
建議:
- 避免手動管理數組:優先使用
std::vector
或std::unique_ptr<T[]>
等容器/智能指針。// 使用智能指針自動管理數組 std::unique_ptr<int[]> safeArray(new int[10]);
- 嚴格配對:若必須手動管理,確保
new[]
和delete[]
嚴格成對出現。
示例驗證
#include <iostream>class Test {
public:Test() { std::cout << "Constructor\n"; }~Test() { std::cout << "Destructor\n"; }
};int main() {Test* arr = new Test[3];delete[] arr; // 正確:輸出3次"Destructor"// delete arr; // 錯誤:僅輸出1次"Destructor",其余泄漏return 0;
}
輸出(正確使用 delete[]
):
Constructor
Constructor
Constructor
Destructor
Destructor
Destructor
輸出(錯誤使用 delete
):
Constructor
Constructor
Constructor
Destructor // 只有第一個元素被銷毀
遵循規則可避免資源管理錯誤,確保程序健壯性。
C++的多態是怎么實現的?
C++的多態性主要通過虛函數(Virtual Functions)和虛函數表(vtable)機制來實現,其核心在于動態綁定(Dynamic Binding),允許在運行時確定調用的具體函數。以下是詳細的實現機制:
1. 虛函數表(vtable)
- 定義:每個包含虛函數的類(或從包含虛函數的類派生的類)都有一個虛函數表。這是一個隱式的靜態數組,存儲該類所有虛函數的地址。
- 結構:
- 表中條目按虛函數的聲明順序排列。
- 若子類重寫虛函數,表中對應條目替換為子類函數的地址;未重寫則保留父類函數地址。
- 多重繼承:若類繼承多個含虛函數的基類,會為每個基類維護一個獨立的虛函數表。
2. 虛函數指針(vptr)
- 隱藏成員:每個對象實例內部包含一個指向其所屬類虛函數表的指針(
vptr
),通常位于對象內存布局的起始位置。 - 初始化:
- 在對象構造時,由構造函數隱式初始化
vptr
,使其指向當前類的虛函數表。 - 構造順序:基類→派生類,故
vptr
在構造過程中可能被多次修改,最終指向實際類型的虛函數表。
- 在對象構造時,由構造函數隱式初始化
- 析構過程:析構時反向調整
vptr
,確保銷毀階段仍能正確調用當前類的虛函數。
3. 動態綁定過程
- 調用步驟:
- 通過對象指針/引用找到
vptr
。 - 通過
vptr
定位到虛函數表。 - 根據函數在表中的偏移量獲取實際函數地址。
- 執行該地址對應的函數。
- 通過對象指針/引用找到
- 示例:
class Animal { public:virtual void speak() { cout << "Animal sound" << endl; } };class Dog : public Animal { public:void speak() override { cout << "Woof!" << endl; } };Animal* animal = new Dog(); animal->speak(); // 輸出 "Woof!"
animal->speak()
運行時,通過Dog
對象的vptr
找到Dog
的虛函數表,調用Dog::speak()
。
4. 關鍵特性與注意事項
- 虛析構函數:若通過基類指針刪除派生類對象,基類析構函數必須為虛函數,以確保正確調用派生類析構函數,避免資源泄漏。
- 性能開銷:
- 空間:每個對象需存儲
vptr
(通常4/8字節)。 - 時間:虛函數調用需兩次間接尋址(訪問
vptr
→查表→跳轉),略慢于非虛函數。
- 空間:每個對象需存儲
- 覆蓋控制:
override
(C++11):顯式標記重寫,編譯器檢查簽名一致性。final
:禁止進一步重寫或繼承。
5. 總結
C++多態通過虛函數表和虛函數指針實現動態綁定:
- 編譯時:生成虛函數表,記錄虛函數地址。
- 運行時:通過對象的
vptr
查找虛函數表,確定實際調用的函數。
這種機制在保持高效性的同時,提供了靈活的運行時多態支持,是C++面向對象設計的核心基石之一。
類中有虛函數,析構函數應該注意什么?什么情況下會觸發虛函數?
在C++中,當類包含虛函數時,析構函數的設計和虛函數的觸發條件是保證多態行為正確性的關鍵。以下是詳細解答:
一、虛函數與析構函數的注意事項
1. 虛析構函數必須存在
-
問題背景:
若通過基類指針刪除一個派生類對象,而基類析構函數未聲明為虛函數,會導致派生類的析構函數未被調用,引發資源泄漏(如內存、文件句柄未釋放)。 -
正確做法:
class Base { public:virtual ~Base() {} // 虛析構函數virtual void foo() = 0; };class Derived : public Base { public:~Derived() { /* 釋放派生類資源 */ }void foo() override { /* 實現 */ } };Base* obj = new Derived(); delete obj; // 正確調用 Derived::~Derived() 和 Base::~Base()
- 基類析構函數必須為虛函數,確保通過基類指針刪除對象時,派生類析構邏輯被執行。
2. 構造函數和析構函數中避免調用虛函數
-
問題:
在構造函數和析構函數中調用虛函數時,動態綁定失效,實際調用的是當前類的版本(而非派生類重寫的版本)。- 構造函數執行時,派生類尚未初始化,虛函數表(vtable)指向當前類的虛函數。
- 析構函數執行時,派生類已部分銷毀,虛函數表可能已恢復為基類版本。
-
示例:
class Base { public:Base() { callFoo(); } // 危險:調用 Base::foo()virtual void foo() { cout << "Base::foo" << endl; }void callFoo() { foo(); } };class Derived : public Base { public:void foo() override { cout << "Derived::foo" << endl; } };Derived d; // 輸出 "Base::foo",而非 "Derived::foo"
二、虛函數的觸發條件
虛函數的動態綁定(多態)僅在以下場景生效:
1. 通過指針或引用調用虛函數
- 動態綁定觸發條件:
Base* ptr = new Derived(); ptr->foo(); // 調用 Derived::foo()Base& ref = *ptr; ref.foo(); // 調用 Derived::foo()
- 若直接通過對象實例調用虛函數,靜態綁定生效,無多態行為:
Derived d; d.foo(); // 調用 Derived::foo() Base b = d; b.foo(); // 調用 Base::foo()(對象切片,無多態)
- 若直接通過對象實例調用虛函數,靜態綁定生效,無多態行為:
2. 虛函數未被隱藏或覆蓋
- 函數簽名必須一致:
派生類的虛函數需與基類虛函數名稱、參數、返回類型完全一致(協變返回類型除外)。- 使用
override
關鍵字(C++11)可讓編譯器檢查重寫是否合法:class Derived : public Base { public:void foo() override { /* ... */ } // 顯式標記重寫 };
- 使用
3. 虛函數未被final
禁止重寫
- 若基類虛函數被聲明為
final
,派生類無法重寫:class Base { public:virtual void foo() final {} // 禁止派生類重寫 };
三、總結
關鍵點
-
虛析構函數:
- 基類的析構函數必須為虛函數,確保多態刪除對象時資源正確釋放。
- 若類可能被繼承,且需通過基類指針操作對象,析構函數應聲明為虛函數。
-
虛函數觸發條件:
- 通過基類指針或引用調用虛函數時觸發動態綁定。
- 構造函數和析構函數中調用虛函數可能導致非預期行為。
-
設計建議:
- 若類含虛函數,析構函數應始終為虛函數(除非明確禁止繼承)。
- 使用
override
和final
增強代碼可讀性和安全性。
示例驗證
#include <iostream>
using namespace std;class Animal {
public:virtual ~Animal() { cout << "~Animal()" << endl; }virtual void speak() { cout << "Animal sound" << endl; }
};class Dog : public Animal {
public:~Dog() { cout << "~Dog()" << endl; }void speak() override { cout << "Woof!" << endl; }
};int main() {Animal* animal = new Dog();animal->speak(); // 輸出 "Woof!"delete animal; // 正確調用 ~Dog() 和 ~Animal()return 0;
}
輸出:
Woof!
~Dog()
~Animal()
通過合理使用虛析構函數和虛函數,可以確保C++多態行為的正確性和資源管理的安全性。
指針和引用的區別?
在C++中,指針(Pointer)和引用(Reference)都是用于間接訪問數據的工具,但它們在語義、用法和底層實現上存在顯著差異。以下是它們的核心區別及適用場景:
1. 定義與本質
特性 | 指針 | 引用 |
---|---|---|
本質 | 存儲變量地址的變量(獨立實體) | 變量的別名(與原變量共享內存地址) |
初始化要求 | 可以不初始化(但建議初始化為nullptr ) | 必須初始化,且綁定后不可更改指向對象 |
空值(Null) | 可以指向空地址(nullptr ) | 不能為空,必須綁定有效對象 |
示例代碼
int a = 10;// 指針
int* p = &a; // 合法
int* p2; // 合法(但未初始化,危險!)
p2 = nullptr; // 合法// 引用
int& r = a; // 合法
int& r2; // 編譯錯誤!引用必須初始化
2. 操作與功能
特性 | 指針 | 引用 |
---|---|---|
重新賦值 | 可修改指向其他對象 | 一旦綁定,無法更改(始終指向初始對象) |
內存操作 | 支持指針算術(如p++ 、p += 1 ) | 不支持算術運算(僅是別名) |
多級間接訪問 | 支持多級指針(如int** pp ) | 不支持多級引用(但可通過typedef 間接實現) |
解引用方式 | 需顯式使用* (如*p = 20 ) | 直接使用原變量名(如r = 20 ) |
示例代碼
int arr[3] = {1, 2, 3};// 指針算術
int* p = arr;
p++; // 指向arr[1]
*p = 20; // arr[1] = 20// 引用無法重新綁定
int x = 5, y = 10;
int& ref = x;
ref = y; // 實際是x = y(ref仍綁定x,而非y)
3. 參數傳遞與語義
場景 | 指針 | 引用 |
---|---|---|
函數參數 | 顯式傳遞地址,需檢查空值 | 隱式傳遞別名,語法更簡潔,無需空值檢查 |
函數重載 | void func(int*) 和void func(int&) 視為不同函數 | |
返回值 | 可以返回指針(如動態分配的對象) | 可以返回引用(如避免對象拷貝) |
示例代碼
// 指針參數
void swap_ptr(int* a, int* b) {if (a && b) { // 需檢查空指針int tmp = *a;*a = *b;*b = tmp;}
}// 引用參數(更安全)
void swap_ref(int& a, int& b) {int tmp = a;a = b;b = tmp;
}
4. 內存管理
特性 | 指針 | 引用 |
---|---|---|
動態內存 | 可指向堆內存(需手動new /delete ) | 通常綁定棧內存,生命周期由作用域管理 |
資源所有權 | 可能涉及資源所有權轉移(需謹慎管理) | 不擁有資源,僅作為別名 |
示例代碼
// 指針管理動態內存
int* p = new int(10);
delete p; // 必須手動釋放// 引用綁定棧對象
int x = 10;
int& r = x; // 無需釋放
5. 底層實現
- 指針:直接對應內存地址,操作透明(如
mov
指令操作地址值)。 - 引用:通常由編譯器實現為“自動解引用的指針”,但在語法層面隱藏了地址操作。
總結:選擇指針還是引用?
場景 | 推薦選擇 | 原因 |
---|---|---|
可選參數(允許空值) | 指針(如nullptr 表示無數據) | 引用無法表示空值 |
函數內需重新綁定對象 | 指針 | 引用一旦綁定不可修改 |
避免拷貝大對象 | 引用 | 傳遞別名而非副本,高效 |
實現多態 | 指針或引用均可(虛函數通過引用/指針觸發多態) | |
資源管理(如動態內存) | 指針(配合RAII或智能指針) | 引用不擁有資源 |
核心原則
- 優先使用引用:在參數傳遞、返回值優化等場景中,引用更安全、簡潔。
- 必須使用指針:當需要處理動態內存、可選參數或多級間接訪問時。
什么是深拷貝和淺拷貝?
在編程中,深拷貝(Deep Copy)和淺拷貝(Shallow Copy)是兩種不同的對象拷貝方式,主要區別在于對資源所有權和內存管理的處理方式。以下是它們的核心區別及實際應用場景:
1. 淺拷貝(Shallow Copy)
-
定義:
僅復制對象的成員值(包括指針的值),而不復制指針指向的實際數據。- 拷貝后的對象與原對象的指針成員指向同一塊內存。
-
特點:
- 速度快(僅復制指針地址,不涉及內存分配和數據復制)。
- 潛在風險:多個對象共享同一資源,可能導致懸空指針、雙重釋放等問題。
-
C++中的默認行為:
class ShallowExample {int* data; public:// 默認拷貝構造函數和賦值運算符執行淺拷貝ShallowExample(const ShallowExample& other) : data(other.data) {} };
示例問題
ShallowExample obj1;
ShallowExample obj2 = obj1; // 淺拷貝
// obj1和obj2的data指向同一內存
delete obj1.data; // 釋放內存
obj2.data[0] = 10; // 危險!obj2.data已是懸空指針
2. 深拷貝(Deep Copy)
-
定義:
不僅復制對象的成員值,還會為指針成員分配新內存,并復制原指針指向的所有數據。- 拷貝后的對象與原對象完全獨立,資源互不影響。
-
特點:
- 安全性高(資源獨立,避免共享沖突)。
- 速度較慢(需要分配內存并復制數據)。
-
實現方式:
class DeepExample {int* data;size_t size; public:// 自定義深拷貝構造函數DeepExample(const DeepExample& other) {size = other.size;data = new int[size]; // 分配新內存memcpy(data, other.data, size * sizeof(int)); // 復制數據}// 深拷貝賦值運算符DeepExample& operator=(const DeepExample& other) {if (this != &other) { // 防止自賦值delete[] data; // 釋放舊資源size = other.size;data = new int[size]; // 分配新內存memcpy(data, other.data, size * sizeof(int));}return *this;}~DeepExample() { delete[] data; } };
正確使用
DeepExample obj1;
DeepExample obj2 = obj1; // 深拷貝:obj2.data是獨立內存塊
delete obj1.data; // 不影響obj2.data
obj2.data[0] = 10; // 安全
3. 關鍵區別總結
特性 | 淺拷貝 | 深拷貝 |
---|---|---|
資源所有權 | 共享資源(多個對象指向同一內存) | 獨立資源(每個對象擁有自己的內存副本) |
內存操作 | 不分配新內存,僅復制指針值 | 分配新內存并復制數據 |
性能 | 快 | 慢(取決于數據大小) |
安全性 | 低(需手動管理共享資源) | 高(資源隔離,避免沖突) |
適用場景 | 只讀共享數據、性能敏感且無資源所有權的場景 | 需獨立管理資源的場景(如動態數組、字符串等) |
4. 何時需要深拷貝?
- 對象管理資源時:
若類包含指針成員,且該指針指向動態分配的內存(如數組、文件句柄、網絡連接等),必須實現深拷貝。 - 遵循“三法則”:
在C++中,如果類需要自定義析構函數,通常也需要自定義拷貝構造函數和拷貝賦值運算符(即深拷貝邏輯)。
5. 其他語言的實現
- Java/Python:
- 默認的賦值和拷貝(如
clone()
或copy.copy()
)通常是淺拷貝。 - 深拷貝需手動實現(如Java實現
Cloneable
接口,Python使用copy.deepcopy()
)。
- 默認的賦值和拷貝(如
- JavaScript:
- 對象展開符(
{...obj}
)或Object.assign()
是淺拷貝。 - 深拷貝需遞歸復制或使用
JSON.parse(JSON.stringify(obj))
(局限性:無法處理函數、循環引用)。
- 對象展開符(
6. 總結
- 淺拷貝:適合輕量級、無需資源隔離的場景,但需謹慎管理共享資源。
- 深拷貝:確保資源獨立性,是管理動態內存或獨占資源的必要手段。
- 在C++中:深拷貝需手動實現;在高級語言中(如Python),可依賴內置方法,但仍需理解底層邏輯以避免陷阱。
C++中opencv的mat是怎么深拷貝和淺拷貝的?
說一下快排,二分查找
快速排序(Quick Sort)
算法思想:
快速排序采用分治策略,通過選擇一個基準元素將數組分成兩部分,使左邊元素均小于基準,右邊元素均大于基準,然后遞歸地對子數組排序。
步驟詳解:
-
選擇基準(Pivot):
通常可選第一個元素、最后一個元素、中間元素或隨機元素作為基準。優化方法如三數取中法(選首、中、尾的中位數)可減少最壞情況概率。 -
分區(Partition):
重新排列數組,使小于基準的元素位于左側,大于基準的位于右側。最終基準元素的位置即為分區點。- 雙指針法:
i = 左邊界 - 1 pivot = 右邊界元素 for j from 左邊界 to 右邊界-1:if arr[j] <= pivot:i += 1swap arr[i] 和 arr[j] swap arr[i+1] 和 arr[右邊界] return i+1
- 雙指針法:
-
遞歸排序:
對分區后的左右子數組重復上述步驟,直到子數組長度為1。
時間復雜度:
- 平均:(O(n \log n))
- 最壞(已排序數組+固定基準):(O(n^2))
- 優化后(隨機基準):接近平均情況
代碼示例:
#include <iostream>
#include <vector>
using namespace std;// 分區函數:將數組分為左右兩部分,返回基準元素的正確索引
int partition(vector<int>& arr, int low, int high) {int pivot = arr[high]; // 選擇最后一個元素作為基準int i = low - 1; // i標記比基準小的元素的右邊界// 遍歷數組,將小于等于基準的元素交換到左側for (int j = low; j < high; j++) {if (arr[j] <= pivot) {i++;swap(arr[i], arr[j]);}}// 將基準元素放到正確的位置(i+1)swap(arr[i + 1], arr[high]);return i + 1;
}// 快速排序遞歸函數
void quickSort(vector<int>& arr, int low, int high) {if (low < high) {int pi = partition(arr, low, high); // 獲取基準位置quickSort(arr, low, pi - 1); // 遞歸排序左半部分quickSort(arr, pi + 1, high); // 遞歸排序右半部分}
}// 測試快速排序
void testQuickSort() {vector<int> arr = {10, 7, 8, 9, 1, 5};cout << "原始數組: ";for (int num : arr) cout << num << " ";cout << endl;quickSort(arr, 0, arr.size() - 1);cout << "排序后數組: ";for (int num : arr) cout << num << " ";cout << endl;
}
二分查找(Binary Search)
算法思想:
在有序數組中,通過不斷將搜索區間對半分,快速定位目標值的位置。
步驟詳解:
-
初始化邊界:
左邊界left = 0
,右邊界right = len(arr) - 1
。 -
循環查找:
- 計算中間索引
mid = left + (right - left) // 2
(避免整數溢出)。 - 若
arr[mid] == target
,返回mid
。 - 若
arr[mid] < target
,調整左邊界left = mid + 1
。 - 否則調整右邊界
right = mid - 1
。
- 計算中間索引
-
終止條件:
當left > right
時,表示目標不存在,返回 -1。
時間復雜度:
- (O(\log n)),每次將搜索范圍縮小一半。
代碼示例:
#include <iostream>
#include <vector>
using namespace std;// 二分查找函數(返回目標索引,未找到返回-1)
int binarySearch(const vector<int>& arr, int target) {int left = 0;int right = arr.size() - 1;while (left <= right) {int mid = left + (right - left) / 2; // 避免整數溢出if (arr[mid] == target) {return mid;} else if (arr[mid] < target) {left = mid + 1; // 目標在右半部分} else {right = mid - 1; // 目標在左半部分}}return -1;
}// 測試二分查找
void testBinarySearch() {vector<int> arr = {1, 3, 5, 7, 9};int target = 5;cout << "有序數組: ";for (int num : arr) cout << num << " ";cout << endl;int index = binarySearch(arr, target);if (index != -1) {cout << "目標 " << target << " 的索引為: " << index << endl;} else {cout << "目標 " << target << " 未找到" << endl;}
}
對比總結
算法 | 應用場景 | 時間復雜度 | 核心操作 | 關鍵點 |
---|---|---|---|---|
快速排序 | 大規模數據排序 | 平均 (O(n \log n)) | 分區與遞歸 | 基準選擇、原地排序 |
二分查找 | 有序數據查詢 | (O(\log n)) | 折半比較 | 邊界處理、有序前提 |
注意事項:
- 快速排序需注意基準選擇優化以避免最壞情況。
- 二分查找必須確保輸入數組有序,且正確處理中間值計算防止溢出。
char a = -1,在計算機內存中是怎么存儲的?
在C/C++中,char a = -1;
在內存中的存儲方式如下:
1. 符號性決定存儲形式
-
char
的符號性:
char
的默認符號性由編譯器決定:- 有符號(signed char):范圍是
-128
到127
(8位補碼)。 - 無符號(unsigned char):范圍是
0
到255
(8位純二進制)。
大多數編譯器(如GCC/x86)默認
char
為 有符號,但某些平臺(如ARM)可能默認為無符號。
需通過編譯選項或顯式聲明(signed char
/unsigned char
)明確符號性。 - 有符號(signed char):范圍是
2. 存儲過程
情況1:有符號char(signed char)
- 補碼表示:
-1
的補碼形式為11111111
(二進制),即0xFF
(十六進制)。- 補碼規則:絕對值取反后加1。
1的二進制:00000001 取反:11111110 加1:11111111 → 即-1的補碼
- 補碼規則:絕對值取反后加1。
- 內存存儲:
a
的內存占用1字節(8位),直接寫入0xFF
。
情況2:無符號char(unsigned char)
- 溢出處理:
若編譯器默認char
為無符號,-1
會被隱式轉換為 模運算結果:-1 mod 256 = 255 → 二進制 11111111 (0xFF)
- 內存存儲:
同樣存入0xFF
,但程序邏輯上將其視為255
。
3. 驗證代碼
#include <stdio.h>int main() {char a = -1;unsigned char *p = (unsigned char*)&a;printf("內存值(十六進制): 0x%02X\n", *p); // 輸出 0xFFreturn 0;
}
無論 char
的默認符號性如何,輸出均為 0xFF
,證明內存中存儲的是 11111111
。
4. 總結
關鍵點 | 說明 |
---|---|
內存存儲值 | 固定為 11111111 (二進制)或 0xFF (十六進制) |
符號性影響 | 有符號char解釋為 -1 ,無符號char解釋為 255 |
編譯器差異 | 依賴默認符號性,建議顯式聲明 signed char 或 unsigned char 避免歧義 |
補碼機制 | 負數的存儲通過補碼實現,保證運算一致性 |
5. 擴展:補碼的優勢
- 統一加減法:補碼允許使用同一套電路處理加減法,無需區分符號位。
- 零的唯一性:補碼中
0
僅有00000000
一種表示,無+0
和-0
歧義。 - 范圍對稱性:8位有符號整數范圍為
-128
到127
,充分利用所有位組合。
通過上述分析,char a = -1;
的內存存儲始終為 0xFF
,其符號性由編譯器決定值的解釋方式。
C++實現sharedPtr,手寫C++的string類
對cache的理解,如何提高緩存命中率?
數據結構中順序存儲和鏈式存儲的優缺點
大疆車載面經
網上的參考:
自我介紹+介紹簡歷項目
圍繞項目問涉及的知識點
問了很多DSP的內容(項目相關)
回顧你做的項目,你覺得有哪些地方可以改進?有哪些挑戰?難忘的經歷
說說你對大疆車載的了解,反問
優化手段有哪些
模型推理加速是怎么整的
量化部署過程
gpu加速有什么好處,原理是什么
cuda相關
多線程如何使用,痛點是什么
如何做同步,同步的手段有哪些?區別是什么?分別用在什么場景
內存優化有哪些手段?dma相關的
cache cpu 內存之間的聯系
高性能面試問題
C++
-
static的作用,修飾成員變量,成員函數。static全局變量和普通變量的區別。
-
怎么只在堆上創建構造函數
-
右值引用
-
鎖
-
lambda表達式
-
move forward
-
編譯的過程 動態庫和靜態庫的區別
-
new 和 malloc的區別, new的底層實現
-
拷貝構造函數是傳值還是引用,為什么要傳引用。
-
線程安全的單例模式
-
智能指針,shared_ptr是不是線程安全的
-
vector的擴容機制?為什么要2倍擴容
-
C++內存模型
-
為什么構造函數不能是虛函數,為什么析構函數是虛函數
-
線程間共享內存,什么時候用到條件變量,什么時候用到鎖 ,有什么區別
-
死鎖條件,如何避免,lock_gard unquie_lock 區別
-
C++怎么調用c語言封裝的函數
-
多態實現,原理,虛函數表存的位置,注意別忘了模板多態,模板的偏特化
-
進程和線程的區別
-
大端小端 怎么判斷,兩種方法
高性能相關
-
opencl的運行流程
-
GPU架構
-
GPU全局內存和局部內存區別, 怎么更好利用局部內存
-
Cache原理
-
如何提高cache命中率
-
通常優化的思路
-
寫opencl為什么要減少分支,掩碼
-
opencl 寫kernel的主要參數有哪些
-
計算密集型和訪存密集型的區別
-
可分離卷積在GPU上為什么慢,為什么是訪存密集型
-
算子融合 conv+BN 融合的公式,為什么可以融合
-
推理框架中卷積的實現有哪些
-
時間局部性和空間局部性
-
產生bank confict的原因和解決方法
-
TVM
-
opencl 實現矩陣乘法, 向量求和