c++STL——stack、queue、priority_queue的模擬實現

文章目錄

  • stack、queue、priority_queue的模擬實現
    • 使用部分
    • 模擬實現
      • 容器適配器
      • deque的介紹
        • 原理
        • 真實結構
        • deque的迭代器
        • deque的操作
        • deque的優缺點
      • stack的模擬實現
      • 按需實例化
      • queue的模擬實現
      • priority_queue的模擬實現
        • 為何引入仿函數
        • 代碼實現

stack、queue、priority_queue的模擬實現

使用部分

對于stackqueue的使用其實是非常簡單的。當我們進行文檔的查閱的時候,會發現提供的接口其實并不算多,也沒有迭代器。這是因為棧只能在棧頂操作數據,隊列只能在隊尾隊頭操作數據。不能遍歷數據。

priority_queue名稱是優先級隊列,看著很陌生,實際上我們早已經接觸過,其實就是我們以前實現的數據結構——堆。

對于它們三個的使用我們就不進行過多的介紹了,因為接口的使用是非常簡單的,且以往我們已經使用和實現過。所以自行查閱文檔使用即可:
queue的使用(包括priority_queue)
stack的使用

模擬實現

本篇文章的重點將放在三者的模擬實現上。這三者的實現和以往的模擬實現是有些不同的。

容器適配器

在以往學習list等容器的時候,我們會發現模板參數里面是有內存池的聲名:
在這里插入圖片描述
allocator< T >,這是內存池的聲名。

但是我們仔細翻看stackqueue的時候發現:
在這里插入圖片描述
在這里插入圖片描述
我們發現模板參數里面竟然聲明了一個Container類型,且給有缺省值deque< T >。這里就需要說一下,模板參數也是可以給缺省值的

其實priorty_queue也是一樣的。也是使用這樣的模板聲名方式。

其實也就是說,其實stackqueue其實并不是像我們以前那樣自行開辟數組空間或者實現鏈式隊列一樣。再整出一些順序表來實現這些結構。

棧和隊列的本質就是線性表,只不過是有限制的線性表。只能在表的固定位置進行訪問和修改。而STL庫中已經實現好了vectorlist這樣比較復雜的線性表,并且是有提供相關的接口進行操作的。

那也就是說,完全可以將已經實現好的線性表再進行一次封裝,只提供固定位置的修改和訪問操作的接口。那這樣子其實就很方便了,就不用再費時費力去手撕一個棧或隊列的代碼出來。

我們看文檔圖中被方框圈起來的字樣,Container Adaptor其實就是容器適配器的意思,其實就是通過對別的容器進行二次封裝后實現的容器。

只不過說,對于默認的適配容器,這里選擇了deque這個容器。其實很好理解,因為對于棧來說,使用vector最為方便,對于隊列來說,使用list最為方便。(在c語言數據結構部分就已經說過)。但是反過來就不方便了。

基于此,STL庫中實現了一種將vectorlist的特性融合的容器,即為deque,我們可以理解為是二者的縫合怪

deque的介紹

我們先來了解一下deque是什么。

原理

deque本質也是個容器,其內部結構比較復雜:

deque(雙端隊列):是一種雙開口的"連續"空間的數據結構,雙開口的含義是:可以在頭尾兩端進行插入和刪除操作,且時間復雜度為O(1),與vector比較,頭插效率高,不需要搬移元素;與list比較,空間利用率比較高:
在這里插入圖片描述

真實結構

這樣一看,那和vector也沒什么太大的區別嘛。就是多了一端進行插入刪除操作。

其實并不是這樣子的。實際上,deque是一段由一段的連續空間(緩沖區)存儲數據,然后這每一段空間的地址會放在一個叫中控數組的地方:
在這里插入圖片描述
大致是這個樣子。中控數組存儲的是各個緩沖區的地址,各個緩沖區進行存儲數據。

這樣子是結合了vectorlist的特點進行構造的數據結構。直到結構后,我們就得明白deque時如何訪問存儲的數據的,這樣子才能明白其對應的操作是如何進行的。

deque的迭代器

