一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C/C++ - C++string底層框架模擬實現代碼

C++string底層框架模擬實現代碼

2022-02-21 15:39SuchABigBug C/C++

本節文章主要說明淺拷貝和深拷貝的優缺點,以及仿寫string類的邏輯并分析實現過程,對C++string底層框架模擬實現代碼感興趣的朋友一起看看吧

一、 前言

本節文章主要說明淺拷貝和深拷貝的優缺點,以及仿寫string類的邏輯并分析實現過程
如果人對一個事物感興趣,總是那么好奇,就會刨根問底

 

二、 淺拷貝與深拷貝優缺點

1. 淺拷貝

話不多說,先來上一組代碼

class string{
 public:
     string(char* str="")
         :_size(strlen(str))
         ,_capacity(_size)
     {
         _str = new char[_capacity+1];
         strcpy(_str, str);
     }

     char& operator[](const char& pos) const {
         return _str[pos];
     }
     
     ~string(){
         delete[] _str;
         _str = nullptr;
         _size = _capacity = 0;
     }

 private:
     char* _str;
     size_t _size;
     size_t _capacity;
};
void Test1(){
  string s1("never gonna give you up");
  string s2 = s1;
  s2[0] = 'Y';
}

當我們把s1拷貝給s2,這里是深拷貝還是淺拷貝呢?先不說答案我們debug看一下

C++string底層框架模擬實現代碼

一目了然,這里的s1和s2為同一個地址

C++string底層框架模擬實現代碼

那么當我們改變s2[0]的值時,s1[0]也隨之改變,那么還是給人一種藕斷絲連的感覺沒有完全獨立拷貝,這就是淺拷貝,相信我們也看到了淺拷貝帶來的弊端

其次,當我們調用析構函數也會出現問題,由于他們指向的是同一塊空間,s2會先析構將里面的數組delete并置空

C++string底層框架模擬實現代碼

接著s1調用析構函數時就會報錯,所以不能指向同一塊空間

C++string底層框架模擬實現代碼

總結一下淺拷貝問題:

這塊空間會在兩個對象析構函數被delete兩次一個對象修改會影響另外一個對象

2. 深拷貝

深拷貝怎么實現?

string(const string& s) //函數重載
  :_str(new char[strlen(s._str)+1])
{
  strcpy(this->_str, s._str);
}

我們重新new一個空間給s2,下面下圖可以看到s1和s2在堆上不再是同一個空間
這里第一個參數是this被匿名了,為了方面看,我把this加了進去
和淺拷貝區別就在于指明了當前字符串需重新分配空間和重新賦值到新開的這組新空間,這里的深拷貝就不會出現上面的問題,當我們改變s2[0]的值,s1[0]不會跟著改變

C++string底層框架模擬實現代碼

賦值重載的方式解決淺拷貝問題

string& operator=(string& s){
  if(this != &s){
      char* tmp = new char[strlen(s._str)+1];
      delete[] _str; //刪除this原來的內容
      _str = tmp; //將新空間賦給_str
      strcpy(_str, s._str);
      _size = _capacity = strlen(s._str);
      _str[_size] = '\0';
  }
  return *this;
}

3. 深拷貝現代版

上面都為傳統寫法,通過new開辟一組新的堆空間,和現代寫法的思路是有區別的
這里是重新構造一個臨時string tmp變量新開了一組空間,然后再swap把兩者進行交換,等于this->_str獲得了這個新空間,最后把不需要的空間自動析構

string (const string& str)
  :_str(nullptr)
{
  string tmp(str);
  swap(_str, tmp._str);
}

//這組沒有用到引用,因為形參自動調用拷貝構造
string& operator=(string t)
{
  swap(_s,t._s);
  return *this;
}

4. 寫時拷貝

那么是不是深拷貝一定就是最好的呢?
答案是不一定,寫時拷貝在淺拷貝基礎上增加了引用計數方式,換句話說有多少個對象指向這塊空間,計數就會++,那么只有當某個對象去寫數據時,那個對象才會進行深拷貝,然后計數C

