目錄
- 1. 簡易string(無增刪查改)
- 1.1 深淺拷貝
- 2. string的傳統寫法
- 2.1 四個默認成員函式
- 3. string的現代寫法
- 3.1 建構式與解構式
- 3.2 拷貝構造
- 3.3 賦值多載
- 3.4 std中的swap與string中的swap
- 4. 修改
- 5. 增加
- 6. 洗掉
- 7. 查找
- 8. 多載的全域函式
- 最終代碼
1. 簡易string(無增刪查改)
開始必須重新定義一個命名空間,把我們自定義的string放進命名空間里,
假如不考慮增刪查改,我們先可以做一個簡單的string,幾乎下意識的就寫出了這樣的建構式
string(char* str)
:_str(str)
{}
這是非常不合理的,假如外面傳入的是一個常量字串,
string s("1234");
假如要修改常量字串的話,會直接中斷,
那我們就開一個和他同樣大小的空間,再把字符拷貝進去,使用new開辟后面還可以動態增長,
namespace zjn
{
class string
{
public:
string(char* str)
:_str(new char[strlen( str)+1])
{
strcpy(_str, str);
}
~string()
{
delete[] _str;
}
private:
char* _str;
};
}
有時我們也會定義無參的string,那么這樣寫可以嗎,我們配合一個介面c_str進行驗證
string()
:_str(nullptr)
{
;
}
const char* c_str()
{
return _str;
}
int main()
{
zjn::string s1("1234");
zjn::string s2;
cout << s1.c_str() << endl;
cout << s2.c_str ()<< endl;
return 0;
}
cout是自動識別型別,是因為他多載了各種型別的函式,列印的原理無非就是遇到‘\0’停止,可是在s2由于我們構造的時候使用nullptr構造的,所以cout會解參考進行訪問,就會崩潰,(解構式時delet[]所包含的free(NULL)并不會報錯)

所以正確的做法應該是
string()
:_str(new char[1])
{
_str[0] = '\0';
}
其實只要寫成默認建構式就好了,這樣就不用寫兩個建構式
string(char* str="")
:_str(new char[strlen( str)+1])
{
strcpy(_str, str);
}
1.1 深淺拷貝
在學習拷貝構造的時候,我們講到一個stack物件的拷貝,自動生成的建構式只是淺拷貝,兩個指標指向的是同一塊區域,所以執行解構式的時候會將同一塊資源釋放兩次,


所以默認生成的滿足不了需求,必須自己寫一個拷貝建構式來實作深拷貝,


#include<iostream>
#include<assert.h>
#pragma warning(disable:4996)
using namespace std;
namespace zjn
{
class string
{
public:
string(char* str="")
:_str(new char[strlen( str)+1])
{
strcpy(_str, str);
}
~string()
{
delete[] _str;
_str = nullptr;
}
const char* c_str()
{
return _str;
}
string(const string& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str,s._str);
}
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
//釋放原空間,然后深拷貝
string& operator=(const string& s)
{
if (this!=&s)
{
delete[] _str;
//不能直接strcpy因為空間可能不夠
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
private:
char* _str;
};
}
int main()
{
zjn::string s1("1234");
zjn::string s2;
zjn::string s3(s1);
s3[1] = 'x';
cout << s1.c_str() << endl;
cout << s2.c_str ()<< endl;
return 0;
}
2. string的傳統寫法
一個真正的string是要支持增刪查改的,所以必須要有size和capcacity,
size和capacity是內置型別幾乎不需要注意什么地方
2.1 四個默認成員函式
/*string(char* str)
:_str(new char[strlen(str)+1])
,_size(strlen( str))
, _capacity(_size)
{}*/
string(char* str="")
{
_str=new char[strlen(str)+1];
_size = strlen(str);
_capacity = _size;
strcpy(_str, str);
}
string(const string& s)
{
_size = s._size;
_capacity = s._capacity;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
~string()
{
delete[] _str;
_str = nullptr;
}
string& operator=(const string& s)
{
if (this != &s){
delete[] str;
_str = new char[strlen(s._str) + 1];
_capacity = s._capacity;
_size = s._size;
}
return *this;
}
- 默認建構式:當默認建構式選擇為為引數串列宣告時,最好不要寫有依賴關系的,因為實際初始化的順序只和宣告的順序有關,所以我們直接選擇寫在函式體內
- 拷貝建構式:需要深拷貝實作,即重新開一段空間,把資料拷貝過來
- 多載賦值函式:需要注意自己給自己賦值的情況,釋放原空間進行深拷貝,假如是自己給自己賦值的話,原空間釋放,指標指向新開的一段空間,strlen時遇到’\0’停止,那段空間是隨機值,
3. string的現代寫法
3.1 建構式與解構式
string(const char* str="")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size=0;
}
- 建構式與解構式沒有什么差別,
3.2 拷貝構造
一開始是這么寫的
string(const string& s)
{.
//區域物件擁有相同的str,用于和this._str進行交換
string temp(s._str);
std::swap(_str,temp._str);
std::swap(_size,temp._size);
std::swap(_capacity,temp._capacity);
}
很明顯是有錯誤的,因為_str沒有初始化是一個野指標,當與temp._str進行交換時,_str指向temp._str那塊空間,temp._str指向那塊隨機空間,當出了函式體,呼叫解構式,清理資源_str,free掉temp._str,就會報錯,即free了一個野指標,
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string temp(s._str);
std::swap(_str,temp._str);
std::swap(_size,temp._size);
std::swap(_capacity,temp._capacity);
}
那么我們將它初始化成nullptr就好了,
3.3 賦值多載
string& operator=(const string& s)
{
if (this != &s)
{
string temp(s._str);
std::swap(_str,temp._str);
std::swap(_size,temp._size);
std::swap(_capacity,temp._capacity);
}
return *this;
}