實現了list后我們肯定就知道,對于deque這么復雜的結構,使用原生指針肯定是不可能實現迭代器的功能的。肯定是需要對其進行封裝,我們來看看源碼:
在這里插入圖片描述
迭代器有四個成員變量,有三個是指向緩沖區數據類型的指針,一個是指向緩沖區的指針。

具體的指向是怎么樣的呢?

在這里插入圖片描述
對于firstlast指針,分別指向的是緩沖區的開頭數據位置和末尾數據位置的下一個。我們可以認為是以原生指針形式實現的、每一小段緩沖區的迭代器。而cur指針是用來遍歷數據的,一旦達到了last的位置就需要遍歷到下一段緩沖區了。而node指針是用來找到當前緩沖區所處中控數組的位置的。

緩沖區的指針只需要存儲在這個中控器上,存儲的位置一定是從中間開始存儲一直擴散至兩側。因為需要頭插和尾插,放在中間肯定是更方便的。當中控數組位置不夠時擴容即可。對于中控數組的擴容代價是一點也不大的,只需要將地址復制到新中控器,將原中控器的指向置空再釋放即可。也就是說,使用淺拷貝就可以。

deque的操作

實現好這樣的迭代器之后,只需要通過迭代器來進行容器的一系列操作即可。這個結構我們只需做一些簡單的了解即可。不用去模擬實現。因為這個結構還是有那么一些的缺陷的。

deque容器中會存放兩個迭代器,一個是_start,指向第一個緩沖區,一個是_finish,指向最后一個緩沖區。可能還有有一些別的數據,如緩沖區個數等(即中控器中數據個數)。

尾插操作:就是在_finish這個迭代器的cur指針位置插入后,再將cur指針往后移動。如果當前cur指針和last指針是處于一個位置的時候,就需要移動至下一個緩沖區。也就是將node指針向后移動一個位置。再調整其余指針的位置后再執行上述操作。當然如果中控器位置不夠需要重新擴容。

頭插操作:過程與尾插相反,需要在_start這個迭代器的cur指針位置插入,只不過我們需要注意的是,對于區間的選擇,一般都是傾向于左閉右開,所以尾插的時候cur指針指向的是最后一個數據的下一個位置,而頭插的cur指針指向的是第一個數據。

且尾插是在緩沖區中是從前往后插入。頭插不一樣,是從后往前插入:
在這里插入圖片描述
隨機訪問操作deque是支持隨機訪問,也就是說它的迭代器是隨機類型。可以支持運算符[]的重載。想要訪問也是很簡單。比如訪問第25個數據(從第0個開始),每個緩沖區有10個數據。可以先25 / 10 = 2算出當前是第幾個緩沖區,然后再25 % 10 = 5算出位于緩沖區的位置。然后通過中控器訪問,指向中控器的map是一個二級指針,經過解引用得到內部數據,就是指向緩沖區的一級指針,再經過解引用就可以得到數據,所以此時deque dq[25] = map[25 / 10][25 % 10] 就可以訪問到了,還是很簡單的。

置于其他的使用其實很簡單,只要前面的容器有認真學習和使用過都會用。因為底層都是將接口進行封裝了,我們只需要關心怎么用的。

deque的優缺點
  1. 與vector比較,deque的優勢是:頭部插入和刪除時,不需要搬移元素,效率特別高,而且在擴容時,也不需要搬移大量的元素,因此其效率是必vector高的。
  2. 與list比較,其底層是連續空間,空間利用率比較高,不需要存儲額外字段。
  3. 但是,deque有一個致命缺陷:不適合遍歷,因為在遍歷時,deque的迭代器要頻繁的去檢測其是否移動到某段小空間的邊界,導致效率低下,而序列式場景中,可能需要經常遍歷,因此在實際中,需要線性結構時,大多數情況下優先考慮vector和list,deque的應用并不多,而目前能看到的一個應用就是,STL用其作為stack和queue的底層數據結構。

還要說的是是中間位置插入的效率,對于deque來說,除了頭部尾部之外的插入其實都很麻煩,這是顯而易見的。所以一旦涉及到大量的中間位置的插入,我們都是可以選擇使用list,因為任意位置的插入是O(1),且不挪動數據。但是由于其尾插頭插效率極高,甚至更甚vector一籌,所以對于棧和隊列這種只在一側或者兩側大量操作的容器,很明顯用deque是效率更高。

