高性能計算面經

高性能計算面經

  • 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紋理互操作),減少數據傳輸開銷。

總結表

特性OpenGLOpenCL
目標圖形渲染通用并行計算
硬件主要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. 訪問方式

特性BufferImage
讀寫接口直接通過指針(__global float*必須通過采樣器(Sampler)和坐標(如read_imagef
坐標訪問線性索引(buffer[offset]多維坐標(如(x, y, z)
自動濾波不支持支持(如雙線性插值、邊界處理)
數據類型轉換無自動轉換自動轉換為規范化值(如[0,1]

3. 性能特點

特性BufferImage
緩存效率依賴訪問模式(適合隨機訪問)利用空間局部性(適合相鄰像素訪問)
硬件加速無特殊優化可能使用紋理硬件單元(高速緩存、濾波)
內存帶寬普通全局內存帶寬可能更高(紋理內存的合并訪問優化)

4. 典型應用場景

場景BufferImage
適合任務通用數據(數組、結構體等)圖像/紋理處理(濾波、插值等)
示例矩陣運算、物理模擬、數值計算圖像卷積、光線追蹤采樣、體積渲染

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. 關鍵區別總結

維度BufferImage
數據布局一維線性多維結構化
硬件支持普通內存紋理內存+硬件濾波
適用場景通用計算圖像/空間數據
訪問靈活性高(直接指針)低(需采樣器+坐標)
性能優化依賴算法空間局部性+緩存優化

何時選擇?

  • 用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)(零點):將浮點零點對齊到整數的偏移量。
具體實現方法
  1. 確定浮點范圍

    • 非對稱量化:統計張量中的最小值( 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?]
  2. 計算縮放因子((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)
  3. 計算零點((Z))(僅非對稱量化需要):
    Z = round ( 0 ? x min S ) Z = \text{round}\left(0 - \frac{x_{\text{min}}}{S}\right) Z=round(0?Sxmin??)

    • 用于對齊浮點數0與整數零點(例如,處理負數)。
  4. 量化與反量化

    • 量化:將浮點數轉換為整數:
      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/callocfree操作堆內存。
  • C++:引入newdelete運算符,支持構造/析構函數自動管理資源(如RAII技術)。

4. 標準庫

  • C:標準庫較小,提供基礎功能(如stdio.h, stdlib.h, 字符串處理等)。
  • C++:包含C的大部分庫,并擴展了面向對象和泛型的庫:
    • STL(標準模板庫):容器(vector, map)、算法(sort, find)、迭代器。
    • 輸入輸出流cout/cin代替printf/scanf

5. 兼容性

  • C++兼容C:大多數C代碼可在C++中編譯(需注意少數語法差異,如強制類型轉換)。
  • C不兼容C++:C無法直接使用C++的類、引用等特性。

6. 應用場景

  • C:適合底層開發(操作系統、嵌入式系統、驅動程序),追求極致性能和硬件控制。
  • C++:適合大型軟件、游戲引擎、GUI應用、高頻交易等,兼顧性能與抽象需求。

7. 語法細節差異

特性CC++
空指針NULLnullptr(更安全)
默認返回值函數不寫返回值默認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++的名稱修飾。
示例
  1. C代碼(example.c

    #include <stdio.h>void c_function() {printf("This is a C function.\n");
    }
    
  2. C頭文件(example.h:聲明時添加 extern "C"(僅在C++中生效):

    #ifdef __cplusplus
    extern "C" {  // 告訴C++編譯器:以下函數按C的規則編譯和鏈接
    #endifvoid c_function();#ifdef __cplusplus
    }
    #endif
    
  3. C++代碼(main.cpp

    #include "example.h"int main() {c_function();  // 正確調用C函數return 0;
    }
    
  4. 編譯命令

    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中聲明。
示例
  1. C++代碼(cpp_code.cpp

    #include <iostream>// 導出C兼容的函數(禁止名稱修飾)
    extern "C" void cpp_function() {std::cout << "This is a C++ function called from C.\n";
    }
    
  2. C頭文件(cpp_header.h

    #ifdef __cplusplus
    extern "C" {  // C++需要extern "C",C編譯器會忽略這段代碼
    #endifvoid cpp_function();  // 在C中聲明為普通函數#ifdef __cplusplus
    }
    #endif
    
  3. C代碼(main.c

    #include "cpp_header.h"int main() {cpp_function();  // 調用C++函數return 0;
    }
    
  4. 編譯命令

    g++ -c cpp_code.cpp -o cpp_code.o       # 編譯C++代碼
    gcc main.c cpp_code.o -o main -lstdc++  # 編譯C并鏈接C++的目標文件(需C++標準庫)
    

注意事項

  1. 類型兼容性

    • 被調用的函數參數和返回值必須是C兼容的類型(如基本類型、指針、struct等)。
    • 避免傳遞C++特有類型(如類、模板、引用等)。
  2. 全局函數限制

    • C無法直接調用C++的類成員函數或操作對象,需通過封裝全局函數間接實現:
      extern "C" void wrapper_for_cpp_class_method() {MyClass obj;obj.method();
      }
      
  3. 名稱修飾問題

    • C++函數若未用 extern "C",其符號名會被編譯器修飾(如 _Z12func_namev),導致C代碼無法找到該函數。
  4. 編譯器和鏈接器

    • 確保C和C++代碼分別用對應編譯器編譯(如 gccg++)。
    • 鏈接時可能需要指定C++標準庫(如 -lstdc++)。

總結

場景關鍵方法示例
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. 對基本類型數組的影響

即使數組元素是基本類型(如intfloat),仍需使用 delete[]

int* arr = new int[10];
delete arr;  // 錯誤!應為 delete[] arr;
  • 雖然基本類型沒有析構函數,但 new[] 可能仍會記錄數組長度信息。
    使用 delete 會導致內存管理器錯誤解析分配塊,可能引發堆損壞。

4. 總結與最佳實踐

操作正確用法錯誤用法后果
分配數組new T[n]--
釋放數組delete[] ptrdelete ptr未定義行為(內存泄漏、崩潰等)
分配單個對象new T--
釋放單個對象delete ptrdelete[] ptr未定義行為(堆損壞)
建議
  • 避免手動管理數組:優先使用 std::vectorstd::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. 動態綁定過程

  • 調用步驟
    1. 通過對象指針/引用找到vptr
    2. 通過vptr定位到虛函數表。
    3. 根據函數在表中的偏移量獲取實際函數地址。
    4. 執行該地址對應的函數。
  • 示例
    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++多態通過虛函數表虛函數指針實現動態綁定:

  1. 編譯時:生成虛函數表,記錄虛函數地址。
  2. 運行時:通過對象的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 {}  // 禁止派生類重寫
    };
    

三、總結

關鍵點
  1. 虛析構函數

    • 基類的析構函數必須為虛函數,確保多態刪除對象時資源正確釋放。
    • 若類可能被繼承,且需通過基類指針操作對象,析構函數應聲明為虛函數。
  2. 虛函數觸發條件

    • 通過基類指針或引用調用虛函數時觸發動態綁定。
    • 構造函數和析構函數中調用虛函數可能導致非預期行為。
  3. 設計建議

    • 若類含虛函數,析構函數應始終為虛函數(除非明確禁止繼承)。
    • 使用 overridefinal 增強代碼可讀性和安全性。
示例驗證
#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)

算法思想
快速排序采用分治策略,通過選擇一個基準元素將數組分成兩部分,使左邊元素均小于基準,右邊元素均大于基準,然后遞歸地對子數組排序。

步驟詳解

  1. 選擇基準(Pivot)
    通常可選第一個元素、最后一個元素、中間元素或隨機元素作為基準。優化方法如三數取中法(選首、中、尾的中位數)可減少最壞情況概率。

  2. 分區(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
      
  3. 遞歸排序
    對分區后的左右子數組重復上述步驟,直到子數組長度為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)

算法思想
在有序數組中,通過不斷將搜索區間對半分,快速定位目標值的位置。

步驟詳解

  1. 初始化邊界
    左邊界 left = 0,右邊界 right = len(arr) - 1

  2. 循環查找

    • 計算中間索引 mid = left + (right - left) // 2(避免整數溢出)。
    • arr[mid] == target,返回 mid
    • arr[mid] < target,調整左邊界 left = mid + 1
    • 否則調整右邊界 right = mid - 1
  3. 終止條件
    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):范圍是 -128127(8位補碼)。
    • 無符號(unsigned char):范圍是 0255(8位純二進制)。

    大多數編譯器(如GCC/x86)默認 char有符號,但某些平臺(如ARM)可能默認為無符號。
    需通過編譯選項或顯式聲明(signed char/unsigned char)明確符號性。


2. 存儲過程

情況1:有符號char(signed char)
  • 補碼表示
    -1 的補碼形式為 11111111(二進制),即 0xFF(十六進制)
    • 補碼規則:絕對值取反后加1。
      1的二進制:00000001
      取反:11111110
      加1:11111111 → 即-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 charunsigned char 避免歧義
補碼機制負數的存儲通過補碼實現,保證運算一致性

5. 擴展:補碼的優勢

  • 統一加減法:補碼允許使用同一套電路處理加減法,無需區分符號位。
  • 零的唯一性:補碼中 0 僅有 00000000 一種表示,無 +0-0 歧義。
  • 范圍對稱性:8位有符號整數范圍為 -128127,充分利用所有位組合。

通過上述分析,char a = -1; 的內存存儲始終為 0xFF,其符號性由編譯器決定值的解釋方式。

C++實現sharedPtr,手寫C++的string類

對cache的理解,如何提高緩存命中率?

數據結構中順序存儲和鏈式存儲的優缺點

大疆車載面經

網上的參考:

自我介紹+介紹簡歷項目

圍繞項目問涉及的知識點

問了很多DSP的內容(項目相關)

回顧你做的項目,你覺得有哪些地方可以改進?有哪些挑戰?難忘的經歷

說說你對大疆車載的了解,反問

優化手段有哪些

模型推理加速是怎么整的

量化部署過程

gpu加速有什么好處,原理是什么

cuda相關

多線程如何使用,痛點是什么

如何做同步,同步的手段有哪些?區別是什么?分別用在什么場景

內存優化有哪些手段?dma相關的

cache cpu 內存之間的聯系

高性能面試問題

C++

  1. static的作用,修飾成員變量,成員函數。static全局變量和普通變量的區別。

  2. 怎么只在堆上創建構造函數

  3. 右值引用

  4. lambda表達式

  5. move forward

  6. 編譯的過程 動態庫和靜態庫的區別

  7. new 和 malloc的區別, new的底層實現

  8. 拷貝構造函數是傳值還是引用,為什么要傳引用。

  9. 線程安全的單例模式

  10. 智能指針,shared_ptr是不是線程安全的

  11. vector的擴容機制?為什么要2倍擴容

  12. C++內存模型

  13. 為什么構造函數不能是虛函數,為什么析構函數是虛函數

  14. 線程間共享內存,什么時候用到條件變量,什么時候用到鎖 ,有什么區別

  15. 死鎖條件,如何避免,lock_gard unquie_lock 區別

  16. C++怎么調用c語言封裝的函數

  17. 多態實現,原理,虛函數表存的位置,注意別忘了模板多態,模板的偏特化

  18. 進程和線程的區別

  19. 大端小端 怎么判斷,兩種方法

高性能相關

  1. opencl的運行流程

  2. GPU架構

  3. GPU全局內存和局部內存區別, 怎么更好利用局部內存

  4. Cache原理

  5. 如何提高cache命中率

  6. 通常優化的思路

  7. 寫opencl為什么要減少分支,掩碼

  8. opencl 寫kernel的主要參數有哪些

  9. 計算密集型和訪存密集型的區別

  10. 可分離卷積在GPU上為什么慢,為什么是訪存密集型

  11. 算子融合 conv+BN 融合的公式,為什么可以融合

  12. 推理框架中卷積的實現有哪些

  13. 時間局部性和空間局部性

  14. 產生bank confict的原因和解決方法

  15. TVM

  16. opencl 實現矩陣乘法, 向量求和

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/899773.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/899773.shtml
英文地址,請注明出處:http://en.pswp.cn/news/899773.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

青少年編程與數學 02-014 高中數學知識點 07課題、專業相關性分析

青少年編程與數學 02-014 高中數學知識點 07課題、專業相關性分析 一、函數與微積分1. 函數與初等函數2. 導數與優化 二、概率與統計1. 概率基礎2. 統計推斷3. 隨機變量與分布 三、幾何與代數1. 向量與矩陣運算2. 復數與坐標變換 四、數學建模與算法思維1. 數學建模2. 算法邏輯…

11亂碼問題的解釋(2)

這個字符串使用哪種方式編碼的?---看包含在哪個文件中 和當前 mylabel.cpp 文件的編碼方式是一致的~~ 如果這里顯示的是 UTF-8&#xff0c;說明這個文件就是UTF-8 編碼 如果顯示的是 ANSI,說明這個文件就是 GBK 編碼~ Qt Creator 內置的終端是 utf8 的方式來顯示字符串嗎?? …

我的機器學習學習之路

學習python的初衷 ? hi&#xff0c;今天給朋友們分享一下我是怎么從0基礎開始學習機器學習的。 ? 我是2023年9月開始下定決心要學python的&#xff0c;目的有兩個&#xff0c;一是為了提升自己的技能和價值&#xff0c;二是將所學的知識應用到工作中去&#xff0c;提升工作…

27--當路由器學會“防狼術“:華為設備管理面安全深度解剖(完整戰備版)

當路由器學會"防狼術"&#xff1a;華為設備管理面安全深度解剖&#xff08;完整戰備版&#xff09; 引言&#xff1a;網絡世界的"門神"進化論 “從前有個路由器&#xff0c;它把所有數據包都當好人&#xff0c;直到有一天…” ——《悲慘世界網絡版》 如果…

Docker容器網絡相關設置

確認容器是否正確啟動 首先&#xff0c;確保 MySQL 容器正在運行。可以使用 docker ps 查看當前正在運行的容器。如果 MySQL 容器沒有啟動&#xff0c;可以嘗試以下命令啟動它&#xff1a; docker run -d --name mysql-container -e MYSQL_ROOT_PASSWORDrootpassword mysql:8 這…

hive相關面試題以及答案

什么是Hive&#xff1f;它的作用是什么&#xff1f; 答&#xff1a;Hive是一個建立在Hadoop之上的數據倉庫工具&#xff0c;它提供了類似于SQL的查詢語言HiveQL來操作存儲在Hadoop中的數據。Hive的主要作用是讓用戶能夠使用SQL語法來查詢和分析大規模數據集。 Hive的架構是什么…

前端學習記錄之HTML

1. 網頁 1.1 什么是網頁 網站是指在因特網上根據一定的規則&#xff0c;使用HTML等制作的用于展示特定內容相關的網頁集合。 網頁是網站中的一“頁”&#xff0c;通常是HTML格式的文件&#xff0c;它要通過瀏覽器來閱讀 網頁是構成網站的基本元素。它通常由圖片&#xff0c;…

【1-1】ICT=IT+CT

前言 從這篇文章開始&#xff0c;我將總結軟考網工相關的筆記和自己的所思所想。我所總結內容均來自互聯網&#xff0c;歡迎大家交流、學習、討論。 1. ICT ICT IT CT 這里&#xff0c;這三個縮寫的對應英文如下&#xff1a; 縮寫英文含義ICTInformation and Communicat…

多賬號安全登錄與瀏覽器指紋管理的實現方案

隨著跨境電商、社交媒體運營等場景的普及&#xff0c;用戶對多賬號管理與反檢測技術的需求日益增長。指紋瀏覽器作為一款專注于多賬號安全登錄與瀏覽器指紋管理的工具&#xff0c;通過虛擬瀏覽器環境隔離、動態指紋模擬等技術&#xff0c;解決了賬號關聯封禁的痛點。本文將從技…

CMake Presets教程

在使用 CMake 作為構建工具的時候, 對于一個稍微大一點的項目, 存在有很多的選項. 比如 Debug 版本還是 Release 版本, 是否開啟特定選項, 是否開啟測試等等. 這些通常是作為命令行參數傳遞進去的. 但是很多程序員并不在命令行中作開發, 更多的是使用 IDE 來進行開發. 不同的 I…

vue搭建一個樹形菜單項目

首先搭建項目需要先通過步驟搭建一個vue的項目&#xff0c;然后創建一個component文件&#xff0c;里面新建一個index.vue頁面來。 這是引入的element-ui組件庫里的組件&#xff0c;來實現我的路由&#xff0c;渲染的是我存儲的動態路由&#xff0c;所以需要先安裝并且引用。 …

【Python 算法】動態規劃

本博客筆記內容來源于靈神&#xff0c;視頻鏈接如下&#xff1a;https://www.bilibili.com/video/BV16Y411v7Y6?vd_source7414087e971fef9431117e44d8ba61a7&spm_id_from333.788.player.switch 01背包 計算了f[i1]&#xff0c;f[i]就沒用了&#xff0c;相當于每時每刻只有…

c#的反射和特性

在 C# 中&#xff0c;反射&#xff08;Reflection&#xff09;和特性&#xff08;Attributes&#xff09;是兩個強大的功能&#xff0c;它們在運行時提供元編程能力&#xff0c;廣泛用于框架開發、對象映射和動態行為擴展。以下是對它們的詳細介紹&#xff0c;包括定義、用法、…

云終端的作用,此刻在校園和醫院里具象化

數字化轉型已經成為各行各業交流的熱點話題&#xff0c;校園和醫院這兩個重要領域正經歷著深刻變革。云終端&#xff0c;正以實際應用成果展現其獨特作用&#xff0c;讓人們切實感受到它帶來的高效與便利。 傳統的教學中&#xff0c;學校機房的電腦設備更新換代成本高&#xf…

UniApp快速表單組件

環境&#xff1a;vue3 uni-app 依賴庫&#xff1a;uview-plus、dayjs 通過配置項快速構建 form 表單 使用 <script setup>import CustomCard from /components/custom-card.vue;import { ref } from vue;import CustomFormItem from /components/form/custom-form-it…

Android: Handler 的用法詳解

Android 中 Handler 的用法詳解 Handler 是 Android 中用于線程間通信的重要機制&#xff0c;主要用于在不同線程之間發送和處理消息。以下是 Handler 的全面用法指南&#xff1a; 一、Handler 的基本原理 Handler 基于消息隊列(MessageQueue)和循環器(Looper)工作&#xff…

UE5學習筆記 FPS游戲制作33 游戲保存

文章目錄 核心思想創建數據對象創建UIUI參數和方法打開UI存檔文件的位置可以保存的數據類型 核心思想 UE自己有保存游戲的功能&#xff0c;核心節點&#xff0c;類似于json操作&#xff0c;需要一個數據類的對象來進行保存和讀取 創建存檔 加載存檔 保存存檔 創建數據對象…

【藍橋杯】 枚舉和模擬練習題

系列文章目錄 藍橋杯例題 枚舉和模擬 文章目錄 系列文章目錄前言一、好數&#xff1a; 題目參考&#xff1a;核心思想&#xff1a;代碼實現&#xff1a; 二、藝術與籃球&#xff1a; 題目參考&#xff1a;核心思想&#xff1a;代碼實現: 總結 前言 今天距離藍橋杯還有13天&…

大數據技術之Scala:特性、應用與生態系統

摘要 Scala 作為一門融合面向對象編程與函數式編程范式的編程語言&#xff0c;在大數據領域展現出獨特優勢。本文深入探討 Scala 的核心特性&#xff0c;如函數式編程特性、類型系統以及與 Java 的兼容性等。同時&#xff0c;闡述其在大數據處理框架&#xff08;如 Apache Spa…

Linux信號——信號的產生(1)

注&#xff1a;信號vs信號量&#xff1a;兩者沒有任何關系&#xff01; 信號是什么&#xff1f; Linux系統提供的&#xff0c;讓用戶&#xff08;進程&#xff09;給其他進程發送異步信息的一種方式。 進程看待信號的方式&#xff1a; 1.信號在沒有發生的時候&#xff0c;進…