這本質上是寫的時候一種延遲深拷貝,但如果拷貝對象后沒有人進行修改,沒有進行深拷貝重開一塊空間,也就順理成章的提高效率。但是實際應用場景中不是很理想

 

三、 string框架搭建

1. 框架定義

這里私有成員結構就類似順序表,當我們增加或刪除數據時需要記錄當前的信息,以及是否需要擴容等,npos用于查找是否有此字符,默認返回-1

class string{
 public:
 private:
     char* _str;
     size_t _size;
     size_t _capacity;
     static const size_t npos;
};
const size_t string::npos = -1;

2. 構造函數

void Test2(){
  string s1("helloworld");
  string s2(5, 'g');
  string();
}

上面的函數名都構成函數重載,目前實現了三種常見的

string(const char* str="")
  :_size(strlen(str))
  ,_capacity(_size)
{
  _str = new char[_capacity+1];
  strcpy(_str, str);
}

第一種最普遍,直接將字符串賦給s1即可

string(size_t n, char c)
  :_size(n)
  ,_capacity(_size)
{
  _str = new char[ _size + 1];
  for(size_t i=0; i<n; ++i) _str[i] = c;
}

第二種可以創建一組n個相同的字符

string()
  :_str(new char[1])
  ,_size(0)
  ,_capacity(0)
{
  *_str = '\0';
}

最后一種創建一個空函數,里面并不為空,默認放一個斜杠零

3. 析構函數

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

用于釋放空間,當我們new完一組空間后需要手動創建析構函數

4. 賦值重載

下列賦值重載中,第一個支持讀和寫

char& operator[](size_t pos){
 return _str[pos];
}

第二個不支持寫

const char& operator[](size_t pos) const{
  return _str[pos];
}

這里我把賦值重載都給列出來,push_back函數在下面會提到

//可讀可寫
String& operator+=(char ch){
  push_back(ch);
  return *this;
}

下列的賦值重載,實現依次比較數組中值的大小

//按照ASCII碼進行比較
//s1 > s2 ?
//"abc" "abc" false
//"abc" "ab" true
//"ab"  "abc" false
bool operator>(const string& s1, const string& s2){
  size_t i1 = 0, i2 =0;
  while(i1 < s1.size() && i2 < s2.size()){
      if(s1[i1] > s2[i2]){
          return true;
      }else if(s1[i1] < s2[i2]){
          return false;
      }else{
          ++i1;
          ++i2;
      }
  }
  if(i1 == s1.size()){
      return false;
  }else{
      return true;
  }
  
  return true;
}

bool operator==(const string& s1, const string& s2){
  size_t i1 = 0, i2 =0;
  while(i1 < s1.size() && i2 < s2.size()){
      if(s1[i1] > s2[i2]){
          return true;
      }else if(s1[i1] < s2[i2]){
          return false;
      }else{
          ++i1;
          ++i2;
      }
  }
  if(i1 == s1.size() && i2 == s2.size()){
      return true;
  }else{
      return false;
  }
}

bool operator!=(const string& s1, const string& s2){
  return !(s1==s2);
}

bool operator>=(const string& s1, const string& s2){
  return (s1>s2 || s1==s2);
}

bool operator<(const string& s1, const string& s2){
  return !(s1 >= s2);
}

bool operator<=(const string& s1, const string& s2){
  return !(s1>s2);
}

String operator+(const string& s1, const string& str){
  String ret = s1;
  ret += str;
  return ret;
}

5. 實現擴容

resize分為三種情況

  • 當n小于等于size,則size等于n
  • 當n大于size但小于capacity,無法添加數據
  • 當n大于capacity時,先增容,然后從size開始填數據,填到n
void reserve(size_t n){
   if(n > _capacity){
       char* tmp = new char[n+1]; //開一個更大的空間
       strcpy(tmp, _str); //進行拷貝,然后釋放此空間
       delete[] _str;
       _str = tmp; //然后指向新開的空間
   }
   
   _capacity = n;
}

