一、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;
}
運行代碼之后我們發現,程序崩潰了!

那為什么程序會崩潰呢?因為這里的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是創建了一個中間對象進行交換(如下圖所示),又需要調用拷貝構造和賦值運算符重載,就會陷入死循環!

所以這里需要將內置類型進行交換(也就是把_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成員函數,如下圖所示)

但是這里我們自己實現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函數,一個是成員函數,另一個是全局函數。

那為什么這樣設計呢?算法庫中也有一個swap(之前在3.11展示過),但是算法庫里的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;
}