成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

【C++學(xué)習(xí)】string的模擬實(shí)現(xiàn)-創(chuàng)新互聯(lián)

🐱作者:一只大喵咪1201
🐱專欄:《C++學(xué)習(xí)》
🔥格言:你只管努力,剩下的交給時間!
圖

做網(wǎng)站、網(wǎng)站設(shè)計(jì),成都做網(wǎng)站公司-創(chuàng)新互聯(lián)建站已向千余家企業(yè)提供了,網(wǎng)站設(shè)計(jì),網(wǎng)站制作,網(wǎng)絡(luò)營銷等服務(wù)!設(shè)計(jì)與技術(shù)結(jié)合,多年網(wǎng)站推廣經(jīng)驗(yàn),合理的價格為您打造企業(yè)品質(zhì)網(wǎng)站。

上篇文章中本喵介紹了C++標(biāo)準(zhǔn)庫中string類的使用,下面本喵來模擬實(shí)現(xiàn)一下string類。庫中的string類是將模板實(shí)例化并且typedef后得到的類,所以我們直接實(shí)現(xiàn)類,而忽略模板。

string類的模擬實(shí)現(xiàn)
  • 🍚默認(rèn)成員函數(shù)
    • 🍛構(gòu)造函數(shù)
    • 🍛拷貝構(gòu)造函數(shù)
    • 🍛賦值運(yùn)算符重載
    • 🍛析構(gòu)函數(shù)
  • 🍚常用的string接口
    • 🍛[]操作符重載
    • 🍛迭代器iterator
    • 🍛size()和capacity()
  • 🍚增
    • 🍛改變?nèi)萘?/li>
    • 🍛插入
    • 🍛刪除
    • 🍛查找
  • 🍚流插入(<<)流提取(>>)運(yùn)算符重載
  • 🍚總結(jié)

🍚默認(rèn)成員函數(shù)

首先就是來實(shí)現(xiàn)類和對象中的四大默認(rèn)成員函數(shù),構(gòu)造函數(shù),拷貝構(gòu)造函數(shù),析構(gòu)函數(shù),賦值運(yùn)算符重載函數(shù)。

🍛構(gòu)造函數(shù)
//使用C字符串構(gòu)造
		string(const char* str = "")
		{	_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];

			strcpy(_str, str);
		}

這是使用C字符串進(jìn)行的構(gòu)造。

  • 使用C字符串進(jìn)行構(gòu)造的時候,需要加一個缺省值,當(dāng)創(chuàng)建一個空的string的時候就會使用到這個缺省值。
  • _size表示的是有效字符的個數(shù),不包括’\0’,是通過C語言中的strlen函數(shù)求出來的。
  • _capacity表示的是有效空間的大小,同樣不包括’\0’所占的空間,在最初構(gòu)造一個string對象的時候,讓_size和_capacity的大小一樣。
  • 使用new開辟動態(tài)空間來存放字符串的時候,開辟的空間需要比_capacity大一個,這多出來的一個是專門用來存放’\0’的,一個字符串中只有一個’\0’。

為了查看效果,需要實(shí)現(xiàn)一個打印字符串的成員函數(shù):

//字符串打印
		const char* c_str()
		{	return _str;
		}

tu
可以看到,使用C字符串,空的string,以及使用string類都可以實(shí)現(xiàn)一個新的string類對象的創(chuàng)建。

🍛拷貝構(gòu)造函數(shù)

編譯器自動生成的默認(rèn)拷貝構(gòu)造函數(shù)進(jìn)行的淺拷貝,如下圖:

圖
當(dāng)使用s1來拷貝構(gòu)造s2的時候,就會導(dǎo)致如上圖所示的問題,新的s2指向的動態(tài)空間和s1指向的動態(tài)空間是同一塊空間,這樣不僅在釋放的時候會導(dǎo)致錯誤,而且在使用的時候也會在改變s2中的內(nèi)容的同時將s1中的內(nèi)容改變,所以對于拷貝構(gòu)造函數(shù)需要進(jìn)行深拷貝。

所謂深拷貝就是將內(nèi)容全部復(fù)制過來,但是s1和s2使用的是兩塊空間:
圖
而深拷貝的實(shí)現(xiàn)方式又有倆種,分別是傳統(tǒng)方式和現(xiàn)代方式。

傳統(tǒng)方式實(shí)現(xiàn):

//傳統(tǒng)拷貝構(gòu)造函數(shù)
		string(const string& s)
		{	_size = s._size;
			_capacity = s._capacity;
			_str = new char[_capacity + 1];

			strcpy(_str, s._str);
		}