void resize(size_t n, char ch='\0'){
   //大于,小于,等于的情況
   if(n <= _size){
       _size = n;
       _str[_size] = '\0';
   }else{
       if(n > _capacity){ //空間不夠,先增容
           reserve(n);
           for(size_t i = _size; i<n; i++){ //從size開始填數據,填到n
               _str[i] = ch;
           }
           _size = n;
           _str[_size] = '\0';
       }
   }
}

6. 增添數據

在字符串的最后增加一個字符

void push_back(char c){
  if(_size >= _capacity){
  	//這種如果size和capacity都為0,那么計算出的2倍w也為0,最后調用析構的時候報錯
      //reserve(_capacity * 2); 
      size_t newCapacity = _capacity == 0 ? 4 : _capacity*2;
      reserve(newCapacity);
  }
  _str[_size] = c;
  ++_size;
  _str[_size] = '\0';
}

在字符串的最后增加一串字符,這里需要注意判斷原本容量的大小是否比新增的字符串容量要小,如果是就需要新reserve一組更大的空間

void append(const char* s){
  size_t len = strlen(s);
  if(_size + len >= _capacity){
      reserve(_size + len);
  }
  strcpy(_str + _size, s); //把這個字符串給拷貝過去
  _size += len;
}

在pos位置,插入字符或者字符串,這里我們實際應用中,不推薦使用,因為數組數據過于龐大時,在中間插入后,pos后面的數據都需要依次向后挪動

string& insert(size_t pos, char ch){
  assert(pos<=_size); //pos不能超出此范圍
  if(_size == _capacity){
      size_t newcapacity = _capacity == 0 ? 4 : _capacity*2;
      reserve(newcapacity);
  }

  size_t end = _size+1;
  while( end > _size){
      _str[end-1] = _str[end];
      --end;
  }
  _str[_size] = ch;
  ++_size;
  
  return *this;
}

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

7. 刪除數據

String& erase(size_t pos, size_t len=npos){
  assert(pos < _size);
  //1. pos后面刪完
  //2. pos后面刪一部分
  if(len == npos || len+pos >= _size){
      _str[pos] = '\0';
      _size = pos;
  }else{
      //刪一部分
      strcpy(_str + pos, _str + pos + len);
      _size -= len;
  }
  return *this;
}

8. 數據查找

查找匹配的第一個字符,返回其下標

//查找,直接返回字符的下標位置
size_t find(char ch, size_t pos=0){
  for(size_t i = pos; i<_size; ++i){
      if(_str[i] == ch ){
          return i;
      }
  }
  return npos;
}

查找字符串,這里就直接調用C語言中的strstr函數進行暴力匹配查找

size_t find(const char* sub, size_t pos=0){
  const char* p = strstr(_str+pos, sub);
  if(p == nullptr){
      return npos;
  }else{
      return p-_str;
  }
}

9. iterator迭代器

前面不加const的支持數據修改
初次用迭代器玩不明白,其實刨開看也沒有特別神奇,只不過是封裝起來了

typedef  char* iterator;
typedef const char* const_iterator;

iterator begin(){
  return _str;
}

iterator end(){
  return _str + _size;
}

const_iterator begin() const{
  return _str;
}

const_iterator end() const{
  return _str + _size;
}

10. 插入/提取流與getline函數

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

//流提取
istream& operator>>(istream& in, String& s){
 s.clear();
 char ch;
//        in >> ch; 遇到換行會自動停止
 ch = in.get();
 while(ch != ' ' && ch != '\n'){
     s += ch; //提取字符串到這個String s字符串中去
     ch = in.get();
 }
 return in;
}
//上面的是遇到空格就停止了然后傳給cout,而下面我們要實現一行哪怕中間有空格
istream& getline(istream& in, String& s){
  s.clear();
  char ch;
  ch = in.get();
//    while(ch != ' ' && ch != '\n'){
  while(ch != '\n'){ //遇到空格不結束
      s += ch;
      ch = in.get();
  }
  return in;
}

