Vector底層實現

来源:https://www.cnblogs.com/LonelyMoNan/archive/2022/09/01/16647533.html
-Advertisement-
Play Games

Vector底層實現 vector的三個私有成員 :_start 記錄初始位置 , _finish 記錄有效字元 , _endofstoage 記錄容量大小 vector會存儲的類型不同,所以要用模版來定類型 typedef T* iterator; iterator _start; iterato ...


Vector底層實現

vector的三個私有成員

:_start   記錄初始位置
, _finish 記錄有效字元
, _endofstoage  記錄容量大小

vector會存儲的類型不同,所以要用模版來定類型 

typedef T* iterator;

iterator _start;
iterator _finish;
iterator _endofstoage;

也就是T*

 

構造函數的方法很多 可以用迭代器的範圍來構造

//用迭代器構造的構造函數

傳過來的是它的迭代器的類型 我們也用它的類型來接收 不比加* &

 三個屬性先初始化

只要根據傳過來的範圍來push_back()即可

push_back函數後面會實現

        //用迭代器構造的構造函數
        template <class InputIterator>
        vector(InputIterator first, InputIterator last)
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            while (first != last)
            {
                push_back(*first);
                ++first;
            }
        }

 

構造是 也可以根據n分val來構造 所以這個功能也需要提供

傳過來的是n個 用size_t接收 因為n必須是>=0的  而val是根據類型 所以用模版類型接受  

有些情況下 val會不傳參  那麼我們就會提供他的預設構造  (註意 在C++中,內置類型也是有預設構造的)

三個屬性初始話

先用reserve函數創建n個空間

在分別push_back()添加val

        //構造n個val的構造函數
        vector(size_t n, const T& val = T())
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            reserve(n);
            for (size_t i = 0; i < n; ++i)
            {
                push_back(val);
            }
        }

 

因為某些情況 第一個參數是int  第二個參數也是int 會調用到迭代器的函數 因為這兩個類型更加適配,所以會出問題,所以需要再提供一個第一個參數為int的相同函數,來避免這種情況

    //構造n個val的構造函數
        //因為用int會調用到其他函數 所以為了區分 單獨寫出一個第一個為int
        vector(int n, const T& val = T())
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            reserve(n);
            for (int i = 0; i < n; ++i)
            {
                push_back(val);
            }
        }

 

swap函數

這個函數是用來給拷貝構造使用

交換類的三個屬性成員

        //交換
        void swap(vector<T>& v)
        {
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_endofstoage, v._endofstoage);
        }

 

拷貝構造

拷貝的本質是把一個有數據的拷貝給一個無數據的,只需要用這個無數據的迭代器去調用迭代器構造,給一個臨時tmp,最後再用這個無數據的與臨時tmp交換  即可

因為傳過來的是另一個vector 所以必須用vector<T> 接收  C++傳參是特別需要註意的,真的很容易亂

        //拷貝構造
        vector(const vector<T>& v)
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            vector<T> tmp(v.begin(), v.end());//復用用迭代器構造的構造函數
            swap(tmp);
        }

 

賦值重載

v1=v2  是兩個vector的數據賦值 所以它的返回值必須是vector<t>  而傳參數時 也必須是vector<t> 

函數本質也是交換,所以直接調用swap  這裡之所以能直接調用 而不影響到v2 是因為函數是用傳值傳參,它是不會影響到v2本體的,(現代寫法)

返回時是返回本體 (*this)

    vector<T>& operator=(vector<T> v)//賦值重載   不用引用  現代寫法
        {
            swap(v);//現代寫法 
            return *this;
        }

 

析構函數

判斷是否為空 當不為空時才需要析構  如果為空 去析構 會崩潰

把數據釋放,並且它三個屬性置空

    // 資源管理
        ~vector()
        {
            if (_start)
            {
                delete[] _start;
                _start = _finish = _endofstoage = nullptr;
            }
        }

 

迭代器部分

vector的迭代器本質就是指針  根據傳進來的類型  iterator就是這個類型的指針

並且迭代器分為const版本和非const版本 

所以需要提供兩個版本

        //迭代器
        typedef T* iterator;
        typedef const T* const_iterator;

 