這種方式在新的string對象創(chuàng)建的時候新開辟了一個和原對象一樣大小的空間,并且將原對象中的字符串拷貝過來。

現(xiàn)代方式實(shí)現(xiàn):

//現(xiàn)代拷貝構(gòu)造函數(shù)
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{	string tmp(s._str);
			std::swap(_str, tmp._str);
			std::swap(_size, tmp._size);
			std::swap(_capacity, tmp._capacity);
		}

上面的便是現(xiàn)代版本的拷貝構(gòu)造函數(shù)實(shí)現(xiàn)的思想,下面本喵用圖來給大家解釋:
圖

tmp通過s1中的字符串構(gòu)造了一個新的string類對象,而此時s2的_str指向的空間中是隨機(jī)值。
為了讓s2中的內(nèi)容變成和s1中的一樣,只要將臨時的類對象tmp中內(nèi)容拿過來即可。

圖
此時就將tmp和s2中的內(nèi)容全部進(jìn)行了交換,而s2也沒有開辟新的空間就實(shí)現(xiàn)了拷貝構(gòu)造的目的。

注意:

圖
紅色框中的初始化列表中,必須將s2的_str置為空,否則會編譯報(bào)錯。

  • s2拿走了tmp中的_str,而將自己的_str給了tmp,當(dāng)tmp生命周期結(jié)束的時候,會釋放空間,如果不是nullptr就會釋放原本不屬于tmp而屬于s2的空間,所以就會報(bào)錯.。

swap函數(shù)的實(shí)現(xiàn):

上面使用的swap是標(biāo)準(zhǔn)算法庫中的swap函數(shù),但是在string標(biāo)準(zhǔn)庫中還有一個swap函數(shù):

圖

這倆個函數(shù)是有區(qū)別的。

  • 標(biāo)準(zhǔn)算法庫中的swap是一個函數(shù)模板,它會自動推演數(shù)據(jù)類型,如果使用這個函數(shù)進(jìn)行倆個string類對象的交換,會發(fā)生三次拷貝構(gòu)造,系統(tǒng)開銷比較大。
  • 所以在交換string類對象的時候使用string庫中的swap函數(shù)比較合適。

既然是string的模擬實(shí)現(xiàn),所以同樣也需要我們自己實(shí)現(xiàn)一個。

//交換函數(shù)
		void swap(string& s)
		{	std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

在string中的swap同樣是通過標(biāo)準(zhǔn)算法庫中的swap實(shí)現(xiàn)的,細(xì)心的小伙伴肯定發(fā)現(xiàn)了一個問題,在上面代碼中,使用標(biāo)準(zhǔn)算法庫中的swap交換的數(shù)據(jù)都是內(nèi)置類型的,而函數(shù)模板中有一個語句c(a),也就是用a來拷貝構(gòu)造c。

如果a和c都是自定義類型我們不難理解,但是此時的a和c都是內(nèi)置類型。

說明:

內(nèi)置類型也有拷貝構(gòu)造函數(shù)以及析構(gòu)函數(shù),一般情況下是不用考慮這個的,而且在C語言中是沒有的,但是為了迎合模板,就不得不有。

圖

可以看到,內(nèi)置類型也可以使用拷貝構(gòu)造以及賦值運(yùn)算符重載等方式來創(chuàng)建。

此時現(xiàn)代方式的拷貝構(gòu)造函數(shù)就有了更加簡潔的寫法:

string(string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{	string tmp(s._str);
			swap(tmp);
		}

此時就非常簡潔的實(shí)現(xiàn)了深度拷貝。

🍛賦值運(yùn)算符重載

和拷貝構(gòu)造函數(shù)一樣,如果使用編譯器自動生成的默認(rèn)賦值運(yùn)算符重載函數(shù),也是會有淺拷貝的問題,所以賦值也是需要深度拷貝的。同樣的,也是有傳統(tǒng)和現(xiàn)代倆種實(shí)現(xiàn)方式。

傳統(tǒng)方式:

//傳統(tǒng)賦值運(yùn)算符重載函數(shù)
		string& operator=(const string& s)
		{	if (this != &s)
			{		_size = s._size;
				_capacity = s._capacity;

				delete[] _str;

				_str = new char[_capacity + 1];

				strcpy(_str, s._str);
			}
			return *this;
		}

和拷貝構(gòu)造函數(shù)一樣,也開辟一個和原本類對象一樣大小的空間,然后將內(nèi)容賦值過來。

  • 需要判斷一下是否是自己給自己賦值,如果是的話就直接返回this指向的內(nèi)容。
  • 給類對象賦值以后,這個類對象原本指向的空間不再使用了,因?yàn)殚_辟了新的空間,所以要將原本指向的空間使用delete釋放掉。

現(xiàn)代方式:

和拷貝構(gòu)造函數(shù)現(xiàn)代方式實(shí)現(xiàn)的思路一樣,也是拿其他對象的構(gòu)造成果,堅(jiān)決不自己開辟空間。

//現(xiàn)代賦值運(yùn)算符重載函數(shù)
		string& operator=(string& s)
		{	if (this != &s)
			{		//string tmp(s._str);
				string tmp(s);
				swap(tmp);
			}
			return *this;
		}

使用s拷貝構(gòu)造一個臨時對象tmp,然后將tmp中的內(nèi)容拿走,最后返回this指針的內(nèi)容。

既然都是找一個中間的打工仔,而不自己實(shí)現(xiàn),可以直接使用形參這個打工仔:

string& operator=(string s)
		{	swap(s);
			return *this;
		}

此時就更加的簡潔,而且形參的地址必定和this中的值不同,所以都不用排除自己給自己賦值的情況。

  • 現(xiàn)代版本的深度拷貝僅是為了代碼更加簡潔而不考慮效率。
🍛析構(gòu)函數(shù)

析構(gòu)函數(shù)沒有什么要點(diǎn),非常的簡單:

//析構(gòu)函數(shù)
		~string()
		{	delete[] _str;
			_size = 0;
			_capacity = 0;
		}

直接使用delete釋放空間即可。

🍚常用的string接口 🍛[]操作符重載

在使用string的時候,可以想方法C字符串?dāng)?shù)組一樣使用[]來訪問string類對象中的字符,下面本喵來給大家看看它是如何實(shí)現(xiàn)的:

