C++ STL主要組件之String總結(第二部分 深、淺拷貝問題以及賦值運算符重載)

第一部分連接https://blog.51cto.com/14232799/2447326

二.String的模擬實現

在第一步之後緊接着的就該是模擬實現部分,這一部分主要是體現自己對第一部分的掌握情況。強烈推薦和我一樣在學習String的朋友們自己動手實現一下。因爲在面試中,面試官總喜歡讓我們自己來模擬實現string類。

自己來實現String最主要是實現String類的構造、拷貝構造、賦值運算符重載(第一部分operator開頭的方法)以及析構函數。

以下是我完成的基礎模擬實現

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

namespace self{
    class string{
    public:
        string(const char* s = " "){
            if (s == nullptr){
                assert(false);
                return;
            }
            _s = new char[strlen(s) + 1];
            strcpy(_s, s);
        }
        ~string(){
            if (_s){
                delete[] _s;
                _s = nullptr;
            }
        }
    private:
        char* _s;
    };
}
int main(){
    self::string k = "hello";
    self::string i("world");
    self::string m;
    //self::string l(k);
    return 0;
}

以上就是沒有重載賦值運算符且沒有顯式定義拷貝構造函數的string類模擬實現。基本完整的模擬實現會在本篇文章的最後給出(當然免不了有紕漏,若是發現請各位大佬提醒)
上面的代碼中的main函數中有一句註釋語句 //self::string l(k); 我將其註釋是因爲如果加入這一句代碼程序就會運行崩潰!!!!
程序崩潰的原因是: 當我們不去顯式定義拷貝構造方法的時候,系統就會生成默認的拷貝構造函數,這種拷貝構造函數是一種淺拷貝,最終結果就是導致 對象l和對象k在共用同一塊內存空。看起來似乎沒什麼問題?
但是!當函數結束時,在調用析構函數的操作上就會出現大問題。
原本的話,每一個對象都會調用一次析構函數來清理自己佔用的空間。但是當兩個對象所佔用的是同一塊空間時,一個對象調用完析構函數後另一個對象調用析構函數的時候,就會生同一塊空間被釋放多次的程序錯誤!從而引起程序崩潰!
所以說在以上這個的代碼中不可以使用拷貝構造方法。這個問題也就引出了下一個要總結的部分:淺拷貝和深拷貝

三.淺拷貝和深拷貝

1.淺拷貝
此處只是給個定義:
淺拷貝:也稱位拷貝,編譯器只是將對象中的值拷貝過來。如果對象中管理資源,最後就會導致多個對象共享同一份資源,當一個對象銷燬時就會將該資源釋放掉,而此時另一些對象不知道該資源已經被釋放,以爲還有效,所以 當繼續對資源進項操作時,就會發生髮生了訪問違規。所以要解決淺拷貝問C++中引入了深拷貝。
(第二部分的string模擬事先就是個例子)
放個圖片佔位:
C++ STL主要組件之String總結(第二部分  深、淺拷貝問題以及賦值運算符重載)
2.深拷貝
“如果一個類中涉及到資源的管理,其拷貝構造函數、賦值運算符重載以及析構函數必須要顯式給出。一般情況都是按照深拷貝方式提供”
上面這句話是真理!
首先給出深拷貝一般在string'類中的實現:

String(const String& s)
 : _str(new char[strlen(s._str)+1])   // 看見這一步開闢空間就知道是深拷貝了
 {
 strcpy(_str, s._str);
 }

再來說深拷貝的定義:
每個string都需要空間來存放字符串,而當使用一個string類對象來構造另一個string類對象。就用到了深拷貝:給每個對象獨立分配資源,保證多個對象之間不會因共享資源而造成空間多次釋放而造成的程序奔潰問題。

三.String中賦值運算符重載
1.先給出幾種在string中常用的賦值運算符重載:
<1> <<

ostream& bit::operator<<(ostream& _cout, const self::String& s)
{
 cout << s._str;
 return _cout;
}

對於<<的重載算是比較特殊的了,因爲會用到ostream類型,所以在這裏展開說明一下:
ostream是output stream的簡稱,即輸出流。一個典型的輸出流對象就是在C++中標準輸出流cout。
在C++中,很少自定義ostream的對象,更多的是直接使用cout。
ostream這個類型,往往出現在<<操作重載中,作爲某個類的友元函數出現。
比如對於class A, 可以定義ostream & operator << (ostream &os, const A& a);
這樣在調用A的對象var時,就可以這樣使用
cout &lt;&lt; var ;

<2> =

String& operator=(String s)
 {
 swap(_str, s._str); 
 return *this;
 }

<3> +=

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

<4> [ ]

char& operator[](size_t index)
 {
 assert(index < _size);
 return _str[index];
 }

四.最後是給出的較爲完整的string類模擬實現:

namespace bit
{
 class String
 {
 public:
 typedef char* iterator;
 public:
 String(const char* str = "")
 {
 _size = strlen(str);
 _capacity = _size;
 _str = new char[_capacity+1];
 strcpy(_str, str);
 }
 String(const String& s)
 : _str(nullptr)
 , _size(0)
 , _capacity(0)
 {
 String tmp(s);
 this->Swap(tmp);
 }
 String& operator=(String s)
 {
 this->Swap(s)
 return *this;
 }
 ~String()
 {
 if (_str)
 {
 delete[] _str;
 _str = nullptr;
 }
 }
 /////////////////////////////////////////////////////////////////
 // iterator
 iterator begin() {return _str;}
 iterator end(){return _str + _size;}
 /////////////////////////////////////////////////////////////////
 // modify
 void PushBack(char c)
 {
 if (_size == _capacity)
 Reserve(_capacity*2);

 _str[_size++] = c;
 _str[_size] = '\0';
 }
 String& operator+=(char c)
 {
 PushBack(c);
 return *this;
 }
 void Clear()
 {
 _size = 0;
 _str[_size] = '\0';
 }
 void Swap(String& s)
 {
 swap(_str, s._str);
 swap(_size, s._size);
 swap(_capacity, s._capacity);
 }
 const char* C_Str()const
 {
 return _str;
 }
 size_t Size()const
 size_t Capacity()const
 bool Empty()const

 void Resize(size_t newSize, char c = '\0')
 {
 if (newSize > _size)
 {
 // 如果newSize大於底層空間大小,則需要重新開闢空間
 if (newSize > _capacity)
 {
 Reserve(newSize);
 }
 memset(_str + _size, c, newSize - _size);
 }
 _size = newSize;
 _str[newSize] = '\0';
 }
 void Reserve(size_t newCapacity)
 {
 // 如果新容量大於舊容量,則開闢空間
比特科技
 if (newCapacity > _capacity)
 {
 char* str = new char[newCapacity + 1];
 strcpy(str, _str);
 // 釋放原來舊空間,然後使用新空間
 delete[] _str;
 _str = str;
 _capacity = newCapacity;
 }
 }
 char& operator[](size_t index)
 {
 assert(index < _size);
 return _str[index];
 }
 const char& operator[](size_t index)const
 {
 assert(index < _size);
 return _str[index];
 }
 private:
 friend ostream& operator<<(ostream& _cout, const bit::String& s);
 private:
 char* _str;
 size_t _capacity;
 size_t _size;
 };
}
ostream& bit::operator<<(ostream& _cout, const bit::String& s)
{
 cout << s._str;
 return _cout;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章