首頁 > 軟體

C++模擬實現string的方法詳解

2022-11-11 14:00:19

1.string 成員變數

首先需要一個動態開闢的指標指向這個字串,然後還需要容量和儲存的個數,並且我們不能和標準庫的string進行衝突所以我們需要寫在我們自己的類域中,並且我們庫中還有一個靜態的變數是npos,就是無符號的-1,代表整形的最大值:

namespace cyf
{
    class string
    {
    public:
        //成員函數
    private:
        char *_str;
        size_t size;
        size_t capaticy;
        const static size_t npos = -1;
    };
}

這裡有一個特例:static成員變數一般在類中宣告在類外定義,但是const static int型的變數可以直接在類中定義。

2.建構函式

strlen求出的是之前的字元個數,所以_size和_capacity標識的是實際儲存的字元個數,在開闢空間時多開闢一個字元用來儲存''。

string(const char* s = "")  
        {
            _size = strlen(s);  
            _capacity = _size;
            _str = new char[_capacity + 1];   
 
            strcpy(_str, s);  //開闢好空間後將s的內容拷貝至_str
        }

3.拷貝構造、賦值過載和解構函式

1.拷貝構造

_str 維護的是一塊空間,所以不能簡單的將s._str的值賦值給_str (淺拷貝),而是單獨開闢一塊空間,讓_str指向這一塊空間,再將s._str空間中的值拷貝至新開闢的空間,新開闢的空間比_capacity多開一個位元組用來儲存'',作為字串的結束標誌。

//string s1(s)
string(const string& s)
        {
            _size = s._size;
            _capacity = s._capacity;
            _str = new char[s._capacity + 1];
 
            strcpy(_str, s._str);
        }

2.賦值過載

首先開闢一塊空間,將字串的內容拷貝至這個空間,將_str原來指向的空間釋放,_str再指向這個新開闢的空間,size和capacity還是原來的大小。

//s2=s1
        string& operator=(const string& s)
        {
            if (this != &s)   //避免自己給自己賦值
            {
                char* tmp = new char[s._capacity + 1];
                strcpy(tmp, s._str);
                delete[] _str;
                _str = tmp;
                _size = s._size;
                _capacity = s._capacity;
            }
            return *this;
        }

3.解構函式

~string()
        {
            delete[] _str;
            _str = nullptr;
            _size = _capacity = 0;
        }

4.存取成員變數

提供介面可以在類的外邊檢視字串的內容,儲存字串的元素個數和容量

        const char* c_str()
        {
            return _str;
        }
 
        size_t size()
        {
            return _size;
        }
        size_t capacity()
        {
            return _capacity;
        }

配合之前的建構函式和這裡的介面,我們進行驗證:

執行結果:

5.遍歷

遍歷有三種模式:

1.下標+【】

_str是一個指標,那麼我們可以通過陣列的方式來存取,只需要過載operator []即可。我們還是要過載兩個版本的,因為普通變數和const變數的存取許可權不一樣。

//普通變數,可讀可寫
char& operator[](size_t pos)
{
    assert(pos < _size);  //檢查不能越界存取
 
    return _str[pos];
}
 
//const變數,唯讀屬性
char& operator[](size_t pos) const
{
    assert(pos < _size);
 
 
    return _str[pos];
}

2.迭代器(iterator)

在string中,迭代器就是一個指標,只不過我們進行了封裝,typedef一下就可以啦,同樣我們也要實現兩個版本的,const和非const的。

typedef char* iterator;
typedef const char* const_iterator;
 
iterator begin()
{
    return _str;
}
 
const_iterator begin()const
{
    return _str;
}
 
iterator end()
{
    return _str +_size;
}
 
const_iterator end() const
{
    return _str +_size;
}

3.範圍for

我們範圍for的底層就是迭代器,所以我們不用實現,只要實現了迭代器,那麼我們就可以直接使用範圍for,範圍for在執行的時候實際還是通過迭代器實現的,上例子:

執行結果:

6.空間的申請

1.reserve