註意 end返回的是你的實際有效字元 而不是你的的空間多大

        iterator begin()
        {
            return _start;
        }

        iterator end()
        {
            return _finish;
        }

        const_iterator begin() const
        {
            return _start;
        }

        const_iterator end() const
        {
            return _finish;
        }

 

size 

你的有效字元 _finish  而_finish實際上是有效字元的下一個位置

所以需要減去初始的位置 得到它真正的有效字元

        size_t size() const
        {
            return _finish - _start;
        }

 

capacity

計算的是vector的空間大小 也就是你的記錄空間大小減去初始位置

        size_t capacity() const
        {
            return _endofstoage - _start;
        }

 

【】重載

vector是支持隨意訪問的,所以【】的重載必不可少

返回的是vector里存儲的類型  實際上就是模版類型  因為傳出去也代表它需要改變 所以需要傳引用

        T& operator[](size_t n)
        {
            assert(n < size());
            //return *(_start + n);
            return _start[n]; //這個也可以
        }

 

【】重載 有些地方是需要提供const版本 不允許更改的版本

        const T& operator[](size_t n) const
        {
            assert(n < size());
            //return *(_start + n);
            return _start[n]; //這個也可以
        }

 

reserve

擴容空間  但不初始化

這裡需要註意擴容後的三個屬性更新出現的問題  正常運行會崩潰。  問題的關鍵:開闢一個新空間時,他們三個的類型是指針!而不是下標,一個地址更新,去用以前的指針,去更改這個新的地址,是會崩潰的,而下標是固定位置,地址在怎麼更新,下標的位置就是固定的。

 

我們需要記錄原先的有效字元個數

正常比較和擴容,只是這裡用memcpy函數時,出現嵌套情況時,會出現淺拷貝情況

需要用賦值 深拷貝

 

另外兩個要更新時,要用預先存儲好的數據來更新 詳情看註釋!

        void reserve(size_t n)
        {
            size_t sz = size();
            if (n > capacity())
            {
                T* tmp = new T[n];
                if (_start)  //如果_start為空時 就不需要拷貝數據了
                {
                    //memcpy(tmp, _start, size() * sizeof(T));//把_start的數據拷貝到tmp  //這樣做 在嵌套時,會發生淺拷貝
                    for (size_t i = 0; i < size(); i++)//正確做法是直接賦值  在vector<vector> 這種嵌套時 是深拷貝
                    {
                        tmp[i] = _start[i];
                    }
                    delete[] _start;
                }
                _start = tmp;//更新過後 _start就不再是nullptr
            }
            //_finish = _start + size();//×  結果為空 解引用無地址 賦值會崩潰 ==_start+(_finish-_start)  這裡的_finsih最後是等於空  
            //而跳出函數後 _finish解引用再賦值會崩潰 因為空地址無法賦值

            _finish = _start + sz;//√ 結果為_start有地址。解引用能賦值   ==_start+0 ==  這裡先讓原先的_finish-_start==0  再+上0  因為_start已經更新過了 所以需要在開頭記錄_size的大小 
            //而更新過的_start不是空 這時候在調用size _finish-_start就不是0了 而是其他值了
            _endofstoage = _start + n;
        }

 

resize

擴容,但會初始化數據

要擴容的大小 大於實際 空間大小(不是實際字元大小!) 時,我們先開需要大小空間即可

如果n大於實際字元大小時

我們需要在實際字元的位置後開始不斷添加val(要初始化的值)

如果n小於實際字元大小

那麼就把實際字元大小改為  n個  即 初始值+n

        void resize(size_t n, T val = T())
        {
            if (n > capacity())
            {
                reserve(n);
            }

            if (n > size())
            {
                while (_finish < _start + n)
                {
                    *_finish = val;
                    ++_finish;
                }
            }
            else
            {
                _finish = _start + n;
            }
        }

 

push_back()

添加字元

只要需要添加字元的函數,基本是需要檢查空間是否足夠的

