C++模擬實現(xiàn)string的示例代碼
一、std::swap和std::string::swap的區(qū)別
如果用std::swap交換兩個string對象,將會發(fā)生1次構造和2次賦值,也就是三次深拷貝;而使用std::string::swap僅交換成員,代價較小。
二、string的默認構造函數(shù)
1、構造函數(shù)
string(const char* s = "")
{
_size = strlen(s);//_size和_capacity均不包含'\0'
_capacity = _size;
_arr = new char[_size + 1];
memcpy(_arr, s, _size + 1);
}
構造函數(shù)用缺省值,能夠滿足空串的構造。
這里設計_size和_capacity均不包含'\0'。_arr的空間多new一個,用于儲存'\0'。
再將形參的內(nèi)存拷貝至_arr中,即可完成構造。
2、拷貝構造
寫法1:老老實實的根據(jù)string對象的私有變量進行拷貝構造。
string(const string& s)
{
_size = s._size;//_size和_capacity均不包含'\0'
_capacity = s._capacity;
_arr = new char[_capacity + 1];
memcpy(_arr, s._arr, _capacity + 1);
}
寫法2:通過構造一個臨時對象,將這個臨時對象的私有變量全部和*this的私有變量交換。
注意拷貝構造需要先將_arr初始化為nullptr,防止后續(xù)tmp拿到隨機地址。(tmp銷毀將調(diào)用析構函數(shù),對一塊隨機地址的空間進行析構程序?qū)罎ⅲ?/p>
void swap(string& s)
{
std::swap(_arr, s._arr);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
:_arr(nullptr)//防止交換后tmp._arr為隨機值,析構出錯
{
string tmp(s.c_str());//構造
swap(tmp);
}
3、賦值運算符重載
寫法1:同樣的老實人寫法。這種寫法要防止自己給自己賦值!
string& operator=(const string& s)
{
if (this != &s)//防止自己給自己賦值
{
_size = s._size;
_capacity = s._capacity;
char* tmp = new char[_capacity + 1];
delete[] _arr;
_arr = tmp;
memcpy(_arr, s._arr, _capacity + 1);
}
return *this;
}
寫法2:通過構造臨時變量tmp,完成賦值。這種寫法無需擔心自己給自己賦值的情況,并且_arr無需初始化為nullptr。
void swap(string& s)
{
std::swap(_arr, s._arr);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string& operator=(const string& s)
{
string tmp(s.c_str());//構造
swap(tmp);
return *this;
}
4、析構函數(shù)
~string()
{
_size = _capacity = 0;
delete[] _arr;
_arr = nullptr;
}
三、string中的小接口
//string的size()接口
size_t size()const//右const修飾*this,這樣const和非const對象均可調(diào)用
{
return _size;
}
//string的c_str()接口
const char* c_str()const
{
return _arr;
}
//string的capacity()接口
size_t capacity()const
{
return _capacity;
}
//string的clear()接口
void clear()
{
_arr[0] = '\0';
_size = 0;
}
//string的判空
bool empty()const
{
return _size == 0 ? false : true;
}如果函數(shù)形參不發(fā)生改變的,無腦加const修飾。
只有指針和引用會有const權限問題。
四、遍歷接口的實現(xiàn)
1、對operator[]進行重載
char& operator[](size_t pos)//普通對象,可讀可寫
{
assert(pos < _size);
return _arr[pos];
}
const char& operator[](size_t pos)const//const對象,僅讀
{
assert(pos < _size);
return _arr[pos];
}
讓字符串進行下標式的訪問,需要重載兩個operator[]函數(shù),正常對象去調(diào)可讀可寫,const對象調(diào)用只讀。
2、迭代器
typedef char* iterator;
iterator begin()
{
return _arr;
}
iterator end()//end指向字符串的'\0'
{
return _arr + _size;
}
string的迭代器是字符指針,寫完迭代器就可以用迭代器實現(xiàn)訪問、修改了。
范圍for的底層也是一個迭代器,但是范圍for底層只認begin()和end(),如果和自己實現(xiàn)的迭代器接口名稱對不上,那么范圍for將無法使用。
五、reserve和resize
//sring的reserve接口, 如果預開空間小于現(xiàn)有空間,將不會改變?nèi)萘俊?
void reserve(size_t n = 0)
{
if (n + 1 > _capacity)
{
char* tmp = new char[n + 1];
memset(tmp, '\0', n + 1);
memcpy(tmp, _arr, _size);
delete[] _arr;
_arr = tmp;
_capacity = n;
}
}
//sring的resize接口
void resize(size_t n, char c)
{
//判斷n的大小
if (n > _capacity)
{
reserve(n);
memset(_arr + _size, c, n - _size);
_size = n;
}
else
{
_arr[n] = '\0';
_size = n;
}
}reserve是擴容,可以用于預開空間,防止頻繁的空間申請。申請一塊n+1大小的空間,將該空間全部初始化'\0',再將_arr中的數(shù)據(jù)拷貝至tmp中,釋放_arr,_arr指向tmp。
在resize中需要考慮_size擴容和縮容的問題。
六、插入刪除查找相關接口
1、push_back、append、+=
string& push_back(const char c)
{
//判斷容量
if (_size == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;//防止出現(xiàn)空串的情況
reserve(newCapacity);
}
_arr[_size++] = c;
return *this;
}
string& append(const char* s)
{
//判斷容量
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_arr + _size, s);
_size += len;
return *this;
}
string& operator+=(const char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}寫push_back要考慮到原對象為空串的情況(即_capacity為0)。
+=可以復用push_back和append。
2、insert和earse
string& insert(size_t pos, char c)
{
assert(pos < _size);
//判斷容量
if (_size == _capacity)
{
reserve(_capacity + 1);
}
//挪動數(shù)據(jù)
for (size_t i = _size; i > pos; --i)
{
_arr[i] = _arr[i - 1];
}
_arr[pos] = c;
++_size;
return *this;
}
string& insert(size_t pos, const char* s)
{
size_t len = strlen(s);
//判斷容量
if (len + _size > _capacity)
{
reserve(len + _size);
}
//挪動數(shù)據(jù)
for (size_t i = _size + len; i > pos + len - 1; --i)
{
_arr[i] = _arr[i - len];
}
memcpy(_arr + pos, s, len);
_size += len;
return *this;
}
string& earse(size_t pos, size_t len = npos)
{
assert(pos < _size);
//先判斷刪到底的情況
if (len == npos || pos + len >= _size)
{
_arr[pos] = '\0';
_size = pos;
}
else
{
memcpy(_arr + pos, _arr + pos + len, _size - pos - len);
_size -= len;
}
return *this;
}insert接口在挪動數(shù)據(jù)時,從最后一個元素的后一個(后len個)位置開始覆蓋,可以保證不出現(xiàn)size_t 類型越界的情況。
earse接口,需要分類討論字符串是否刪到底。
注意,這個pos是const static成員,C++語法中,只有指針和整型的const static成員是可以在類中進行初始化的。
3、find
size_t find(const char c, size_t pos = 0)const
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (_arr[i] == c)
{
return i;
}
}
return npos;
}
size_t find(const char* s, size_t pos = 0)const
{
assert(pos < _size);
const char* p = strstr(_arr, s);
if (p != nullptr)
{
return _arr - p;
}
return npos;
}從指定位置找字符或字符串,找到了,返回第一個匹配字符/子串的下標。
七、流插入和流提取
//流插入和流提取的重載時為了自定義類型的輸入輸出
inline ostream& operator<<(ostream& out, const string& s)//這里訪問的到私有,所以可以不用寫成友元函數(shù)
{
for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'結束打印
{ //比如我在字符串中間插入一個'\0',打印結果不一樣
out << s[i];
}
return out;
}
inline istream& operator>>(istream& in, string& s)
{
s.clear();//用之前先清空s
//in >> c;//流提取不會識別空格和換行
char c = in.get();
char buff[128] = { '\0' };//防止頻繁擴容
size_t i = 0;
while (c != ' ' && c != '\n')
{
if (i == 127)
{
s += buff;
i = 0;
}
buff[i++] = c;
c = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}因為string提供了訪問私有的接口,所以流插入和流提取可以不用重載成string類的友元函數(shù)。
對于流提取,如果頻繁的尾插,會造成頻繁擴容。而且C++的擴容和C語言的擴容不一樣,C++使用new不能原地擴容,只能異地擴容,異地擴容就會導致新空間的開辟、數(shù)據(jù)的拷貝、舊空間釋放。為了防止頻繁擴容,我們可以創(chuàng)建一個可以存儲128字節(jié)的數(shù)組,在這個數(shù)組中操作,這個數(shù)組滿了就尾插至對象s中。
為什么不能用getline,而是要一個字符一個字符尾插呢?因為流提取遇到空格和'\n'會結束提取,剩余數(shù)據(jù)暫存緩沖區(qū),如果是getline的話,遇到空格是不會停止讀取的。
八、模擬實現(xiàn)的string整體代碼
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using std::cout;
using std::cin;
using std::endl;
using std::ostream;
using std::istream;
namespace jly
{
class string
{
public:
void swap(string& s)
{
std::swap(_arr, s._arr);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//構造函數(shù)
string(const char* s = "")
{
_size = strlen(s);//_size和_capacity均不包含'\0'
_capacity = _size;
_arr = new char[_size + 1];
memcpy(_arr, s, _size + 1);
}
//拷貝構造
//寫法1
//string(const string& s)
//{
// _size = s._size;//_size和_capacity均不包含'\0'
// _capacity = s._capacity;
// _arr = new char[_capacity + 1];
// memcpy(_arr, s._arr, _capacity + 1);
//}
//寫法2
string(const string& s)
:_arr(nullptr)//防止交換后tmp._arr為隨機值,析構出錯
{
string tmp(s.c_str());//構造
swap(tmp);
}
//賦值運算符重載
//寫法1
//string& operator=(const string& s)
//{
// if (this != &s)//防止自己給自己賦值
// {
// _size = s._size;
// _capacity = s._capacity;
// char* tmp = new char[_capacity + 1];
// delete[] _arr;
// _arr = tmp;
// memcpy(_arr, s._arr, _capacity + 1);
// }
// return *this;
//}
//寫法2
string& operator=(const string& s)
{
string tmp(s.c_str());//構造
swap(tmp);
return *this;
}
//析構函數(shù)
~string()
{
_size = _capacity = 0;
delete[] _arr;
_arr = nullptr;
}
//string的size()接口
size_t size()const//右const修飾*this,這樣const和非const對象均可調(diào)用
{
return _size;
}
//string的c_str()接口
const char* c_str()const
{
return _arr;
}
//string的capacity()接口
size_t capacity()const
{
return _capacity;
}
//string的clear()接口
void clear()
{
_arr[0] = '\0';
_size = 0;
}
//string的判空
bool empty()const
{
return _size == 0 ? false : true;
}
//對operator[]進行重載
char& operator[](size_t pos)//普通對象,可讀可寫
{
assert(pos < _size);
return _arr[pos];
}
const char& operator[](size_t pos)const//const對象,僅讀
{
assert(pos < _size);
return _arr[pos];
}
//迭代器
typedef char* iterator;
iterator begin()const
{
return _arr;
}
iterator end()const//end指向字符串的'\0'
{
return _arr + _size ;
}
//string的reserve接口,如果預開空間小于現(xiàn)有空間,將不會改變?nèi)萘俊?
void reserve(size_t n=0)
{
if (n + 1 > _capacity)
{
char* tmp = new char[n + 1];
memset(tmp, '\0', n + 1);
memcpy(tmp, _arr, _size);
delete[] _arr;
_arr = tmp;
_capacity = n;
}
}
//string的resize接口
void resize(size_t n, char c='\0')
{
//判斷n的大小
if (n > _capacity)
{
reserve(n);
memset(_arr + _size,c,n-_size);
_size = n;
}
else
{
_arr[n] = '\0';
_size = n;
}
}
//插入刪除查找相關接口
string& push_back(const char c)
{
//判斷容量
if (_size == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;//防止出現(xiàn)空串的情況
reserve(newCapacity);
}
_arr[_size++] = c;
return *this;
}
string& append(const char* s)
{
//判斷容量
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_arr+_size,s);
_size += len;
return *this;
}
string& operator+=(const char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}
string& insert(size_t pos, char c)
{
assert(pos < _size);
//判斷容量
if (_size == _capacity)
{
reserve(_capacity + 1);
}
//挪動數(shù)據(jù)
for (size_t i = _size; i > pos; --i)
{
_arr[i] = _arr[i - 1];
}
_arr[pos] = c;
++_size;
return *this;
}
string& insert(size_t pos, const char* s)
{
size_t len = strlen(s);
//判斷容量
if (len + _size > _capacity)
{
reserve(len + _size);
}
//挪動數(shù)據(jù)
for (size_t i = _size + len; i > pos + len - 1; --i)
{
_arr[i] = _arr[i - len];
}
memcpy(_arr + pos, s, len);
_size += len;
return *this;
}
string& earse(size_t pos, size_t len = npos)
{
assert(pos<_size);
//先判斷刪到底的情況
if (len == npos || pos + len >= _size)
{
_arr[pos] = '\0';
_size = pos;
}
else
{
memcpy(_arr + pos, _arr + pos + len,_size-pos-len);
_size -= len;
}
return *this;
}
size_t find(const char c, size_t pos = 0)const
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (_arr[i] == c)
{
return i;
}
}
return npos;
}
size_t find(const char* s, size_t pos = 0)const
{
assert(pos < _size);
const char* p = strstr(_arr, s);
if (p != nullptr)
{
return _arr - p;
}
return npos;
}
private:
char* _arr;
size_t _size;
size_t _capacity;
const static size_t npos = -1;//只有const static整型、指針成員變量可以在類中定義,其他類型不行
};
//流插入和流提取的重載時為了自定義類型的輸入輸出
inline ostream& operator<<(ostream& out, const string& s)//這里訪問得到私有,所以可以不用寫成友元函數(shù)
{
for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'結束打印
{ //比如我在字符串中間插入一個'\0',打印結果不一樣
out << s[i];
}
return out;
}
inline istream& operator>>(istream& in, string& s)
{
s.clear();//用之前先清空s
//in >> c;//流提取不會識別空格和換行
char c=in.get();
char buff[128] = { '\0' };//防止頻繁擴容
size_t i = 0;
while (c != ' ' && c != '\n')
{
if (i == 127)
{
s += buff;
i = 0;
}
buff[i++] = c;
c = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
//測試函數(shù)
void test1()
{
}
}以上就是C++模擬實現(xiàn)string的示例代碼的詳細內(nèi)容,更多關于C++實現(xiàn)string的資料請關注腳本之家其它相關文章!
相關文章
C語言實現(xiàn)數(shù)據(jù)結構和雙向鏈表操作
這篇文章主要介紹了C語言實現(xiàn)數(shù)據(jù)結構雙向鏈表操作,需要的朋友可以參考下2017-03-03
Visual Studio 2022配置fftw第三方庫的詳細過程
FFTW是一個可以進行可變長度一維或多維DFT的開源C程序庫,是目前最快的FFT算法實現(xiàn),本文簡述了在Windows平臺上,如何在C++中調(diào)用FFTW,所使用的IDE為Visual Studio 2022,感興趣的朋友一起看看吧2024-06-06
VS2017開發(fā)C語言出現(xiàn)“no_init_all“的解決辦法
這篇文章介紹了VS2017開發(fā)C語言出現(xiàn)“no_init_all“的解決辦法,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-12-12

