C++篇(7)string類的模擬實現

一、string的成員變量

string和數據結構中的順序表類似,本質上可以理解成字符順序表,其成員變量仍然是_str,_size和_capacity。但是,C++標準庫里面也有一個string,和我們要自己實現的string類沖突了,該如何解決呢?這里就體現出了命名空間的作用。

namespace bit
{class string{private:char* _str;size_t _size;size_t _capacity;};
}

二、string的構造與析構

首先先來實現無參的構造函數。

string();

提到string的構造,我們第一想法可能是這樣寫:

string::string():_str(nullptr),_size(0),_capacity(0)
{}

但是這里要注意,這樣寫會有一個很大的問題。我們來看下面這個樣例。

#include "string.h"namespace bit
{void test_string1(){string s1;cout << s1.c_str() << endl;}
}int mian()
{bit::test_string1();return 0;
}

運行代碼之后我們發現,程序崩潰了!

程序的退出碼非0,程序崩潰

那為什么程序會崩潰呢?因為這里的s1的_str是const char*的空指針,我們要打印出s1的字符,就會對該空指針解引用,程序就會崩潰。但是換成標準庫里的string類,程序能正常運行。

說明我們的string類實現是有問題的。_str應該也有一份空間,該空間初始化成'\0'。

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

現在再運行剛才的程序,就沒有問題了。

這里我們先不急著重載流插入和流提取,因為比較復雜,后面再細講,這里先用c_str來返回const char*的指針,同樣可以打印出字符串。c_str()函數實現如下:

const char* string::c_str() const
{return _str;
}

接著我們來看帶參數的構造函數該如何實現。

string(const char* str);

可能會有人直接把str的值傳給_str。

string::string(const char* str):_str(str)
{}

注意,不能直接把str的值給_str,因為str是const char*類型,而_str是char*類型,二者類型不匹配。所以,這里應該要給_str開一塊同樣大小的空間,再把str里面的內容拷貝過來。

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

注意,strlen不包含字符串后面的'\0',但是strcpy會把'\0'一起拷貝過來。所以_str開的空間要加1,而size和capacity在C++委員會中規定,不包含'\0',所以不用加1。

但是,strlen是在運行時計算,這里用了三次strlen,那么該如何優化呢?能不能用strlen初始化_size,再用_size初始化_capacity和_str(如下面代碼所示)

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

這個方法看似沒有問題,但是有一個大坑!初始化列表是按聲明的順序初始化,所以要是按照下面代碼優化的話,我們就需要把_size放在_str的上面聲明。但是這樣寫就極大地增加了代碼的耦合度,降低了可讀性。所以,我們這里可以不用初始化列表來全部初始化,而是可以放在函數體內初始化。

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

析構函數的實現很簡單,這里直接給出代碼。

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

三、string的相關函數

3.1 尾插一個字符

尾插字符的操作很簡單,就是在_size的位置插入字符,然后讓_size++,這里有個細節要注意一下,字符串的末尾是\0,所以要在_size++后的位置再插入\0。接下來再考慮內存不夠需要擴容的情況,這里我們先實現reserve函數用來申請空間。我們新申請一塊空間,把原空間的內容拷貝到新空間中,釋放舊空間,讓_str指向新空間即可。

void string::reserve(size_t n)
{if (n > _capacity){char* str = new char[n + 1];strcpy(str, _str);delete[] _str;_str = str;_capacity = n;}
}void string::push_back(char ch)
{if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';
}

3.2 追加一個字符串

首先考慮擴容的問題,這里的擴容就不能像之前一樣擴二倍,因為要考慮追加的字符串的長度。如果追加的字符串過長,那么擴二倍還不夠。所以這里要用三目運算符判斷一下。

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

3.3 流插入運算符重載

對于流插入運算符重載,我們第一時間想到的就是把s.c_str()輸出即可。(如下面代碼所示)

ostream& operator<<(ostream& out, const string& s)
{out << s.c_str();return out;
}

看似沒有任何問題,但是下面這個樣例程序就會出現bug。