交換了之后,出了函式體,呼叫析構,剛好也會清理掉不要的"worldx"那塊資源,
其實還有一種更簡潔的寫法
//引數不能加const因為我們會修改s,不能傳參考因為我們修改了s,傳參考外面的也會修改
string& operator=( string s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
return *this;
}
s就代替了之前的temp物件,且s也是一個區域物件,也會呼叫解構式,
可以看到存在了大量的swap代碼復用,我們可以將swap寫成一個函式,然后對所有代碼進行修改,
string(const char* str="")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
// std中的swap并不好
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size,s._size);
std::swap(_capacity, s._capacity);
}
//拷貝構造
//string s1(s2);
string(const string& s)
:_str(nullptr)
, _capacity(0)
, _size(0)
{
string temp(s._str);
swap(temp);
}
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size=0;
}
string& operator=( string s)
{
swap(s);
return *this;
}
這就是一個有資源管理的string的現代寫法,
3.4 std中的swap與string中的swap
那么為什么不直接呼叫庫中的swap交換兩個物件,而是用庫中的swap一個個交換屬性,最后將他們封裝成一個專門的swap呢?
swap(s1,s2);
s1.swap(s2);
這兩種差異是特別大的,
std中的swap是一個由函式模板推演而來的模板函式,其中經歷了一次拷貝構造,兩次賦值,代價太大,效率會大打折扣,