我們可以看到流提取和getline的最大區別在于,流提取遇到空格后,就直接結束了
而getline不一樣,函數里面忽略判斷空格條件,而只保留判斷換行符

void test1(){
	// cin >> s1;
	// cout << s1 << endl; //注意如果有空格的hello world,hello打印出來了但是 world check the rythm還在緩沖區
	getline(cin, s1); //打印帶有空格的一個字符串
	cout << s1 <<endl;
}

 

四. 完整代碼

Gitee鏈接

string simulation

到此這篇關于C++string底層框架模擬實現代碼的文章就介紹到這了,更多相關C++string底層框架內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/sinat_44182212/article/details/121249742

延伸 · 閱讀

精彩推薦
  • C/C++詳解c語言中的 strcpy和strncpy字符串函數使用

    詳解c語言中的 strcpy和strncpy字符串函數使用

    strcpy 和strcnpy函數是字符串復制函數。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數使用,感興趣的朋友跟隨小編要求看看吧...

    spring-go5642021-07-02
  • C/C++C語言實現電腦關機程序

    C語言實現電腦關機程序

    這篇文章主要為大家詳細介紹了C語言實現電腦關機程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    xiaocaidayong8482021-08-20
  • C/C++C/C++經典實例之模擬計算器示例代碼

    C/C++經典實例之模擬計算器示例代碼

    最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關于C/C++經典實例之模擬計算器的相關資料,文中通過示...

    jia150610152021-06-07
  • C/C++C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下...

    青山的青6062022-01-04
  • C/C++c++ 單線程實現同時監聽多個端口

    c++ 單線程實現同時監聽多個端口

    這篇文章主要介紹了c++ 單線程實現同時監聽多個端口的方法,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下...

    源之緣11542021-10-27
  • C/C++C語言中炫酷的文件操作實例詳解

    C語言中炫酷的文件操作實例詳解

    內存中的數據都是暫時的,當程序結束時,它們都將丟失,為了永久性的保存大量的數據,C語言提供了對文件的操作,這篇文章主要給大家介紹了關于C語言中文件...

    針眼_6702022-01-24
  • C/C++深入理解goto語句的替代實現方式分析

    深入理解goto語句的替代實現方式分析

    本篇文章是對goto語句的替代實現方式進行了詳細的分析介紹,需要的朋友參考下...

    C語言教程網7342020-12-03
  • C/C++學習C++編程的必備軟件

    學習C++編程的必備軟件

    本文給大家分享的是作者在學習使用C++進行編程的時候所用到的一些常用的軟件,這里推薦給大家...

    謝恩銘10102021-05-08
主站蜘蛛池模板: 青青视频国产依人在线 | 91手机看片国产永久免费 | 91精品乱码一区二区三区 | 成人性爱视频在线观看 | 国内精品久久久久影院男同志 | 国产1区精品 | 亚欧视频在线观看 | 色播导航| 四虎永久在线精品波多野结衣 | 单亲乱l仑在线观看免费观看 | 国产区成人精品视频 | 精品无人区乱码1区2区3区在线 | 亚洲无限观看 | xxx95日本老师xxx学生 | 免费欧美一级 | 欧美综合国产精品日韩一 | 欧美18-19| 青青久久精品国产 | 亚洲精品福利你懂 | 欧美a级v片在线观看一区 | 饭冈加奈子黑人解禁在线播放 | www.av免费| 成人中文字幕在线高清 | 99热精品在线免费观看 | 亚洲高清在线精品一区 | 国产成人+亚洲欧洲 | 亚洲精品中文字幕久久久久久 | 久久精品热只有精品 | 视频在线观看国产 | 欧美18~20性hd| 超级乱淫伦小说全集np | 亚洲国产精品嫩草影院久久 | 99九九成人免费视频精品 | 男女激情网 | 成人福利免费在线观看 | 久久精品久久久 | chinese男性厕所撒尿合集 | 息与子中文字幕bd | 国产成人影院 | 国产成人亚洲精品一区二区在线看 | 久草高清在线 |