stack的模擬實現

了解完deque后,我們來實現一下stack,我們直接上代碼:

namespace MySpace {template<class T, class Container = deque<T>>class stack {public:stack() {}//一定要寫 會走初始化列表 如果是自定義類型會調用其默認構造函數stack(const initializer_list<T>& x){typename initializer_list<T>::iterator it = x.begin();while (it != x.end()) {push(*it); ++it;}} void push(const T& x) {_con.push_back(x);   }void pop() {_con.pop_back();}T& top() {return _con.back();}const T& top() const {return _con.front();}size_t size() const{return _con.size();}bool empty() const {return _con.empty();}private: Container _con;};}

當然,用initializer_list這個構造函數可以不寫,只寫那個空的即可。我寫只是為了創建對象的時候方便一些,便于大量測試。

那個空的構造函數是一定要寫的。我們來回顧一下:
因為即使是空的構造函數,成員變量也要走初始化列表的操作,對于內置類型,有傳參用傳參的,反之用缺省值,如果再沒有只能給隨機值了。而自定義類型不一樣了,會調用其默認構造函數,沒有就報錯。我們這里的stack是只有一個容器的,這個容器默認是STL 庫里面的deque,肯定是有其默認構造函數的。如果這里是我們自己整的容器那我們也要提供默認構造函數。

而后對于入棧出棧以及判空,獲取棧頂數據,獲取數據個數都是調用的容器內已有的接口。這十分方便。

按需實例化

很多人會好奇,既然是調用的容器的接口,但是對于有些容器來說,有些接口它是沒有的。就比如vector是沒有pop_front接口的。如果我們在類模板里面調用了這個接口,但是顯示實例化類對象的時候傳了vector這個容器怎么辦呢?

這個不用擔心,編譯器會報錯的。模板最大的特點就是按需實例化,如果有些接口我們不進行調用,寫在那里它并不會報錯。如果我們調用了那個接口,如果與當前容器不符,編譯器是能夠與檢查的出來的。

queue的模擬實現

queue的實現其實和stack也是很類似的,稍微修改一下就好:

namespace MySpace {template<class T, class Container = deque<T>>class queue {public:queue() {}queue(const initializer_list<T>& x) {typename initializer_list<T>::iterator it = x.begin(); while (it != x.end()) { push(*it);  ++it; } }void push(const T& x) {_con.push_back(x);}void pop() {_con.pop_front();  }T& front() {return _con.front();}const T& front() const{return _con.front();}T& back() {return _con.back();}const T& back() const {return _con.back();}size_t size() const {return _con.size();}bool empty() const {return _con.empty();}private:Container _con;};}

這里就是剛剛說到的那種情況,我們調用了pop_front接口,當然dequelist<是有這樣的接口的,所以隊列的是新是不能用vector的,這其實也符合之前的判斷。因為用vector不適合頭刪數據。

priority_queue的模擬實現

priority_queue的模擬實現其實早已經實現過,但是在這里我們得引入一些新的用法。我們先來看看文檔中是怎么定義的:
在這里插入圖片描述
我們發現,除了適配容器之外,模板參數中還多了一個叫Compare的參數,這個是什么呢?

這個其實是仿函數,其實也是一個類。

為何引入仿函數

還記得以前模擬實現堆這個數據結構的時候,我們可能需要通過手動調整向上調整算法和向下調整算法的的比較邏輯,從而達到是建大堆還是建小堆。

但這很明顯是非常不合理的。總不能每一次都要手動調整吧。但是在學習c語言的時候是沒有太多辦法解決這個問題,除了函數指針。

函數指針用來控制比較邏輯其實我們早已見過,就是使用c庫內自帶的qsort函數時,需要我們自己傳一個比較邏輯,這個比較邏輯按照庫中的要求就是排成升序。但是我們實現和庫要求相反的比較函數,就可以排升序。這些就不再多說,都是以前的知識。

但是c++中是不太喜歡使用函數指針這個東西的,因為函數指針還是比較復雜的,且代碼的可讀性一般。有沒有什么辦法能夠達到這樣的效果:
即我傳入一個類似小于號的東西,就建立大堆,傳入類似大于號的東西,就建立小堆。然后就使用這個傳入的內容去執行比較邏輯,復用這個邏輯建立大堆或者小堆呢?

答案是可以的,標準庫里面就是用仿函數實現的。至于為什么要規定小于號建大堆,大于號建小堆這是不用管的,標準庫中就是這樣玩的。

我們來看看仿函數是怎么寫的:

template<class T>
class less {
public:bool operator()(const T& x1, const T& x2) {return x1 < x2;}
};template<class T>
class greater {
public:bool operator()(const T& x1, const T& x2) {return x1 > x2;}
};

當前我們只需要簡單的了解一下仿函數怎么簡單的實現一個并且會使用即可。

仿函數其實就是一個空的類,沒有成員變量,內部有一個對()進行重載的函數,返回值是bool類型。注意這個空類的大小為1個字節,這是為了占位。這在類的大小部分說過。

這也是個類模板,其實很好理解,因為比較的內容可能不只是整形或者浮點型,各式各樣的都需要比較。STL內的容器也是可以比較的,因為內部都有重載比較邏輯的函數。

如果是我們自己實現的類比較,我們可以在內部自行實現比較邏輯。但是如果我們現在是傳入兩個指針指向兩個對象,那使用類內的比較邏輯可能就不是我們想要的。解決方法就是:要不然就是在內部重載一個專門針對指針的比較邏輯,要不然就是重載一個仿函數即可。

代碼實現

我們來看看代碼實現:

namespace MySpace {template<class T>class less {public:bool operator()(const T& x1, const T& x2) {return x1 < x2;}};template<class T>class greater {public:bool operator()(const T& x1, const T& x2) {return x1 > x2;}};template<class T, class Container = vector<T>, class Compare = less<T>> //默認less代表傳小于號 建大堆 反之相反class priority_queue {public: priority_queue() {}template <class InputIterator>priority_queue(InputIterator first, InputIterator last) {InputIterator it = first;while (it != last) {push(*it);++it;}}template <class T>priority_queue(const initializer_list<T>& x) { typename initializer_list<T>::iterator it = x.begin(); while (it != x.end()) { push(*it);++it;  } }void push(const T& x) {_con.push_back(x);Adjustup(size() - 1);}void pop() {std::swap(_con[0], _con[size() - 1]);_con.pop_back();AdjustDown(0);}bool empty() const {return _con.empty();}size_t size() const {return _con.size();}T& top() const {return _con[0];}void Adjustup(size_t child) {int Child = child, Parent = (child - 1) / 2;while (Child > 0) {if (_cmp(_con[Parent], _con[Child])) {std::swap(_con[Parent], _con[Child]);Child = Parent;Parent = (Parent - 1) / 2;}else break;}}void AdjustDown(size_t parent) {size_t Parent = parent, Child = 2 * Parent + 1; while (2 * Parent + 1 < size()) {if (Child + 1 < size() && _cmp(_con[Child], _con[Child + 1])) ++Child; if (_cmp(_con[Parent], _con[Child])) {std::swap(_con[Parent], _con[Child]);Parent = Child; Child = Child * 2 + 1;}else break;}}private:Container _con;Compare _cmp;};}

雖然以前實現過這個數據結構,但是把它們放在類里面還是需要進行調整的。比如向上向下調整算法的參數就是被修改的。

從這里我們也看得出來為什么是仿函數了,因為調用的形式就和函數一樣,傳入兩個參數進行比較,表達式的返回值就是比較的結果。和函數是一樣的方法,但是我們還能通過外界控制整個邏輯。

當然由于引入了仿函數的機制,所以向上向下調整算法的邏輯可能稍微需要修改一下,和以往我們在c語言數據結構中實現的那個版本還是有一些區別的。但是換湯不換藥,我們可以將符號代入進去驗證即可。(STL庫中實現的是傳類less建大堆,反之小堆,默認建大堆)。

總的來說,這個部分的模擬實現還是非常簡單的,只需了解一下適配器模式和仿函數的概念即可輕松手撕這樣的容器出來。

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

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

相關文章

【深度學習—李宏毅教程筆記】Transformer

目錄 一、序列到序列&#xff08;Seq2Seq&#xff09;模型 1、Seq2Seq基本原理 2、Seq2Seq模型的應用 3、Seq2Seq模型還能做什么&#xff1f; 二、Encoder 三、Decoder 1、Decoder 的輸入與輸出 2、Decoder 的結構 3、Non-autoregressive Decoder 四、Encoder 和 De…

C++鐫刻數據密碼的樹之銘文:二叉搜索樹

文章目錄 1.二叉搜索樹的概念2.二叉搜索樹的實現2.1 二叉搜索樹的結構2.2 二叉搜索樹的節點尋找2.2.1 非遞歸2.2.2 遞歸 2.3 二叉搜索樹的插入2.3.1 非遞歸2.3.2 遞歸 2.4 二叉搜索樹的刪除2.4.1 非遞歸2.4.2 遞歸 2.5 二叉搜索樹的拷貝 3.二叉樹的應用希望讀者們多多三連支持小…

系統架構設計師:流水線技術相關知識點、記憶卡片、多同類型練習題、答案與解析

流水線記憶要點? ?公式 總時間 (n k - 1)Δt 吞吐率 TP n / 總時間 → 1/Δt&#xff08;max&#xff09; 加速比 S nk / (n k - 1) | 效率 E n / (n k - 1) 關鍵概念 周期&#xff1a;最長段Δt 沖突?&#xff1a; ?數據沖突&#xff08;RAW&#xff09; → 旁路/…

強制重裝及驗證onnxruntime-gpu是否正確工作

#工作記錄 我們經常會遇到明明安裝了onnxruntime-gpu或onnxruntime后&#xff0c;無法正常使用的情況。 一、強制重新安裝 onnxruntime-gpu 及其依賴 # 強制重新安裝 onnxruntime-gpu 及其依賴 pip install --force-reinstall --no-cache-dir onnxruntime-gpu1.18.0 --extra…

桌面我的電腦圖標不見了怎么恢復 恢復方法指南

在Windows操作系統中&#xff0c;“我的電腦”或在較新版本中稱為“此電腦”的圖標&#xff0c;是訪問硬盤驅動器、外部存儲設備和系統文件的重要入口。然而&#xff0c;有些用戶可能會發現桌面上缺少了這個圖標&#xff0c;這可能是由于誤操作、系統設置更改或是不小心刪除造成…

2025.04.20【Lollipop】| Lollipop圖繪制命令簡介

Customize markers See the different options allowing to customize the marker on top of the stem. Customize stems See the different options allowing to customize the stems. 文章目錄 Customize markersCustomize stems Lollipop圖簡介R語言中的Lollipop圖使用ggp…

docker-compose搭建kafka

1、單節點docker-compose.yml version: 3 services:zookeeper:image: zookeeper:3.8container_name: zookeeperports:- "2181:2181"volumes:- ./data/zookeeper:/dataenvironment:ZOO_MY_ID: 1ZOO_MAX_CLIENT_CNXNS: 100kafka:image: bitnami/kafka:3.7container_na…

【問題】一招解決vscode輸出和終端不一致的困擾

背景&#xff08;閑話Trae&#xff09; Trae是挺好&#xff0c;用了幾天&#xff0c;發現它時不時檢查文件&#xff0c;一檢測就轉悠半天&#xff0c;為此我把當前環境清空&#xff0c;就留一個正在調的程序&#xff0c;結果還照樣檢測&#xff0c;雖然沒影響什么&#xff0c;…

Git,本地上傳項目到github

一、Git的安裝和下載 https://git-scm.com/ 進入官網&#xff0c;選擇合適的版本下載 二、Github倉庫創建 點擊右上角New新建一個即可 三、本地項目上傳 1、進入 要上傳的項目目錄&#xff0c;右鍵&#xff0c;選擇Git Bash Here&#xff0c;進入終端Git 2、初始化臨時倉庫…

從零開始配置spark-local模式

1. 環境準備 操作系統&#xff1a;推薦使用 Linux 或 macOS&#xff0c;Windows 也可以&#xff0c;但可能會有一些額外的配置問題。 Java 環境&#xff1a;Spark 需要 Java 環境。確保安裝了 JDK 1.8 或更高版本。 檢查 Java 版本&#xff1a; bash 復制 java -version 如果…

前端~地圖(openlayers)繪制車輛運動軌跡(仿高德)

繪制軌跡路線軌跡路線描邊增加起點終點圖標繪制仿高德方向箭頭模仿車輛動態運動動畫 車輛運行軌跡 車輛軌跡經緯度坐標 const linePoints [new Point([123.676031, 43.653421]),new Point([123.824347, 43.697124]),new Point([124.197882, 43.946811]),new Point([124.104498…

分布式之CAP原則:理解分布式系統的核心設計哲學

聲明&#xff1a;CAP中的P原則都是需要帶著的 在分布式系統的設計與實踐中&#xff0c;CAP原則&#xff08;又稱CAP定理&#xff09;是開發者必須掌握的核心理論之一。它揭示了分布式系統在一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#x…

IF=40.8|腫瘤免疫:從免疫基因組學到單細胞分析和人工智能

一、寫在前面 今天分享的是發表在《Signal Transduction and Targeted Therapy》上題目為"Technological advances in cancer immunity: from immunogenomics to single-cell analysis and artificial intelligence"的文章。 IF&#xff1a;40.8 DOI:10.1038/s41392…

深入理解 Spring @Bean 注解

在 Spring 框架中,@Bean 注解是用于顯式地聲明一個或多個 Bean 實例,并將其注冊到 Spring 容器中的重要工具。與 @Component 系列注解不同的是,@Bean 是方法級別的注解,通常與 @Configuration 注解結合使用。本文將詳細介紹 @Bean 注解的功能、用法及其應用場景。 1. @Bean…

Pycharm 如何刪除某個 Python Interpreter

在PyCharm中&#xff0c;點擊右下角的“Interpreter Settings”按鈕&#xff0c;或者通過菜單欄選擇“File” > “Settings”&#xff08;macOS用戶選擇“PyCharm” > “Preferences”&#xff09;。在設置窗口中&#xff0c;導航到“Project: [Your Project Name]” >…

如何改電腦網絡ip地址完整教程

更改電腦的網絡IP地址以滿足特定的網絡需求&#xff0c;本文將為您提供一份詳細的步驟指南。其實&#xff0c;改變IP地址并不是一件復雜的事&#xff0c;能解決因為IP限制帶來的麻煩。以下是操作指南&#xff1a; 方法一&#xff1a;Windows 系統&#xff0c;通過圖形界面修改 …

Oracle--SQL性能優化與提升策略

前言&#xff1a;本博客僅作記錄學習使用&#xff0c;部分圖片出自網絡&#xff0c;如有侵犯您的權益&#xff0c;請聯系刪除 一、導致性能問題的內在原因 系統性能問題的底層原因主要有三個方面&#xff1a; CPU占用率過高導致資源爭用和等待內存使用率過高導致內存不足并需…

【go】什么是Go語言中的GC,作用是什么?調優,sync.Pool優化,逃逸分析演示

Go 語言中的 GC 簡介與調優建議 Go語言GC工作原理 對于 Go 而言&#xff0c;Go 的 GC 目前使用的是無分代&#xff08;對象沒有代際之分&#xff09;、不整理&#xff08;回收過程中不對對象進行移動與整理&#xff09;、并發&#xff08;與用戶代碼并發執行&#xff09;的三…

【unity實戰】Animator啟用root motion根運動動畫,實現完美的動畫動作匹配

文章目錄 前言1、動畫分類2、如何使用根位移動畫&#xff1f; 一、根位移動畫的具體使用1、導入人形模型2、導入動畫3、配置動畫參數4、配置角色Animator動畫狀態機5、使用代碼控制人物前進后退 二、問題分析三、Humanoid動畫中的Root Motion機制及相關配置1、Humanoid動畫中的…

中間件--ClickHouse-10--海量數據存儲如何抉擇ClickHouse和ES?

在Mysql數據存儲或性能瓶頸時&#xff0c;采用冷熱數據分離的方式通常是一種選擇。ClickHouse和Elasticsearch&#xff08;ES&#xff09;是兩個常用的組件&#xff0c;但具體使用哪種組件取決于冷數據的存儲目的、查詢模式和業務需求等方面。 1、核心對比 &#xff08;1&…