一種情況為 實際空間為0 還未擴容時 預設給它開4個空間,如果有空間,但滿時,擴二倍即可(為什麼擴容二倍?沒什麼!因為合適 或者1.5倍,擴容其他倍數要麼太小,要麼太大,1.5和2的倍數是適應性最好的

擴容好後,或者空間足夠時

直接在有效字元的位置添加字元 (為什麼不先++?因為前面說過,實際字元的指針實際上是指向實際字元的後一位,所以你要添加,直接在實際字元指針添加即可)

最後添加好後,實際字元++ (這也是返回真實字元時,不直接返回_finish,而是返回_finish-_start

 

因為嵌套的作用,此函數在insert函數實現後,可以直接復用insert就可以了

        void push_back(const T& x)
        {
            /*if (_finish == _endofstoage)
            {
                size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
                reserve(newCapacity);
            }

            *_finish = x;
            ++_finish;*/

            insert(end(), x);
        }

 

pop_back()

此函數只需要把實際字元--即可

先判斷合法性,實際字元必須大於初始值

 

因為嵌套的作用,此函數在erase函數實現後,可以直接復用erase就可以了

        void pop_back()
        {
            /*        if (_finish > _start)
                    {
                        --_finish;
                    }*/
            erase(end() - 1);
        }

 

insert

註意此函數會有迭代器失效問題!

通常此函數是不需要返回的,但因為迭代器失效問題,所以必須有返回值,因為返回的就是此指向此函數的指針 也就是T* 模版類型指針  所以用重命名的iterator即可  

pos是指向要插入的位置,這個函數都是用指針來指針,所以沒辦法使用下標,這也是它的失效的問題之一!

首先判斷是否需要擴容

上面的reserve提到過,擴容後,_start的地址是會變的,而pos也是迭代器,它也是指向這個地址的指針,它不會跟著更新地址而變化。

所以這個pos它指向的位置,是原來_start的位置,而這個位置已經被釋放了,所以再去使用這個pos時,是會崩潰的! 

所以為了防止這種情況,我們需要先記錄這個pos的位置,等待_start的地址更新好後,我們要根據原先這個存儲好pos的位置,在去用_start的地址,去更新新的pos位置,並且pos的位置不變。

 

然後開始移動pos後的數據 給pos位置開出空間,能讓val插入

最後在pos的位置插入val

再++實際空間

最後需要放回插入後的位置

那麼我們在使用是,需要用迭代器去接收,才能防止迭代器的失效,因為原先的迭代器已經失效,需要根據這個返回值,來更新迭代器。

    while (it != v1.end())
    {
        if (*it % 2 == 0)
        {
            it = v1.insert(it, 100);//因為迭代器更新數據會失效 所以要用it接收 防止失效
            ++ it;//返回的是插入的位置 再次++會再次遇到原來的位置 所以插入後 要自增++一次
        }
        ++it;
    }

 

erase

此函數存在迭代器失效的問題

正常刪除即可

只是返回必須返回刪除後的下一個值

使用這個函數時,也是需要迭代器用函數返回值接收,來更新迭代器

    while (it != v.end())
    {
        if ((*it) % 2 == 0)
        {
            it = v.erase(it);
        }
        else
        {
            ++it;
        }

    }

 

clear

置空功能,只需要把有效字元置為初始值即可。

        //置空
        void clear()
        {
            _finish = _start;//把有效字元改為初始
        }

 

 

接下來是源碼

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace moxuan
{
    template<class T>
    class vector
    {
    public:
        //迭代器
        typedef T* iterator;
        typedef const T* const_iterator;

        //預設無參構造
        vector()
            :_start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {}

        //用迭代器構造的構造函數
        template <class InputIterator>
        vector(InputIterator first, InputIterator last)
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            while (first != last)
            {
                push_back(*first);
                ++first;
            }
        }

        //構造n個val的構造函數
        vector(size_t n, const T& val = T())
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            reserve(n);
            for (size_t i = 0; i < n; ++i)
            {
                push_back(val);
            }
        }

        //構造n個val的構造函數
        //因為用int會調用到其他函數 所以為了區分 單獨寫出一個第一個為int
        vector(int n, const T& val = T())
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            reserve(n);
            for (int i = 0; i < n; ++i)
            {
                push_back(val);
            }
        }

        //交換
        void swap(vector<T>& v)
        {
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_endofstoage, v._endofstoage);
        }

        //拷貝構造
        vector(const vector<T>& v)
            : _start(nullptr)
            , _finish(nullptr)
            , _endofstoage(nullptr)
        {
            vector<T> tmp(v.begin(), v.end());//復用用迭代器構造的構造函數
            swap(tmp);
        }

        vector<T>& operator=(vector<T> v)//賦值重載   不用引用  現代寫法
        {
            swap(v);//現代寫法 
            return *this;
        }

        // 資源管理
        ~vector()
        {
            if (_start)
            {
                delete[] _start;
                _start = _finish = _endofstoage = nullptr;
            }
        }

        iterator begin()
        {
            return _start;
        }

        iterator end()
        {
            return _finish;
        }

        const_iterator begin() const
        {
            return _start;
        }

        const_iterator end() const
        {
            return _finish;
        }

        size_t size() const
        {
            return _finish - _start;
        }

        size_t capacity() const
        {
            return _endofstoage - _start;
        }

        void reserve(size_t n)
        {
            size_t sz = size();
            if (n > capacity())
            {
                T* tmp = new T[n];
                if (_start)  //如果_start為空時 就不需要拷貝數據了
                {
                    //memcpy(tmp, _start, size() * sizeof(T));//把_start的數據拷貝到tmp  //這樣做 在嵌套時,會發生淺拷貝
                    for (size_t i = 0; i < size(); i++)//正確做法是直接賦值  在vector<vector> 這種嵌套時 是深拷貝
                    {
                        tmp[i] = _start[i];
                    }
                    delete[] _start;
                }
                _start = tmp;//更新過後 _start就不再是nullptr
            }
            //_finish = _start + size();//×  結果為空 解引用無地址 賦值會崩潰 ==_start+(_finish-_start)  這裡的_finsih最後是等於空  
            //而跳出函數後 _finish解引用再賦值會崩潰 因為空地址無法賦值

            _finish = _start + sz;//√ 結果為_start有地址。解引用能賦值   ==_start+0 ==  這裡先讓原先的_finish-_start==0  再+上0  因為_start已經更新過了 所以需要在開頭記錄_size的大小 
            //而更新過的_start不是空 這時候在調用size _finish-_start就不是0了 而是其他值了
            _endofstoage = _start + n;
        }

        void resize(size_t n, T val = T())
        {
            if (n > capacity())
            {
                reserve(n);
            }

            if (n > size())
            {
                while (_finish < _start + n)
                {
                    *_finish = val;
                    ++_finish;
                }
            }
            else
            {
                _finish = _start + n;
            }
        }

        void push_back(const T& x)
        {
            /*if (_finish == _endofstoage)
            {
                size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
                reserve(newCapacity);
            }

            *_finish = x;
            ++_finish;*/

            insert(end(), x);
        }

        void pop_back()
        {
            /*        if (_finish > _start)
                    {
                        --_finish;
                    }*/
            erase(end() - 1);
        }

        T& operator[](size_t n)
        {
            assert(n < size());
            //return *(_start + n);
            return _start[n]; //這個也可以
        }

        const T& operator[](size_t n) const
        {
            assert(n < size());
            //return *(_start + n);
            return _start[n]; //這個也可以
        }

        //插入 註意會有迭代器失效問題!
        iterator insert(iterator pos, const T& val)
        {
            assert(pos >= _start && pos <= _finish);
            if (_finish == _endofstoage)
            {
                size_t n = pos - _start;//因為更新start後 pos無法跟著更新 所以要記錄pos的位置
                size_t newsize = capacity() == 0 ? 4 : capacity() * 2;
                reserve(newsize);
                pos = _start + n;
            }
            iterator cur = _finish - 1;
            while (cur >= pos)
            {
                *(cur + 1) = *cur;
                --cur;
            }
            *pos = val;
            ++_finish;
            return pos;
        }

        //刪除
        iterator erase(iterator pos)//返回刪除後的下一個位置
        {
            assert(pos >= _start && pos < _finish);
            iterator it = pos+1;
            while (it != _finish)
            {
                *(it-1) = *it;
                ++it;
            }
            --_finish;
            return pos++;//返回刪除後的下一個位置
        }

        //置空
        void clear()
        {
            _finish = _start;//把有效字元改為初始
        }

    private:
        iterator _start;
        iterator _finish;
        iterator _endofstoage;
    };

 