//[]重載
		//可讀可寫
		char& operator[](size_t pos)
		{	assert(pos< _size);
			return _str[pos];
		}

		//可讀不可寫
		const char& operator[](size_t pos) const
		{	assert(pos< _size);
			return _str[pos];
		}

操作符[]的重載有倆個重載函數(shù),一個是可讀可寫的,一個是只讀不可寫的。

圖
可以看到,成果的將s1中字符串的前5個字符的ASCII碼都加了1。

🍛迭代器iterator

我們指定,迭代器是行為上像指針一樣的東西,在string類中,迭代器就是一個指針:

typedef char* iterator;

此時一個迭代器類型便實(shí)現(xiàn)好了,有了迭代器后就需要有對應(yīng)的接口來配合迭代器使用。

//begin()
		iterator begin()
		{	return _str;
		}

		//end()
		iterator end()
		{	return _str + _size;
		}

此時倆個和迭代器最常用的接口便實(shí)現(xiàn)好了。

圖
通過迭代器,將s1中的內(nèi)容全部打印了出來。

范圍for:

范圍for一直給我們的映像都很神奇,現(xiàn)在可以揭下它的神秘面紗了。

圖

此時范圍for是正常的。

圖
僅僅將begin函數(shù)中的b換成大寫的B。

圖
在執(zhí)行的時候報(bào)了一大堆的錯誤,都是和begin有關(guān)的。

圖

  • 在編譯的時候,編譯器會將范圍for的代碼轉(zhuǎn)換成使用迭代器的代碼,然后再進(jìn)行編譯。
  • 此時begin函數(shù)沒有了,所以使用迭代器的方式就無法實(shí)現(xiàn)了,所以就會報(bào)錯。

編譯器是不認(rèn)識范圍for這個語法的,所以它必須進(jìn)行轉(zhuǎn)換以后編譯器才能夠認(rèn)識,而且這個轉(zhuǎn)換是寫死了的,必須使用迭代器iterator,begin,end函數(shù),少一個都不行。

🍛size()和capacity()

成員函數(shù)size()是用來獲取效字符的個數(shù)的,capacity()是用來獲取有效空間的大小的。

//size
		size_t size() const
		{	return _size;
		}
		//capacity()
		size_t capacity() const
		{	return _capacity;
		}