可以看到,當我在字符串的后面加上幾個'\0'之后,再加上一個字符'!',輸出結果中并沒有出現'!'這個字符。但是標準庫里的string重載的流插入運算符卻仍然能正常打印。

那么到底錯在哪了呢?我們輸出的是c_str,底層是const char*的指針,在打印時遇到'\0'就自動結束了。所以,這里應該一個字符一個字符地打印。

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

這樣寫看似沒問題了,但是下面這個樣例又出現了bug。

輸出結果中出現了亂碼。但是標準庫中的卻沒有任何問題。

打開程序監視窗口之后我們發現,字符串中少了一個'\0'和一個'!',所以這一塊內容出現的是隨機值。

那到底是什么原因導致的呢?這里我們插入的字符串長度較大,所以需要擴容。而我們擴容用到的是strcpy,strcpy遇到'\0'就停止拷貝了,所以'\0'和'!'才沒有出現在字符串中,然后我們再把新插入的字符串拷貝到str+_size的位置,所以中間的那兩個位置就變成了隨機值,而通常情況下,漢字占兩個字節,所以就會出現一個“屯”字。所以我們就不能用strcpy來拷貝,而是要用memcpy。

void string::reserve(size_t n)
{if (n > _capacity){char* str = new char[n + 1];//strcpy(str, _str);memcpy(str, _str, _size + 1);delete[] _str;_str = str;_capacity = n;}
}

3.4 任意位置插入字符

思路:將pos位置之后的字符依次向后挪動,然后把字符插入到pos位置。

void string::insert(size_t pos, char ch)
{assert(pos <= _size);if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}//挪動數據size_t end = _size;while (end >= pos){_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;
}

看似沒有任何問題,但是有一個特殊的例子就會出現bug。

可以看到,頭插一個字符之后,程序運行崩潰了。因為這里的end是size_t類型的,也就是無符號整型,但是pos==0,也就是說end不可能小于pos,循環會一直進行,出現越界的情況。那是不是我們把end的類型改成int就可以了呢?

程序仍然崩潰了,這又是為什么呢?因為pos是size_t的類型,當end和pos進行比較時會發生整型提升。也就是int轉換為unsigned_int繼續比較,也就是說,雖然我們寫成了int類型,但是沒有任何用。一種解決辦法是把pos強制轉換成int類型,這里給出第二種辦法,把end初始值賦值為_size+1.

void string::insert(size_t pos, char ch)
{assert(pos <= _size);if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}//挪動數據/*int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];end--;}*/size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;
}

3.5 任意位置插入字符串

和上面思想類似,就不做過多贅述了。

void string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}//挪動數據/*int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}*/size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}for (size_t i = 0;i < len;i++){_str[pos + i] = str[i];}_size += len;
}

3.6 查找字符串的子串

查找子串的方法有很多(比如KMP算法),這里就直接調用C++庫里面的strstr函數來完成。strstr返回的是一個const char*的指針,如果沒有找到就返回npos,找到了就用該位置的指針 - 字符串開始位置的指針就是該位置的下標。

size_t string::find(const char* str, size_t pos) const
{const char* p1 = strstr(_str + pos, str);if (p1 == nullptr){return npos;}else{return p1 - _str;}
}

類似地,我們也可以實現提取字符串中的子串的代碼。這兩個函數可以幫助我們解析網址。

string string::substr(size_t pos, size_t len) const
{if (len == npos || len >= _size - pos){len = _size - pos;}string ret;ret.reserve(len);for (size_t i = 0;i < len;i++){ret += _str[pos + i];}return ret;
}

注意,這里ret是一個臨時變量,作為返回值時要有拷貝構造函數,否則就是編譯器默認生成的淺拷貝,會導致程序運行出錯。(關于拷貝構造的實現,見后文)

void split_url(const string& url)
{size_t i1 = url.find(':');if (i1 != string::npos){cout << url.substr(0, i1) << endl;}size_t i2 = i1 + 3;size_t i3 = url.find('/', i2);if (i3 != string::npos){cout << url.substr(i2, i3 - i2) << endl;cout << url.substr(i3 + 1) << endl;}cout << endl;
}void test_string4()
{string url1 = "https://legacy.cplusplus.com/reference/string/string/";string url2 = "https://legacy.cplusplus.com/reference/vector/vector/";split_url(url1);split_url(url2);
}