接下來是用來測試這個vector的可行性  楊輝三角

大家可以源碼拿去編譯器上,然後調用這個test4函數即可

    class Solution {
    public:
        vector<vector<int>> generate(int numRows) {
            vector<vector<int>> vv;
            vv.resize(numRows);
            for (size_t i = 0; i < vv.size(); ++i)
            {
                // 楊輝三角,每行個數依次遞增
                vv[i].resize(i + 1, 0);

                // 第一個和最後一個初始化成1
                vv[i][0] = 1;
                vv[i][vv[i].size() - 1] = 1;
            }

            for (size_t i = 0; i < vv.size(); ++i)
            {
                for (size_t j = 0; j < vv[i].size(); ++j)
                {
                    if (vv[i][j] == 0)
                    {
                        // 中間位置等於上一行j-1 和 j個相加
                        vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];
                    }
                }
            }

            for (size_t i = 0; i < vv.size(); ++i)
            {
                for (size_t j = 0; j < vv[i].size(); ++j)
                {
                    cout << vv[i][j] << " ";
                }
                cout << endl;
            }
            cout << endl;

            return vv;
        }
    };

    void test4()
    {
        vector<vector<int>> ret = Solution().generate(5);

        for (size_t i = 0; i < ret.size(); ++i)
        {
            for (size_t j = 0; j < ret[i].size(); ++j)
            {
                cout << ret[i][j] << " ";
            }
            cout << endl;
        }
        cout << endl;
    }
}

 