🍚增 🍛改變?nèi)萘?ol>
  • reserve()
  • //reserve()
    		void reserve(size_t n)
    		{	if (n >_capacity)
    			{		char* tmp = new char[n + 1];
    				strcpy(tmp, _str);
    				delete[] _str;
    				_str = tmp;
    
    				_capacity = n;
    			}
    		}

    該函數(shù)的作用就是在擴(kuò)容,和C語言中的realloc的作用相似,但是C++中并沒有realloc這樣的函數(shù),所以擴(kuò)容就需要我們手動擴(kuò)容,就在新開辟一塊空間,并且將舊空間中的內(nèi)容拷貝進(jìn)去,再將舊空間釋放掉。

    1. resize()
    //resize()
    		void reszie(size_t n, char ch = '\0')
    		{	if (n<= _size)
    			{		_size = n;
    				_str[_size] = '\0';
    			}
    			else
    			{		reserve(n);
    				for (int i = _size; i< n; ++i)
    				{_str[i] = ch;
    				}
    				_size = n;
    				_str[_size] = '\0';
    			}
    		}

    resize分三種情況:

    • n小于等于原本的_size,此時是在縮容,只需要將_size的值改為n,并且將下標(biāo)為n的位置處放一個’\0’。
    • n大于原本的_size,并小于原本的_capacity,此時不需要進(jìn)行擴(kuò)容只需要將原本的_size設(shè)置成n即可,并且將相比于原本字符多出來的位置用形參ch來填充,如果沒有傳ch,就使用缺省值’\0’。
    • n大于原本的_capacity,此時需要進(jìn)行擴(kuò)容,之后的操作和上面的一樣。

    圖
    運(yùn)行結(jié)果符號預(yù)期。

    🍛插入
    1. push_back()
    //push_back()
    		void push_back(char ch)
    		{	if (_size == _capacity)
    			{		size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
    				reserve(newcapacity);
    			}
    			_str[_size++] = ch;
    			_str[_size] = '\0';
    		}

    尾插插入的只有一個字符串,而且是在字符串的末尾插入,在插入之前需要判斷容量是否夠用,如果不夠則需要擴(kuò)容。

    • 當(dāng)原本的容量就是0的時候,就不能簡單二倍擴(kuò)容,因?yàn)?乘2以后還是0,就需要給定一個初始容量,這里本喵給的初始容量是4。
    1. append()
    //append()
    		void append(const char* str)
    		{	size_t len = strlen(str);
    			if (_size + len >_capacity)
    			{		reserve(_size + len);
    			}
    			strcpy(_str + _size, str);
    			_size += len;
    		}

    尾部插入字符串。同樣需要判斷是否需要擴(kuò)容,之后在原本的’\0’位置處開始,放入插入的字符串,因?yàn)椴迦氲淖址詭А痋0’,所以插入以后不需要自己再寫’\0’。

    1. +=
    //+=一個字符
    		string& operator+=(char ch)
    		{	push_back(ch);
    			return *this;
    		}
    
    		//+=字符串
    		string& operator+=(const char* str)
    		{	append(str);
    			return *this;
    		}

    最常使用的就是+=,因?yàn)樗目勺x性高,而它實(shí)現(xiàn)的原理就是在運(yùn)算符重載的時候復(fù)用了push_back和append函數(shù)。

    圖
    +=一個字符和一個字符串都實(shí)現(xiàn)了。

    1. insert
    • 在指定位置插入一個字符
      tu
      挪動數(shù)據(jù)的時候有倆中方式,如上圖中的紅色框。
    • 第一個紅色框中的方法,如果不將pos強(qiáng)轉(zhuǎn)為int類型的話,當(dāng)在0位置插入字符的時候,就會在while的條件判斷時發(fā)生隱式類型轉(zhuǎn)換,int類型的end提升成size_t,此時就不會出現(xiàn)負(fù)數(shù),就會造成死循環(huán)。
    • 在指定位置插入一個字符串

    圖
    在挪動數(shù)據(jù)的時候同樣有倆種方式,其中第一種和上面的插入一個字符時的注意點(diǎn)一樣。

    下面本喵來畫圖分析一下,插入的過程:

    圖

    • end就等于_size+len,從下標(biāo)為end處開始,將end-len位置的元素移動過去,直到將pos位置處的元素也移動走。
    • 這個過程中會有元素的覆蓋,但是是使用前面的元素覆蓋后面的元素,所以不存在問題。

    圖

    • 移動完數(shù)據(jù)以后,將要插入的字符串,除’\0’以外的所有字符復(fù)制到pos位置開始的空間中,如上圖中黃色線。

    圖
    和我們分析的結(jié)果一致。

    🍛刪除
    1. erase()
      tu
      在這里我們定義一下npos,這是一個靜態(tài)成員變量,在類中進(jìn)行聲明的時候,給它一個缺省值-1,在定義的時候就會使用到這個缺省值。
    • 靜態(tài)成員變量的聲明在類中,定義必須在類外,但是有一個特殊類型,整型家族的靜態(tài)成員就可以在聲明的時候給一個缺省值。
    • 由于是size_t類型的-1,所以它的實(shí)際大小就是32個1的二進(jìn)制,轉(zhuǎn)換成十進(jìn)制大致是42億9千萬。

    npos也代表值string類對象字符串的末尾。

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

    如果要刪除的字符串長度已經(jīng)超出了現(xiàn)有字符串的長度,那么就從指定位置開始全部刪除完,否則就需要將對應(yīng)位置以后相應(yīng)長度的字符串刪除后,再將剩下的字符移動到前面。

    1. clear()
    //clear()
    		void clear()
    		{	_size = 0;
    			_str[_size] = '\0';
    		}

    clear是情況所有字符串,但是不改變?nèi)萘俊?/p>

    圖
    對應(yīng)的結(jié)果是符號我們的預(yù)期的。

    🍛查找
    //find()
    		//查找一個字符
    		size_t find(char ch, size_t pos = 0) const
    		{	assert(pos< _size);
    			while (pos< _size)
    			{		if (_str[pos] == ch)
    				{return pos;
    				}
    				pos++;
    			}
    			return npos;
    		}
    
    		//查找一個字符串
    		size_t find(const char* str, size_t pos = 0) const
    		{	assert(pos< _size);
    			const char* ptr = strstr(_str + pos, str);
    			if (ptr == nullptr)
    			{		return npos;
    			}
    			else
    			{		return ptr - _str;
    			}
    		}

    這是查找一個字符和一個字符串的代碼,找到了就返回下標(biāo),找不到就返回npos的值,在查找字符串的時候,使用了C語言中的strstr查找字串的函數(shù)。

    圖
    結(jié)果也符合我們的預(yù)期。

    🍚流插入(<<)流提取(>>)運(yùn)算符重載

    在前面本喵已經(jīng)實(shí)現(xiàn)過一次了,這里再提及一下。

    1. 流插入(<<)運(yùn)算符重載
    ostream& operator<<(ostream& out, const string& s)
    	{for (size_t i = 0; i< s.size(); ++i)
    		{	out<< s[i];
    		}
    		return out;
    	}
    1. 流提取(>>)運(yùn)算符重載
    istream& operator>>(istream& in, string& s)
    	{s.clear();
    		char ch = in.get();
    		while (ch != ' ' && ch != '\n')
    		{	s += ch;
    			ch = in.get();
    		}
    		return in;
    	}
    • cin和scanf一樣,當(dāng)遇到空格或者換行符時,自動將字符串分割
    • 如果輸入的內(nèi)容很多的話,就會不停地+=,也會導(dǎo)致不停擴(kuò)容,導(dǎo)致系統(tǒng)開銷較大。

    改進(jìn):

    istream& operator>>(istream& in, string& s)
    {s.clear();
        char buff[128] = {'\0' };
        size_t i = 0;
        char ch = in.get();
        while (ch != ' ' && ch != '\n')
        {if (i == 127)
            {s += buff;
                i = 0;
            }
            buff[i++] = ch;
            ch = in.get();
        }
        if (i >= 0)
        {buff[i] = '\0';
            s += buff;
        }
        return in;
    }

    先開辟一個數(shù)組,將輸入的字符放在數(shù)組中,當(dāng)數(shù)組滿了后進(jìn)行一次擴(kuò)容,此時即使輸入很長的字符串,擴(kuò)容的頻率也不會很高。

    🍚總結(jié)

    我們自己模擬實(shí)現(xiàn)的string并不是要和標(biāo)準(zhǔn)庫中的完全一樣,只是為了了解string的底層原理,讓我們在使用起來更加得心應(yīng)手。

    你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧

    分享標(biāo)題:【C++學(xué)習(xí)】string的模擬實(shí)現(xiàn)-創(chuàng)新互聯(lián)
    文章分享:http://jinyejixie.com/article12/djssdc.html

    成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計(jì)、虛擬主機(jī)、網(wǎng)站改版搜索引擎優(yōu)化、品牌網(wǎng)站建設(shè)網(wǎng)站維護(hù)

    廣告

    聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

    h5響應(yīng)式網(wǎng)站建設(shè)
    城市| 泸州市| 林口县| 富蕴县| 福安市| 灌阳县| 临沂市| 乌海市| 布尔津县| 泽普县| 山丹县| 德州市| 行唐县| 曲阳县| 舞阳县| 日土县| 邯郸市| 永定县| 东阿县| 南岸区| 合山市| 区。| 牡丹江市| 会宁县| 微博| 昌江| 手游| 关岭| 若羌县| 奎屯市| 杂多县| 札达县| 怀化市| 泗水县| 阳高县| 营口市| 榆林市| 清丰县| 化隆| 连平县| 紫云|