3.7 比較運算符重載

從兩個字符串下標為0的位置開始,一個字符一個字符地比較(比較它們的ASCII碼值),如果相等就繼續向后遍歷。重載了<和==運算符之后,其他的比較運算符重載實現就可以復用了。

// s1 < s2
// "hello"   "hello"   ->  flase
// "hellox"  "hello"   ->  false
// "hello"   "hellox"  ->  true
bool string::operator<(const string& s) const
{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] < s[i2]){return true;}else if (_str[i1] > s[i2]){return false;}else{++i1;++i2;}}return i2 < s._size;
}bool string::operator==(const string& s) const
{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] != s[i2]){return false;}else{++i1;++i2;}}return i1 == _size && i2 == s._size;
}bool string::operator<=(const string& s) const
{return *this < s || *this == s;
}bool string::operator>(const string& s) const
{return !(*this <= s);
}
bool string::operator>=(const string& s) const
{return !(*this < s);
}bool string::operator!=(const string& s) const
{return !(*this == s);
}

3.8 流提取運算符重載

輸入字符串的本質其實就是一個字符一個字符的讀取,當輸入到空格或者換行時,讀取結束,把讀取到的字符加到數組中去。按照這個思路,可以寫出如下代碼:

istream& operator>>(istream& in, string& s)
{char ch;in >> ch;while (ch != ' ' && ch != '\n'){s += ch;in >> ch;}return in;
}

這段代碼看似沒有問題,我們給上一個測試用例運行一下:

我們輸入了xxxxx和yyyyy,按理來說應該輸出xxxxx和yyyyy,但是控制臺并沒有給出輸出結果,說明我們重載的流提取運算符有問題。那么問題出在哪里呢?編譯器認為這里的空格和換行符相當于是字符和字符之間的分隔符,會自動忽略,所以出現了bug。因此這里不應該用in來輸入,在C語言中可以用getchar來讀取,在C++中的輸入流則是提供了成員函數get。修改之后的代碼如下:

istream& operator>>(istream& in, string& s)
{char ch = in.get();// in >> ch;while (ch != ' ' && ch != '\n'){s += ch;// in >> ch;ch = in.get();}return in;
}

那是不是這樣就行呢?我們接著用剛才那段測試用例測試一下:

還是有問題!輸出的字符串的前面多了“hello world”,這又是什么原因呢?因為我們這里的s1和s2對象原本就有值,重載的函數里面是直接在字符串的后面添加字符,所以出現了問題。因此,如果之前的string對象原本就有值的話,需要先清理一下。這里就需要再實現一個函數clear,把string對象的下標為0的位置重置成‘\0’,然后把_size重置成0就可以了。

void string::clear()
{_str[0] = '\0';_size = 0;
}
istream& operator>>(istream& in, string& s)
{s.clear();char ch = in.get();// in >> ch;while (ch != ' ' && ch != '\n'){s += ch;// in >> ch;ch = in.get();}return in;
}

這樣就沒有任何問題了。

3.9 讀取一行字符串

如果我們要一次讀取一行字符串,并且字符串中有空格的話,這時候用cin就行不通了。C++的標準庫string中提供了getline函數,這里我們也模擬實現一下。

原理思路和流提取運算符重載類似,就不再贅述了。

istream& getline(istream& in, string& str, char delim)
{str.clear();char ch = in.get();while (ch != delim){str += ch;ch = in.get();}return in;
}

可以看到,程序是正常運行的:

但是這樣寫還是有一些小的缺陷,如果我們輸入的字符串很長的話,那么底層就會不斷地擴容,影響效率并且還會造成一定空間的浪費。那么有沒有什么優化的方案呢?有的兄弟,有的。

我們開一個數組buff,大小為128(也可以是其他大小,根據實際情況而定),每次讀取到一個字符之后不要直接放在字符串str后面,而是先存儲到buff數組中。當buff數組存儲滿了之后,直接把buff數組里的值一次性全部加到str字符串里面。

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