這就是本文章的全部內容,感謝您能觀看到這裡,如有問題請評論或私信。感謝觀看!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我們來回憶一下,在`css_scoped`和`css_module`出現之前,人們是如何避免css命名衝突的?對,就是人為的定義一些`css命名空間`。那個時候,對每個Component組件都會在其根節點上定義一個不重覆的ID或者class作為其**命名空間**,然後其內部的其它class都會以此命... ...
  • iOS開發相關的證書有很多種,iOS開發證書(iOS App Development)、iOS發佈證書(iOS Distribution (App Store and Ad Hoc))、網站推送ID證書(Website Push ID Certificate)、Apple Pay付款處理證書(App ...
  • Serverless 架構將成為未來雲計算領域重要的技術架構,將會被更多的業務所採納。進一步深究,Serverless 架構在什麼場景下有優秀的表現,在什麼場景下可能表現得並不是很理想呢?或者說,有哪些場景更適合 Serverless 架構呢? ...
  • 顧名思義,如何建房子一樣,建造者模式可以將複雜對象的建造過程抽象出來,使用相同的構建過程可以構造出不同表現的對象。 ...
  • 山河遠闊,煙火人間,又一年,千里嬋娟~ 今天給大家帶來的是給玉兔投喂月餅的小游戲。八月十五中秋夜晚,讓我們對著月亮許願:希望我們在意和在意我們的人,諸邪避退、百事無忌、平安喜樂、萬事勝意。提前祝大家中秋節快樂。 中秋節的起源 中秋節起源於上古時代,普及於漢代,定型於唐朝初年,盛行於宋朝以後。中秋節是 ...
  • 目錄 一.OpenGL 圖像飽和度調節 1.原始圖片 2.效果演示 二.OpenGL 圖像飽和度調節源碼下載 三.猜你喜歡 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學 ...
  • JavaGUI-坦克大戰 1.Java繪圖坐標體系 坐標體系介紹:下圖說明瞭一個Java坐標體系。坐標原點位於左上角,以像素為單位。在Java坐標體系中,第一個是x坐標,表示當前位置為水平方向,距離坐標原點x個像素;第二個是y坐標,表示當前位置為垂直方向,距離坐標原點y個像素。 坐標體系-像素: 繪 ...
  • Spring-04 聲明式事務 1、事務的定義 事務就是由一組邏輯上緊密關聯的多個工作單元(資料庫操作)而合併成一個整體,這些操作要麼都執行,要麼都不執行。 2、事務的特性:ACID 1)原子性A :原子即不可再分,表現:一個事務涉及的多個操作在業務邏輯上缺一不可,保證同一個事務中的操作要不都提交, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...