4. 修改
//[]多載
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
//[]多載(const物件呼叫)
const char& operator[](size_t i)const
{
assert(i < _size);
return _str[i];
}
//迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
//迭代器(const物件)
typedef const char* const_iterator;
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
//范圍for
for (auto& e : s)
{
e += 1;
}
- 多載[]:需要注意,i需要小于size,size作為下表的話是’\0’的位置,
- string 的迭代器實際就是一個指標,typedef為iterator是為了和其它介面保持一致,假如你是一個鏈表就不能繼續使用指標了,因為指標指向那個節點,解參考之后是一個結構體,并不是節點的值,所以在鏈表中迭代器實際是這樣定義的,
struct ListIterator
{
operator*();
operator++();
Node* node;
};
多載他的*和++來達到目的,
- 范圍for:會自動轉換為迭代器,也就是說自動呼叫begin(),與end(),
5. 增加
void resize(size_t n, char ch='\0')
{
//_size變小,capacity不變,直接在n處放'\0'
if (n<_size)
{
_str[n] ='\0';
_size = n;
}
else
{
if (n>_capacity)
{
reserve(n);
}
//1 2 3 4
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
}
//增加就可能會引起擴容
void reserve(int n)
{
if (n > _capacity)
{
//需要temp不然直接delete的話字串的內容會丟失
char* temp = new char[n + 1];
strcpy(temp, _str);
delete[] _str;
_str = temp;
_capacity = n;
}
}
//字符尾插
string& push_back(char ch)
{
if (_size == _capacity)
{
reserve(2 * _capacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
return *this;
}
//字串拼接
void append(const char* str)
{
int len = strlen(str);
if (len + _size> _capacity)
{
reserve(len + _size);
}
//忘記strcpy,引數忘記+_size
strcpy(_str+_size, str);
_size += len;
}
//字符+=
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
//字串+=
string& operator+=(const char* str)
{
append(str);
return *this;
}
//string物件+=
string& operator+=(const string& s)
{
append(s._str);
return *this;
}
//插入字符
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 8 : 2 * _capacity;
reserve(newcapacity);
}
/* 邏輯沒有問題,但是當pos等于0時,要把第一位往前挪,end--就會變成-1,才能由于型別為size_t他就會變為整形最大值,
假如把end變為int型別也避免不了,因為pos為size_t,身為int型別的end會型別提升為size_t,依舊是整形最大值,
那把end變成int型別,再把pos顯示的轉為int(pos不要直接改成int與庫不相符),這樣是可以的,
size_t end = _size;
while(end>=pos)
{
_str[end + 1] = _str[end];
end--;
}*/
//雖然_size是'\0',但是_size+1不會越界因為假如空間不夠前面會擴容,陣列是有這么大空間的,只不過沒有存字符,
//這樣當pos等于0時,end為1字符已經全部挪走,end--為0,此時不會再進入回圈,也就不會變成-1了,
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
//插入字串
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + strlen(str) > _capacity)
{
reserve(_size+len);
}
int end = _size + len;
//畫圖分析
//避免型別提升
while (end >= (int)pos+len)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
- reserve:只要增加就可能會有擴容的場景,
- push_back:追加一個字符
- append:拼接一個字串,
- +=:分別有字符,字串,string三種函式
- insert:push_back,append,+=可以復用這個插入函式,但是要注意型別提升問題,
6. 洗掉
//洗掉,和庫類似,我們給一個npos預設值
void erase(size_t pos, size_t len = npos)
{
//從pos起,洗掉len個字符,
assert(pos < _size);
//len為npos的時候會溢位
//if (pos+len>_size)
//
//假如要求刪的超過字串,a b c d,要求從c洗掉100個,就把d置成'\0'就行了,
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//直接拷過去,然后覆寫,'\0'也會拷貝過去
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
- erase,注意溢位,直接使用strcpy覆寫,
7. 查找
//查找字符,預設默認從第一個位置開始
size_t find(char ch, size_t pos = 0)
{
for (size_t i = 0; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
//查找子串
size_t find(const char* sub, size_t pos = 0)
{
//在_str+pos這個字串里找sub
const char* ret = strstr(_str + pos, sub);
if (nullptr == ret)
{
return npos;
}
else
{
return ret - _str;
}
}
- find: 一個查找字符,一個查找子串,預設值都是從第一個位置開始查找
8. 多載的全域函式
//大于多載
bool operator>(const string& s1, const string& s2)
{
size_t l1=0, l2 = 0;
while (l1 < s1.size() && l2 < s2.size())
{
if (s1[l1] == s1[l2])
{
l1++;
l2++;
}
else
{
if (s1[l1]>s2[l2])
{
return true;
}
else
{
return false;
}
}
}
//假如有一個長一個短
if (l1 < s1.size())
{
return true;
}
else if (l2 < s2.size())
{
return false;
}
else
{
return false;
}
}
//==多載
bool operator==(const string& s1, const string& s2)
{
size_t l1 = 0, l2 = 0;
while (l1 < s1.size() && l2 < s2.size())
{
if (s1[l1] == s1[l2])
{
l1++;
l2++;
}
else
{
return false;
}
}
//假如有一個長一個短
if (l1 < s1.size())
{
return false;
}
else if (l2 < s2.size())
{
return false;
}
else
{
return true;
}
}
//輸出
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i] ;
}
return out;
}
//輸入
std::istream& operator>>(std::istream& in, string& s)
{
char ch;
while (1)
{
//這樣不行,默認空格為下一個,不能123這樣輸,只能1 2 3
//in >> ch;
in.get(ch);
if ( ch==' '||ch=='\n')
{
break;
}
else
{
s += ch;
}
}
return in;
}
最終代碼
#include<iostream>
#include<assert.h>
#pragma warning(disable:4996)
namespace zjn
{
class string
{
public:
//傳統寫法
構造
//string(const char* str = "")
//{
// _size = strlen(str);
// _capacity = _size;
// _str = new char[_capacity + 1];
// strcpy(_str, str);
//}
析構
//~string()
//{
// delete[] _str;
// _str = nullptr;
// _capacity = _size = 0;
//}
拷貝構造
//string(const string& s)
//{
// _str = new char[strlen(s._str) + 1];
// strcpy(_str, s._str);
// _capacity = s._capacity;
// _size = s._size;
//}
賦值多載
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// delete[] _str;
// _str = new char[strlen(s._str) + 1];
// strcpy(_str, s._str);
// _size = s._size;
// _capacity = s._capacity;
// }
// return *this;
//}
//構造
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
//std中的swap并不好
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//拷貝構造
//string s1(s2);
string(const string& s)
:_str(nullptr)
, _capacity(0)
, _size(0)
{
string temp(s._str);
swap(temp);
}
//析構
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size = 0;
}
//賦值
string& operator=(string s)
{
swap(s);
return *this;
}
size_t size()const
{
return _size;
}
//多載[]
//回傳值忘記參考了
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
const char& operator[](size_t i)const
{
assert(i < _size);
return _str[i];
}
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
typedef const char* const_iterator;
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
//增
//擴容+初始化,
//當前n<capacity的話,不改變capacity,只改變size,n>capacity,改變size改變capacity,
void resize(size_t n, char ch='\0')
{
//_size變小,capacity不變,直接在n處放'\0'
if (n<_size)
{
_str[n] ='\0';
_size = n;
}
else
{
if (n>_capacity)
{
reserve(n);
}
//1 2 3 4
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
}
//擴容
void reserve(size_t n)
{
if (n > _capacity)
{
//需要temp不然直接delete的話字串的內容會丟失
char* temp = new char[n + 1];
strcpy(temp, _str);
delete[] _str;
_str = temp;
_capacity = n;
}
}
//字符尾插
void push_back(char ch)
{
/*if (_size == _capacity)
{
reserve(2 * _capacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';*/
insert(_size, ch);
}
void append(const char* str)
{
//int len = strlen(str);
//if (len + _size> _capacity)
//{
// reserve(len + _size);
//}
忘記strcpy,引數忘記+_size
//strcpy(_str+_size, str);
//_size += len;
insert(_size, str);
}
//字符+=
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
//字串+=,引數忘記const
string& operator+=(const char* str)
{
append(str);
return *this;
}
//string物件+=
string& operator+=(const string& s)
{
append(s._str);
return *this;
}
//插入字符
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
//假如capacity為0一開始開8個物件型別(char)空間
size_t newcapacity = _capacity == 0 ? 8 : 2 * _capacity;
reserve(newcapacity);
}
/* 邏輯沒有問題,但是當pos等于0時,要把第一位往前挪,end--就會變成-1,才能由于型別為size_t他就會變為整形最大值,
假如把end變為int型別也避免不了,因為pos為size_t,身為int型別的end會型別提升為size_t,依舊是整形最大值,
那把end變成int型別,再把pos顯示的轉為int(pos不要直接改成int與庫不相符),這樣是可以的,
size_t end = _size;
while(end>=pos)
{
_str[end + 1] = _str[end];
end--;
}*/
//雖然_size是'\0',但是_size+1不會越界因為假如空間不夠前面會擴容,陣列是有這么大空間的,只不過沒有存字符,
//這樣當pos等于0時,end為1字符已經全部挪走,end--為0,此時不會再進入回圈,也就不會變成-1了,
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
//插入字串
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + strlen(str) > _capacity)
{
reserve(_size + len);
}
int end = _size + len;
//畫圖分析
//避免整形提升
while (end >= (int)pos + len)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
}
//洗掉,和庫類似,我們給一個npos預設值
void erase(size_t pos, size_t len = npos)
{
//從pos起,洗掉len個字符,
assert(pos < _size);
//len為npos的時候會溢位
//if (pos+len>_size)
//
//假如要求刪的超過字串,a b c d,要求從c洗掉100個,就把d置成'\0'就行了,
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//直接拷過去,然后覆寫,'\0'也會拷貝過去
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
//查找字符,預設默認從第一個位置開始
size_t find(char ch, size_t pos = 0)
{
for (size_t i = 0; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
//查找子串
size_t find(const char* sub, size_t pos = 0)
{
//在_str+pos這個字串里找sub
const char* ret = strstr(_str + pos, sub);
if (nullptr == ret)
{
return npos;
}
else
{
return ret - _str;
}
}
private:
char* _str;
size_t _capacity;
size_t _size;
//宣告
static const size_t npos;
};
//定義初始化
const size_t string::npos = -1;
//大于多載
bool operator>(const string& s1, const string& s2)
{
size_t l1=0, l2 = 0;
while (l1 < s1.size() && l2 < s2.size())
{
if (s1[l1] == s1[l2])
{
l1++;
l2++;
}
else
{
if (s1[l1]>s2[l2])
{
return true;
}
else
{
return false;
}
}
}
//假如有一個長一個短
if (l1 < s1.size())
{
return true;
}
else if (l2 < s2.size())
{
return false;
}
else
{
return false;
}
}
//==多載
bool operator==(const string& s1, const string& s2)
{
size_t l1 = 0, l2 = 0;
while (l1 < s1.size() && l2 < s2.size())
{
if (s1[l1] == s1[l2])
{
l1++;
l2++;
}
else
{
return false;
}
}
//假如有一個長一個短
if (l1 < s1.size())
{
return false;
}
else if (l2 < s2.size())
{
return false;
}
else
{
return true;
}
}
//輸出
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i] ;
}
return out;
}
//輸入
std::istream& operator>>(std::istream& in, string& s)
{
//防止對一個原本就有資料的物件輸入
s.resize(0);
char ch;
while (1)
{
//這樣不行,默認空格為下一個,不能123這樣輸,只能1 2 3
//in >> ch;
in.get(ch);
if ( ch==' '||ch=='\n')
{
break;
}
else
{
s += ch;
}
}
return in;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/271435.html
標籤:區塊鏈
下一篇:今天早上跑通了 PF-AFN!