3.10 拷貝構造(1)

拷貝構造的原理很簡單,開一樣大的空間,然后拷貝新數據,釋放舊空間并指向新空間。

傳統寫法如下:

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

下面給出另一種寫法(現代寫法)

3.11?拷貝構造(2)

按照前面的想法我們是自己開了一塊空間,但是這里我們不再自己申請空間,而是讓構造函數幫我們申請空間tmp。但是tmp里面的數據內容是我_str想要的,所以調用swap函數交換。

注意:這里不能調用C++算法庫里的swap進行交換,因為算法庫里的swap是創建了一個中間對象進行交換(如下圖所示),又需要調用拷貝構造和賦值運算符重載,就會陷入死循環!

算法庫里的swap函數

所以這里需要將內置類型進行交換(也就是把_str,_size 和 _capacity逐個交換)。初步代碼如下:

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

但是這里的交換代碼要經常調用,所以我們可以把它們提取出來,單獨實現一個成員函數swap用于交換。(可以看到,C++中的string也有一份屬于自己的swap成員函數,如下圖所示)

string自己的swap函數

但是這里我們自己實現swap函數時又會遇到問題,程序編譯時會有現在局部找swap函數,就會出現參數不匹配的情況,所以要在前面加上指定作用域std。完整代碼如下:

void string::swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}// s3(s1)
string::string(const string& s)
{string tmp(s._str);//this->swap(tmp);swap(tmp);
}

3.12?賦值運算符重載

和拷貝構造函數的實現類似,賦值運算符重載同樣要開一樣大的空間,然后拷貝新數據,釋放舊空間并指向新空間。傳統寫法代碼如下:

// s1 = s2
string& string::operator=(const string& s)
{if (this != &s){char* tmp = new char[s._capacity + 1];memcpy(tmp, s.c_str(), s._size + 1);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;
}
原理圖

同理,賦值運算符重載也可以用現代寫法:

string& string::operator=(const string& s)
{if (this != &s){string tmp(s);swap(tmp);}return *this;
}

甚至還可以寫得更簡潔一些:

// s1 = s2
string& string::operator=(string tmp)
{swap(tmp);return *this;
}

這里是傳值傳參,需要調用拷貝構造,也就是說這里的參數tmp就是s2的臨時拷貝,所以直接交換就可以了。

現代寫法與傳統寫法在效率上并沒有太大區別,因為都要進行深拷貝,但是現代寫法的代碼更簡潔,所以更推薦現代寫法。

3.13 swap函數

或許有人會疑惑,swap不是剛剛才實現過了嗎,為什么又要實現一遍?在官方的庫中,你會發現string有兩個swap函數,一個是成員函數,另一個是全局函數。

C++官方庫里的兩個swap函數

那為什么這樣設計呢?算法庫中也有一個swap(之前在3.11展示過),但是算法庫里的swap在底層是三次深拷貝,程序代價很大!但是string里的swap函數底層效率就高很多。

string的全局函數swap

算法庫中的swap參數是模版,而string的全局函數swap參數就是string類,當交換的是兩個string類型的對象時,編譯器會優先調用現成的函數(也就是參數是string類類型的swap)。所以C++庫的設計者就會在全局也設計一個swap函數,與算法庫中的swap構成重載。所以編譯器在調用swap時就會優先調用“成品”,而不會調用模版。

void swap(string& x, string& y)
{x.swap(y);
}

四、完整參考代碼

string.h

#pragma once
#include <iostream>
#include <string.h>
#include <assert.h>
using namespace std;namespace bit
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//string();string(const char* str = "");const char* c_str() const;~string();string(const string& s);//string& operator=(const string& s);string& operator=(string tmp);void swap(string& s);size_t size() const;char& operator[](size_t i);const char& operator[](size_t i) const;void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);void pop_back();string& insert(size_t pos, char ch);string& insert(size_t pos, const char* str);string& erase(size_t pos, size_t len = npos);size_t find(char ch, size_t pos = 0) const;size_t find(const char* str, size_t pos = 0) const;string substr(size_t pos, size_t len = npos) const;void clear();bool operator<(const string& s) const;bool operator<=(const string& s) const;bool operator>(const string& s) const;bool operator>=(const string& s) const;bool operator==(const string& s) const;bool operator!=(const string& s) const;private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;public:static const size_t npos;};ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);istream& getline(istream& in, string& str, char delim = '\n');void swap(string& x, string& y);
}