一般是我們原空間容量滿了,需要申請空間擴容,我們的擴容函數還是要先申請空間,然後在進行拷貝,接著我們delete原來的空間,把申請的空間的指標和 容量 賦值過去即可。 

void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];    //多開一個位元組留給'';
                strcpy(tmp, _str);
                delete[] _str;
                _str = tmp;
 
 
                _capacity = n;
            }
 
 
        }

2.resize

1. 如果我們是傳入一個正整數大於_size的值,那麼我們可以使用傳入的字元(或者預設值)把我們申請的空間進行初始化,也就是從_size到n-1置為我們傳入的字元,n置為' ',最後把_size置為n。

2.如果傳入一個小於_size正整數,那麼我們把0~_size-1進行初始化為傳入的字元(或者預設值),把n位置置為' ',接著我們會把_size置為n,而_capaticy不變。

void resize(size_t n, char ch = '')
        {
            if (n > _size)
            {
                reserve(n);
                for (size_t i = _size; i < n; ++i)
                {
                    _str[i] = ch;
                }
                _size = n;
                _str[_size] = '';
 
            }
 
            else   //小於
            {
                _str[n] = '';
                _size = n;
 
            }
 
    }

7.增刪查改

1.push_back   尾插一個字元

上來就檢查容量,_size==_capacity時就說明沒有容量了,得分類討論:1.原來的字串有元素2.原來的是空字串,如果字串為空,就給4個位元組大小的容量 。如果原來的字串不為空但是需要擴容就呼叫reserve函數進行擴容,容量擴為2倍,2倍比較合適,避免給的小了頻繁的擴容,但是也不能給的太大了,太大了會造成空間的浪費。在_size的位置插入字元ch ,_size++,插入的字元ch 將原來的''給覆蓋了,最後再補上'',作為字串的結束標誌。

void push_back(char ch)
        {
            if (_size == _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }
            _str[_size] = ch;   
            ++(_size);
            _str[_size] = ''; //記得處理 
        }

2.append 尾部插入一個字串

插入字串的大小不確定,就需要確定是否需要擴容。當插入的字串的長度加上當前字串的有效元素個數大於容量_capacity時,就需要擴容,擴後的容量大小為_size+len ,這裡給reverse函數傳的是有效元素的個數,在reverse函數內部為我們多開了一個字元的大小用來儲存''。再進行拷貝工作,這裡記得插入元素後_size要進行變換。

void append(const char* str)
        {
            size_t len = strlen(str);
            if (len + _size > _capacity)
            {
                reserve(_size + len);   
 
            }
            strcpy(_str + _size, str);
            _size += len;
        }

3.insert  在指定位置插入一個字元

上來首先檢查要插入的位置是否合理,_size 代表的位置是有效元素的下一個位置即''的位置,pos的範圍【0,_size】string字串的頭到尾部之間 ,插入元素要檢查容量,記得考慮原string是否為空串的情況。插入資料需要挪動資料,從前往後挪動資料,將end位置確定到''的下一個位置,這樣方便頭插。最終將pos位置騰出來,插入字元ch 插入資料後_size++。

string& insert(size_t pos, char ch)
        {
            assert(pos <= _size);  //等於size 時候相當於尾部插入
            if (_size == _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }
            size_t end = _size + 1;  //這裡是把往  的下一個位置挪動 方便頭插   
            while (end > pos)
            {
                _str[end] = _str[end - 1];
                --end;
            }
            _str[pos] = ch;
            _size++;
            return *this;
        }

4.insert 在指定位置插入一個字串

string& insert(size_t pos, const char* str),我們先進行斷言pos不能超過_size,接著我們開闢空間,這次就不考慮空串的問題了,因為我們要指定開闢的位元組數,和上面一樣的我們也要進行挪動資料,我們只不過是由每次挪動一個步,變為了挪動 len 步了,最後使用strncpy插入字串,把_szie +=len 即可。

畫圖理解:

string& insert(size_t pos, const char* str)
        {
            size_t len = strlen(str);
            if (_size + len > _capacity)
            {
                reserve(_size + len);
            }
            size_t end = _size + len;
            while (end > pos + len - 1)
            {
                _str[end] = _str[end - len];
                --end;
            }
            strncpy(_str + pos, str, len);
            _size += len;
            return *this;
        }

