網站首頁 編程語言 正文
前言
在C語言中,沒有專門用來表示字符串的類型。C語言的字符串是一系列以’\0’為結尾的字符的集合。雖然C語言為這樣的字符串提供了一系列的庫函數如strcpy, strcmp等等,但這些函數與字符串這個類型是分開的,這不太符合C++中面試對象的思想,所以在C++中封裝了一個string類,來幫助我們操作字符串。string該如何使用,我這里就不做贅述了,大家可以去看看官方文檔呀
string - C++ Reference (cplusplus.com)
string模擬實現
string簡單實現
首先我們不考慮string類的增刪查改,只是先給string類搭建一個最簡單的框架出來。
和C語言中相同,為了存儲一個字符串,我們的string類需要一個char*的指針來指向字符像這個對象。作為一個對象,string還需要有構造函數,析構函數和拷貝構造。
class string
{
private:
char *_str;
public:
string(const char *str)
: _str(new char[strlen(str) + 1]) // +1 是給'\0'留出位置
{
strcpy(_str, str);
}
string(const string &str)
: _str(new char[strlen(str._str) + 1])
{
strcpy(_str, str._str);
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
};
有的朋友可能會疑惑,這里的構造函數和拷貝構造函數為什么不用編譯器自動生成的,直接將_str指向原本的字符串就可以了,為什么還要開辟空間呢?
這是因為我們在日常使用中,假如有兩個string類 a 和 b,b是由a拷貝構造而來,一般情況下我們在修改b的同時不希望a也被改。此外,如果直接將_str指向原本的字符串會導致的問題是當 a 和 b用完被銷毀時,會對同一片空間調用兩次析構函數,對同一片空間釋放兩次。所以在這里,我們需要重新開辟一片空間來給這個string。這也就是所謂的深拷貝。
然后,為了訪問string類中的元素,我們需要對運算符[]進行重載。
char& operator[](size_t pos)
{
assert(pos < strlen())
return _str[pos];
}
這樣我們就實現了一個簡單的string類。
string完整實現
構造函數,析構函數,拷貝構造
之前我們實現的一個string類是一個最簡單的string類,它沒有辦法進行增刪查改,接下來我們就來一點一點完善它。
要實現增刪查改,我們還需要兩個變量,一個記錄string類當前長度,一個記錄string類的容量大小。加入這兩個變量后,我們原本的構造函數,拷貝構造和析構函數需要發生一點點變化。
class string
{
private:
char *_str;
size_t _size;
size_t _capacity;
public:
string(const char *str = "")
: _size(strlen(str)), _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string &str)
: _size(str._size), _capacity(str._capacity)
{
_str = new char[_size + 1];
strcpy(_str, str._str);
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
};
運算符重載
接下來我們來實現一下,string類的運算符。在實現運算符重載時,我們需要做的只是實現少數幾個運算符即可,其他的運算符可復用前面實現的運算符來達到我們想要的效果。
//關系運算符的重載
bool operator>(const string &s)
{
return strcmp(_str, s.c_str());
}
bool operator==(const string &s)
{
return strcmp(_str, s.c_str()) == 0;
}
bool operator!=(const string &s)
{
return !(*this == s);
}
bool operator>=(const string &s)
{
return *this > s || *this == s;
}
bool operator<(const string &s)
{
return !(*this >= s);
}
bool operator<=(const string &s)
{
return !(*this > s);
}
//操作運算符的重載
string &operator=(string& str)
{
if(*this != str)
{
char *tmp = new char[str._capacity + 1];
strcpy(tmp,str._str);
delete[] _str;
_str = tmp;
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
char &operator[](size_t pos)
{
assert(pos < _size);
return *(_str + pos);
}
const char &operator[](size_t pos) const
{
assert(pos < _size);
return *(_str + pos);
}
string接口實現
首先是比較簡單的size(),empty(),capacity(),clear()。這些接口大部分直接訪問string類的成員變量就可以得到結果。
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
bool empty() const
{
return 0 == _size;
}
//后面添加const的目的是為了讓所有對象都可以進行訪問
void clear()
{
_str[0] = '\0';
_size = 0;
_capacity = 0;
}
因為后面的接口大部分都需要進行空間的調整,所以首先我們將調整空間的接口,reserve和resize實現。
void reserve(size_t n)
{
if (n > _capacity) //判斷是否需要擴容
{
char *tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//resize和reserve的區別在于,reserve只是開空間,而resize還要進行初始化
void resize(size_t n, char c = '\0')
{
if (n > _capacity)
{
reserve(n); //開空間復用reserve
}
for (size_t i = _size; i < n; ++i)
{
_str[i] = c;
}
_size = n;
_str[_size] = '\0';
}
接下來是插入的實現,首先是push_back,這個比較簡單,找到尾部進行插入即可。
void push_back(char n)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2); //開空間復用reserve
}
_str[_size++] = n;
_str[_size] = '\0';
}
接下來是insert,這個較push_back而言要麻煩一些,因為除了尾插,其他地方去插入數據你都需要挪動后面數據的位置。
string &insert(size_t pos, const char *str)
{
//檢查空間是否足夠
assert(pos <= _size);
size_t len = strlen(str);
if (len + _size > _capacity)
{
reserve(len + _size);
}
//挪動后面的數據
size_t end = _size + len;
while (end != pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
//數據插入
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
寫完了插入,接下來當然就是刪除接口:eraser
string &eraser(size_t pos, size_t len = npos) //npos為靜態變量,值為-1
{
assert(pos < _size);
if (len == npos || pos + len >= _size) //將位置后的元素全部刪除
{
_str[pos] = '\0';
_size = pos;
}
else //刪除位置后的部分元素
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
begin++;
}
_size = _size - len;
}
return *this;
}
迭代器的實現
C++中的迭代器和指針類似。為什么要有迭代器呢?因為C++中有各種各樣的容器,每個容器它背后的存儲方式不同,訪問方式也不同,為了讓使用者的使用成本降低,使大部分容器可以以相同的方式去訪問,就有了迭代器的產生。
接下來我們來實現string的迭代器,其實string的迭代器就是一個指針。并不用去封裝特別的東西。
typedef char *iterator;
typedef const char *const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
部分函數優化和完善
前面在寫運算符重載時,還有部分運算符未重載在此加上
string &operator+=(const char *str)
{
append(str);
}
string &operator+=(char n)
{
push_back(n);
return *this;
}
同時增加拷貝構造和operator=的現代寫法,之前我們寫拷貝構造和operator=時都需要自己去重新開空間,那么這個活可不可以讓其他人幫我做呢?
我們來看看下面這一段代碼
void swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
string(const string &s)
: _str(nullptr), _size(0), _capacity(0)
{
string tmp(s._str);
swap(tmp);
}
string &operator=(string s)
{
swap(s);
return *this;
}
上述代碼同樣可以幫我們完成拷貝構造和operator= ,原理如下:
1.首先是拷貝構造,我們在拷貝構造中使用構造函數去創建一個臨時對象,這個臨時對象在創建時,就幫我們開辟了空間。然后我們將臨時對象和此對象的所有成員進行一個交換,這樣此對象就可以接管臨時對象創建的那塊空間,我們的拷貝構造也就成功了
2.在operator=這,我們使用的是傳值傳參。好處在于由于我們的string類是自定義對象,所以在傳參時會去調用拷貝構造,這樣傳過來的str參數也擁有了自己的空間,此時我們和拷貝構造一樣,將str所開辟的那塊空間接管,同時由于str是函數參數,當函數結束時,str會去調用析構函數進行一個空間釋放。
完整代碼
class string
{
public:
typedef char *iterator;
typedef const char *const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string(const char *s = "")
: _size(strlen(s)),
_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, s);
}
string(const string &s)
: _str(nullptr),
_size(0),
_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
string &operator=(string s)
{
swap(s);
return *this;
}
char &operator[](size_t pos)
{
assert(pos < _size);
return *(_str + pos);
}
const char &operator[](size_t pos) const
{
assert(pos < _size);
return *(_str + pos);
}
const char *c_str() const
{
return _str;
}
void reserve(size_t n)
{
if (n > _capacity)
{
char *tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char n)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size++] = n;
_str[_size] = '\0';
}
string &operator+=(char n)
{
push_back(n);
return *this;
}
void append(const char *str)
{
size_t len = _size + strlen(str);
if (len > _capacity)
{
reserve(len);
}
strcpy(_str + _size, str);
_size = len;
}
string &operator+=(const char *str)
{
append(str);
}
void resize(size_t n, char c = '\0')
{
if (n > _capacity)
{
reserve(n);
}
for (size_t i = _size; i < n; ++i)
{
_str[i] = c;
}
_size = n;
_str[_size] = '\0';
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
bool empty()
{
return 0 == _size;
}
bool operator>(const string &s)
{
return strcmp(_str, s.c_str());
}
bool operator==(const string &s)
{
return strcmp(_str, s.c_str()) == 0;
}
bool operator!=(const string &s)
{
return !(*this == s);
}
bool operator>=(const string &s)
{
return *this > s || *this == s;
}
bool operator<(const string &s)
{
return !(*this >= s);
}
bool operator<=(const string &s)
{
return !(*this > s);
}
string &insert(size_t pos, const char *str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (len + _size > _capacity)
{
reserve(len + _size);
}
size_t end = _size + len;
while (end != pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
string &eraser(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
begin++;
}
_size = _size - len;
}
return *this;
}
void clear()
{
_size = 0;
_str[0] = '\0';
_capacity = 0;
}
void swap(string &s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
size_t find(char c, size_t pos = 0) const
{
while (pos < _size)
{
if (_str[pos] == c)
{
return pos;
}
++pos;
}
return npos;
}
size_t find(char *s, size_t pos = 0) const
{
const char *p = strstr(_str + pos, s);
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;
}
}
private:
char *_str;
size_t _size;
size_t _capacity;
const static size_t npos;
};
const size_t string::npos = -1;
原文鏈接:https://blog.csdn.net/m0_60447315/article/details/126448202
相關推薦
- 2021-12-04 C語言實現可排序通訊錄的示例代碼_C 語言
- 2022-06-15 C++詳細講解繼承與虛繼承實現_C 語言
- 2022-09-21 詳解C語言中typedef和#define的用法與區別_C 語言
- 2022-04-09 SpringBoot設置CorsFilter過濾器解決跨域問題
- 2022-07-09 SAP Commerce Cloud 里的 Site API 調用方式講解
- 2022-09-24 詳解ASP.NET中加密和解密的方法_實用技巧
- 2022-04-18 numpy中np.nanmax和np.max的區別及坑_python
- 2022-12-08 linux服務器中搭建redis6.0.7集群_Redis
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支