string.cpp

#define  _CRT_SECURE_NO_WARNINGS 1
#include "string.h"namespace bit
{const size_t string::npos = -1;string::iterator string::begin(){return _str;}string::iterator string::end(){return _str + _size;}string::const_iterator string::begin() const{return _str;}string::const_iterator string::end() const{return _str + _size;}/*string::string():_str(new char[1]{'\0'}),_size(0),_capacity(0){}*/string::string(const char* str):_size(strlen(str)){_capacity = _size;_str = new char[_size + 1];//strcpy(_str, str);memcpy(_str, str, _size + 1);}string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}// s2(s1)/*string::string(const string& s){_str = new char[s._capacity + 1];memcpy(_str, s.c_str(), s._size + 1);_size = s._size;_capacity = s._capacity;}*/void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// s3(s1)string::string(const string& s){string tmp(s._str);//this->swap(tmp);swap(tmp);}// s1 = s2/*string& string::operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];memcpy(tmp, s.c_str(), s._size + 1);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}*//*string& string::operator=(const string& s){if (this != &s){string tmp(s);swap(tmp);}return *this;}*/// s1 = s2string& string::operator=(string tmp){swap(tmp);return *this;}const char* string::c_str() const{return _str;}size_t string::size() const{return _size;}char& string::operator[](size_t i){assert(i < _size);return _str[i];}const char& string::operator[](size_t i) const{assert(i < _size);return _str[i];}void string::reserve(size_t n){if (n > _capacity){char* str = new char[n + 1];//strcpy(str, _str);memcpy(str, _str, _size + 1);delete[] _str;_str = str;_capacity = n;}}void string::push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}//strcpy(_str + _size, str);memcpy(_str + _size, str, len + 1);_size += len;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::pop_back(){assert(_size > 0);--_size;_str[_size] = '\0';}string& string::insert(size_t pos, char ch){assert(pos <= _size);if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}//挪動數據/*int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];end--;}*/size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;return *this;}string& string::insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}//挪動數據/*int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}*/size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}for (size_t i = 0;i < len;i++){_str[pos + i] = str[i];}_size += len;return *this;}string& string::erase(size_t pos, size_t len){assert(pos < _size);//要刪除的數據大于pos后面的字符個數//pos后面全刪if (len == npos || len >= (_size - pos)){_size = pos;_str[_size] = '\0';}else{size_t i = pos + len;memmove(_str + pos, _str + i, _size + 1 - i);_size -= len;}return *this;}size_t string::find(char ch, size_t pos) const{for (size_t i = pos;i < _size;i++){if (ch == _str[i]){return i;}}return npos;}size_t string::find(const char* str, size_t pos) const{const char* p1 = strstr(_str + pos, str);if (p1 == nullptr){return npos;}else{return p1 - _str;}}string string::substr(size_t pos, size_t len) const{if (len == npos || len >= _size - pos){len = _size - pos;}string ret;ret.reserve(len);for (size_t i = 0;i < len;i++){ret += _str[pos + i];}return ret;}// s1 < s2// "hello"   "hello"   ->  flase// "hellox"  "hello"   ->  false// "hello"   "hellox"  ->  truebool string::operator<(const string& s) const{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] < s[i2]){return true;}else if (_str[i1] > s[i2]){return false;}else{++i1;++i2;}}return i2 < s._size;}bool string::operator==(const string& s) const{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] != s[i2]){return false;}else{++i1;++i2;}}return i1 == _size && i2 == s._size;}bool string::operator<=(const string& s) const{return *this < s || *this == s;}bool string::operator>(const string& s) const{return !(*this <= s);}bool string::operator>=(const string& s) const{return !(*this < s);}bool string::operator!=(const string& s) const{return !(*this == s);}void string::clear(){_str[0] = '\0';_size = 0;}ostream& operator<<(ostream& out, const string& s){//out << s.c_str();for (size_t i = 0;i < s.size();i++){out << s[i];}return out;}istream& operator>>(istream& in, string& s){s.clear();char buff[128];int i = 0;char ch = in.get();// in >> ch;while (ch != ' ' && ch != '\n'){//s += ch;buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}// in >> ch;ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}istream& getline(istream& in, string& str, char delim){str.clear();char ch = in.get();while (ch != delim){str += ch;ch = in.get();}return in;}void swap(string& x, string& y){x.swap(y);}
}

test.cpp

#define  _CRT_SECURE_NO_WARNINGS 1
#include "string.h"namespace bit
{void test_string1(){string s1;cout << s1.c_str() << endl;string s2("hello world");cout << s2.c_str() << endl;for (size_t i = 0;i < s2.size();i++){s2[i]++;}cout << s2.c_str() << endl;for (auto ch : s2){cout << ch << " ";}cout << endl;const string s3("hello world");string::const_iterator it3 = s3.begin();while (it3 != s3.end()){cout << *it3 << " ";++it3;}cout << endl;}void test_string2(){string s1("hello world");s1.push_back('x');cout << s1.c_str() << endl;s1.append(" hello bit");cout << s1.c_str() << endl;s1 += 'y';s1 += "zzzzzzz";cout << s1.c_str() << endl << endl;std::string s2("hello world");s2 += '\0';s2 += '\0';s2 += '!';cout << s2 << endl;cout << s2.c_str() << endl;s2 += "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";cout << s2 << endl;cout << s2.c_str() << endl;}void test_string3(){string s1("hello world");s1.insert(6, 'x');cout << s1 << endl;s1.insert(0, 'x');cout << s1 << endl << endl;string s2("hello world");s2.insert(6, "xxx");cout << s2 << endl;string s3("hello world");s3.erase(7);cout << s3 << endl;string s4("hello world");s4.erase(7, 100);cout << s4 << endl;string s5("hello world");s5.erase(7, 3);cout << s5 << endl;s5.pop_back();cout << s5 << endl;}void split_url(const string& url){size_t i1 = url.find(':');if (i1 != string::npos){cout << url.substr(0, i1) << endl;}size_t i2 = i1 + 3;size_t i3 = url.find('/', i2);if (i3 != string::npos){cout << url.substr(i2, i3 - i2) << endl;cout << url.substr(i3 + 1) << endl;}cout << endl;}void test_string4(){string url1 = "https://legacy.cplusplus.com/reference/string/string/";string url2 = "https://legacy.cplusplus.com/reference/vector/vector/";split_url(url1);split_url(url2);}void test_string5(){/*string s1("hello world"), s2("hello world");cout << s1 << " " << s2 << endl;cin >> s1 >> s2;cout << s1 << " " << s2 << endl;*/string s1;getline(cin, s1);cout << s1 << endl;}void test_string6(){string s1("hello"), s2("hellozyc");string s3(s1);cout << s1 << endl;cout << s3 << endl;s1[0] = 'x';cout << s1 << endl;cout << s3 << endl;s1 = s2;cout << s1 << endl;cout << s2 << endl;}void test_string7(){string s1("hello"), s2("hellozyc");swap(s1, s2);s1.swap(s2);}
}int main()
{bit::test_string7();return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96490.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96490.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96490.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【直接套模板】如何用 Web of Science 精準檢索文獻?

在文獻檢索的時候遇到一些問題&#xff0c;單獨使用關鍵詞檢索出來的文章數量太多&#xff0c;如果是多加一些限定詞&#xff0c;又什么都檢索不到&#xff1a;比如我明明知道某篇論文已經發表&#xff0c;但在 Web of Science (WoS) 里卻檢索不到。這其實和檢索式的寫法密切相…

HTTP 協議:從原理到應用的深度剖析

一、什么是HTTP協議&#xff1f;HTTP協議&#xff0c;全稱 Hyper Text Transfer Protocol&#xff08;超?本傳輸協議&#xff09;的縮寫&#xff0c;是?于服務器與客戶端瀏覽器之間傳輸超?本數據&#xff08;?字、圖?、視頻、?頻&#xff09;的應?層協議。它規定了客戶端…

【算法--鏈表】138.隨機鏈表的復制--通俗講解

算法通俗講解推薦閱讀 【算法–鏈表】83.刪除排序鏈表中的重復元素–通俗講解 【算法–鏈表】刪除排序鏈表中的重復元素 II–通俗講解 【算法–鏈表】86.分割鏈表–通俗講解 【算法】92.翻轉鏈表Ⅱ–通俗講解 【算法–鏈表】109.有序鏈表轉換二叉搜索樹–通俗講解 【算法–鏈表…

為什么現在企業注重數據可視化?一文講清可視化數據圖表怎么做

目錄 一、企業注重數據可視化的原因 1.提升數據理解效率 2.發現數據中的規律和趨勢 3.促進企業內部溝通與協作 4.增強決策的科學性 5.提升企業競爭力 二、可視化數據圖表的基本概念 1.常見的可視化圖表類型 2.可視化圖表的構成要素 3.可視化圖表的設計原則 三、制作…

Cursor 輔助開發:快速搭建 Flask + Vue 全棧 Demo 的實戰記錄

Cursor 輔助開發&#xff1a;快速搭建 Flask Vue 全棧 Demo 的實戰記錄 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般絢爛的技術棧中&#xff0c;我是那個永不停歇的色彩收集者。 &#x1f98b; 每一個優化都是我培育的花朵&#xff0c;每一個…

實戰:用 Python 搭建 MCP 服務 —— 模型上下文協議(Model Context Protocol)應用指南

&#x1f4cc; 實戰&#xff1a;用 Python 搭建 MCP 服務 —— 模型上下文協議&#xff08;Model Context Protocol&#xff09;應用指南 標簽&#xff1a;#MCP #AI工程化 #Python #LLM上下文管理 #Agent架構&#x1f3af; 引言&#xff1a;為什么需要 MCP&#xff1f; 在構建大…

宋紅康 JVM 筆記 Day16|垃圾回收相關概念

一、今日視頻區間 P154-P168 二、一句話總結 System.gc()的理解&#xff1b;內存溢出與內存泄漏&#xff1b;Stop The World;垃圾回收的并行與并發&#xff1b;安全點與安全區域&#xff1b;再談引用&#xff1a;強引用&#xff1b;再談引用&#xff1a;軟引用&#xff1b;再談…

OpenCV 高階 圖像金字塔 用法解析及案例實現

目錄 一、什么是圖像金字塔&#xff1f; 二、圖像金字塔的核心作用 三、圖像金字塔的核心操作&#xff1a;上下采樣 3.1 向下采樣&#xff08; pyrDown &#xff09;&#xff1a;從高分辨率到低分辨率 1&#xff09;原理與步驟 2&#xff09;關鍵注意事項 3&#xff09;…

【ARMv7】系統復位上電后的程序執行過程

引子&#xff1a;對于ARMv7-M系列SOC來說&#xff0c;上電后程序復位執行的過程相對來說比較簡單&#xff0c;因為絕大部分芯片&#xff0c;都是XIP&#xff08;eXecute In Place&#xff0c;就地執行&#xff09;模式執行程序&#xff0c;不需要通過BooROM->PL(preloader)-…

神經網絡的初始化:權重與偏置的數學策略

在深度學習中&#xff0c;神經網絡的初始化是一個看似不起眼&#xff0c;卻極其重要的環節。它就像是一場漫長旅程的起點&#xff0c;起點的選擇是否恰當&#xff0c;往往決定了整個旅程的順利程度。今天&#xff0c;就讓我們一起深入探討神經網絡初始化的數學策略&#xff0c;…

第 16 篇:服務網格的未來 - Ambient Mesh, eBPF 與 Gateway API

系列文章:《Istio 服務網格詳解》 第 16 篇:服務網格的未來 - Ambient Mesh, eBPF 與 Gateway API 本篇焦點: 反思當前主流 Sidecar 模式的挑戰與權衡。 深入了解 Istio 官方的未來演進方向:Ambient Mesh (無邊車模式)。 探討革命性技術 eBPF 將如何從根本上重塑服務網格的…

擺動序列:如何讓數組“上下起伏”地最長?

文章目錄摘要描述題解答案題解代碼分析代碼解析示例測試及結果時間復雜度空間復雜度總結摘要 今天我們要聊的是 LeetCode 第 376 題 —— 擺動序列。 題目的意思其實很有意思&#xff1a;如果一個序列里的相鄰差值能保持正負交替&#xff0c;就叫做“擺動”。比如 [1, 7, 4, 9…

玩轉Docker | 使用Docker部署KissLists任務管理工具

玩轉Docker | 使用Docker部署KissLists任務管理工具 前言 一、KissLists介紹 KissLists簡介 KissLists核心特點 KissLists注意事項 二、系統要求 環境要求 環境檢查 Docker版本檢查 檢查操作系統版本 三、部署KissLists服務 下載KissLists鏡像 編輯部署文件 創建容器 檢查容器狀…

【滑動窗口】C++高效解決子數組問題

個人主頁 &#xff1a; zxctscl 專欄 【C】、 【C語言】、 【Linux】、 【數據結構】、 【算法】 如有轉載請先通知 文章目錄前言1 209. 長度最小的子數組1.1 分析1.2 代碼2 3. 無重復字符的最長子串2.1 分析2.2 代碼3 1004. 最大連續1的個數 III3.1 分析3.2 代碼4 1658. 將 x …

[rStar] 搜索代理(MCTS/束搜索)

第2章&#xff1a;搜索代理(MCTS/束搜索) 歡迎回到rStar 在前一章中&#xff0c;我們學習了求解協調器&#xff0c;它就像是解決數學問題的項目經理。 它組織整個過程&#xff0c;但本身并不進行"思考"&#xff0c;而是將這項工作委托給其專家團隊。 今天&#x…

Electron 核心模塊速查表

為了更全面地覆蓋常用 API&#xff0c;以下表格補充了更多實用方法和場景化示例&#xff0c;同時保持格式清晰易讀。 一、主進程模塊 模塊名核心用途關鍵用法 示例注意事項app應用生命周期管理? 退出應用&#xff1a;app.quit()? 重啟應用&#xff1a;app.relaunch() 后需…

Qt C++ 圖形繪制完全指南:從基礎到進階實戰

Qt C 圖形繪制完全指南&#xff1a;從基礎到進階實戰 前言 Qt框架提供了強大的2D圖形繪制能力&#xff0c;通過QPainter類及其相關組件&#xff0c;開發者可以輕松實現各種復雜的圖形繪制需求。本文將系統介紹Qt圖形繪制的核心技術&#xff0c;并通過實例代碼演示各種繪制技巧…

二分搜索邊界問題

在使用二分搜索的時候&#xff0c;更新條件不總是相同&#xff0c;雖然說使用bS目的就是為了target&#xff0c;但也有如下幾種情況&#xff1a;求第一個target的索引求第一個>target的索引求第一個>target的索引求最后一個target的索引求最后一個<target的索引求最后…

【springboot+vue3】博客論壇管理系統(源碼+文檔+調試+基礎修改+答疑)

目錄 一、整體目錄&#xff1a; 項目包含源碼、調試、修改教程、調試教程、講解視頻、開發文檔&#xff08;項目摘要、前言、技術介紹、可行性分析、流程圖、結構圖、ER屬性圖、數據庫表結構信息、功能介紹、測試致謝等約1萬字&#xff09; 二、運行截圖 三、代碼部分&…

20250907_梳理異地備份每日自動巡檢Python腳本邏輯流程+安裝Python+PyCharm+配置自動運行

一、邏輯流程(autocheckbackup.py在做什么) 1.連接Linux服務器 用 paramiko 登錄你配置的 Linux 服務器(10.1.3.15, 10.1.3.26),進入指定目錄(如 /home, /backup/mes),遞歸列出文件。 采集到的信息:服務器IP、目錄、數據庫名稱、文件名、大小、修改時間。 2.連接Wind…