實現了上述的介面當然最好用的還是下面的介面,對push_back和append進行封裝實現string+=

一個字元 和 string+=一個字串

    string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }
 
        string& operator+=(const char* str)
        {
            append(str);
            return *this;
 
        }

5.刪除介面:erase

指定字串從開始位置到指定位置刪除元素。得保證刪除的位置在string的內部,當只給定了刪除的起始位置沒有給結束的位置那麼就觸法我們的預設值,即從pos位置開始直到將字串刪完,或者說給定的結束位置大於了字串的本身長度,那就從pos位置開始直到刪除完字串,實現的方法很簡單,直接在pos位置添字串的結束標誌''。  如果給定的兩個值都在字串的內部直接進行從len位置往前拷貝覆蓋掉要刪除的元素。

string& erase(size_t pos, size_t len = npos)  
        {
            assert(pos <= len);
            if (len == npos || len >= _size - pos)
            {
                _str[pos] = '';
                _size = pos;
            }
            else
            {
                strcpy(_str + pos, _str + pos + len);
                _size -= len;
 
            }
            return *this;
        }

6.find字元 從某個位置開始查詢字元,如果沒有給定開始位置,就用預設值,預設從開頭尋找,找到了就返回元素的下標,沒有找到就返回npos。

size_t find(char ch, size_t pos = 0)  ///預設從pos位置開始尋找,有預設值0,從pos 位置開始往後尋找
        {                                     //對比找到了就返回下標,找不到返回npos
            assert(pos < _size);
            while (pos < _size)
            {
                if (_str[pos] == ch)  //遍歷尋找
                {
                    return pos;
                }
                pos++;
            }
            return npos;
        }

7.find字串  ,從某個位置開始往後尋找字串,找到了就返回下標,找不到就返回npos

這裡套用c語言的庫函數strstr進行實現

size_t find(const char* str, size_t pos = 0)
        {
            assert(pos < _size);
            const char* ptr = strstr(_str + pos, str);   
            if (ptr == nullptr)  //strstr找不到返回空指標
            {
                return npos;    //轉換至cpp找不到就返回npos
            }
            else
            {
                return ptr - _str;  //返回的是下標  指標-指標  ==下標
            }
 
        }

8.clear  清空字串的內容

直接在第一個位置加入結束標誌就將字串清空了,將清空後_size就為0,

void clear()
        {
            _size = 0;
            _str[0] = '';
        }

8.過載cin 和 cout

1.cout 

依次的輸出string物件的內容即可

    ostream& operator<<(ostream& out,const string& s)
    {
        for (size_t i = 0; i < s.size(); i++)
        {
            out << s[i];
        }
 
        return out;
    }

2.cin

這裡注意因為要改變字串的內容,首先呼叫clear函數清空原來的內容,因為要改變字串的內容所以不用const 直接參照改變的就是字串的本身。因為我們的 in 會預設 '空格' 和 ' n '是分割符,不進行讀取,這樣我們就沒辦法停止。需要使用下 in 的get函數,讓我們的來讀取 ‘  ’  和         ' n ',我們看下程式碼:

void clear()
{
    _str[0] = '';
    _size = 0;
}
 
istream& operator>>(istream& in, string& s)
{
    s.clear();//要先進行清理,否則就會出現剩餘的資料也被我們寫入了。
    char ch;
    ch = in.get();
 
    char buff[32];
    size_t i = 0;
 
    while (ch != ' ' && ch != 'n')
    {
        buff[i++] = ch;
        if (i == 31)
        {
            buff[i] = '';
            s += buff;
            i = 0;
        }
        ch = in.get();
    }
 
    buff[i] = '';
    s += buff;
 
    return in;
}

cin和cout的過載不一定是類的友元函數,在類中提供介面,我們也可以直接存取類的成員變數!

到此這篇關於C++模擬實現string的方法詳解的文章就介紹到這了,更多相關C++實現string內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com