C++:string类模拟实现
前言
今天我们来模拟实现一下string类~
总结实现源码附上~
一、构造相关
1. 类的定义
为了区分和库里面的string我们需要包个自己的命名空间,里面写我们的string类
namespace jyf
{
class string
{
private:
char* _str; //数组
size_t _size; //大小
size_t _capacity; //容量
public:
static const size_t npos; //之后要用的静态成员变量,npos = -1
};
}
2. 构造,拷贝构造,赋值重载,析构
//string();构造相关
string(const char* str = "");
~string();
string(const string& s);
//传统写法
//string& operator=(const string& s);
//现代写法
string& operator=(string s);
//string();构造相关
//构造函数
string::string(const char* str)
:_size(strlen(str))
{
_str = new char[_size + 1];
strcpy(_str, str);
_capacity = _size;
}
//析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
//拷贝构造
//传统写法
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
//赋值运算符重载
//传统写法
string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
3. 注意的点
1)构造函数初始化列表顺序问题
2)赋值运算符重载判断自我赋值
4. 拷贝构造和赋值运算符重载现代写法
1)拷贝构造
//现代写法
string::string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
相当于资本主义,疯狂压榨构造函数和析构的剩余价值~
2)赋值运算符重载
//新版本
string& string::operator=(string s)
{
swap(s);
return *this;
}
构造函数不能只传值,不然会引发无穷递归~
二、遍历相关
1. operator[ ]
char& string::operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& string::operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}
这两个版本是一样的,一个正常调用,一个const修饰的来调用。
2. 迭代器
注意:使用之前要进行typedef
//typedef char* iterator;
using iterator = char*;
using const_iterator = const char*;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
这里的参数名字要写对,不然类似范围for这样的会出问题
三、容量相关
1. size
size_t string::size()const
{
return _size;
}
2. capacity
size_t string::capacity()const
{
return _capacity;
}
3. empty
bool string::empty()const
{
return _size == 0;
}
4. 改变大小resize
这里memset是这样的
//改size,赋值c
void string::resize(size_t newSize, char c)
{
if (newSize > _size)
{
// 如果newSize大于底层空间大小,则需要重新开辟空间
if (newSize > _capacity)
{
reserve(newSize);
}
memset(_str + _size, c, newSize - _size);
}
_size = newSize;
_str[newSize] = '\0';
}
5. 预设空间reserve
这里只有n比_capcacity大才开空间
//预开空间
void string::reserve(size_t n)
{
// n比_capcacity小就不开了
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
四、插入与清除
1. 任意位置插入insert
1)插一个字符
- 断言pos位置pos <= _size
- 判断是否需要扩容
- 循环pos及以后数据后移
注意这里后移逻辑,size_t end = _size + 1;
也就是说要从_size + 1的位置开始把_str[end] = _str[end - 1],如果不这样干,一旦让end变为负数,又因为end为_size_t类型,他就会无限循环
size_t -1可以简单理解为最大的整形数据,因此会无限循环
而且只改end为int类型也不行,因为pos是size_t,end会引发整型提升,提升为无符号整型,除非强转了pos为int
- 插入
//任意位置插入
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
2)插字符串
- 断言pos位置pos <= _size
- 判断是否需要扩容,不一样的是判断_size + len > _capacity,扩容也需要它和2 * _capacity比
- 循环pos及以后数据后移,同样注意end不要小于零
- 插入
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
for (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
2. 尾插字符push_back
可以考虑复用insert,或者也可以像注释这样写
//尾插字符
void string::push_back(char ch)
{
/*if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
_size++;*/
insert(_size, ch);
}
3. 尾插字符串append
可以考虑复用insert,或者也可以像注释这样写
//尾插字符串
void string::append(const char* str)
{
/*size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;*/
insert(_size, str);
}
4. operator+=
可以考虑复用push_back 和 append
//重载加等
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
5. 清除数据clear
不动空间,直接让_str[0]的位置变为’\0’就好
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
6. 删除pos后的n个数据erase
n太大,str太小pos后面全删完
n太小,只删除pos后一部分,要挪数据
//删除
void string::earse(size_t pos, size_t len)
{
assert(pos < _size);
// n太大, str太小pos后面全删完
if (pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else//只删除pos后一部分,要挪数据
{
size_t end = pos + len;
while (end <= _size)
{
_str[end - len] = _str[end];
end++;
}
_size -= len;
}
}
7. 转C风格
char* c_str() const
{
return _str;
}
8. 类内交换
直接调用算法库的swap交换_str, _size, _capacity;
//类内的交换
void string::swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
五、查找相关
1. find
1)返回c在string中第一次出现的位置
同样这里的npos需要定义在类内public作用于下,定义成静态成员变量,类内定义,类外声明
但这里对于size_t npos 编译器做了特殊处理,直接在类内定义都可以,但最好还是按照规则来
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
2)返回子串s在string中第一次出现的位置
这里借助了C语言找字串的函数strstr,左值是从哪个位置开始找,右值是找到的字串
它的返回值是第一次找到的位置,因此我们这个函数要返回的下标就是两个指针相减,得出下标。
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
六、重载比较运算符
借助在日期类那里学到的思路,只需重载 == 和 < ,其他复用这两个就好
1. operator==
bool operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
2. operator<
bool operator< (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
3. 复用 == 与 <
bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}
bool operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}
七、重载流插入,流输出
1. operator<<
ostream& operator<< (ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
2. operator>>
- 赋值要先清理干净原来的内容
- 这里用get()是因为要获取到’ ‘(空格) 和’ \n ',将他们作为判断结束的标志
- 不断地循环 += ch
istream& operator>> (istream& is, string& str)
{
str.clear();
char ch = is.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}
八、getline
getline 和 operator>> 的区别就是,getline默认遇到’ ‘不结束,遇到’ \n’才结束,而且可以自定义结束终止符。
- 赋值就要先清理str
- 这里没有直接 += ,做了一个优化
定义一个 char buff[256] ,假定可以存256个值,每次获取到的 ch 都先放到 buff 里,等到 buff 存到255个数据
用 i 来记录
,就添加一个’\0’,然后 += 到str上面,再将 i 重新置 0 ,继续循环
这样减少了每次的 += 效率的损失,提高了效率
- 出循环的时候 i 不为零,补一个’\0’,再 += 到 str 上就好
//getline获取字符串
istream& getline(istream& is, string& str, char delim)
{
str.clear();
int i = 0;
char buff[256];
char ch;
ch = is.get();
while (ch != delim)
{
buff[i++] = ch;
if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = is.get();
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return is;
}
}
总结
string模拟实现源码:
//string.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace jyf
{
class string
{
public:
//typedef char* iterator;
using iterator = char*;
using const_iterator = const char*;
//string();构造相关
string(const char* str = "");
~string();
string(const string& s);
//传统写法
//string& operator=(const string& s);
//现代写法
string& operator=(string s);
char operator[](size_t i);
const char operator[](size_t i) const;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
size_t size() const
{
return _size;
}
char* c_str() const
{
return _str;
}
bool empty()const
{
return _size == 0;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
void resize(size_t newSize, char c = '\0');
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 insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void earse(size_t pos, size_t len = npos);
size_t find(char ch, size_t pos);
size_t find(const char* str, size_t pos);
void swap(string& str);
string substr(size_t pos, size_t len);
private:
char* _str;
size_t _size;
size_t _capacity;
public:
static const size_t npos;
};
//类外交换
void swap(string& s1, string& s2);
//类外比较的重载
bool operator== (const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string & lhs, const string & rhs);
bool operator>= (const string & lhs, const string & rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);
//类外重载流插入,流提取
ostream& operator<< (ostream& os, const string& str);
istream& operator>> (istream& is, string& str);
//getline获取字符串
istream& getline(istream& is, string& str, char delim = '\n');
}
//string.cpp
#include"string.h"
namespace jyf
{
const size_t string::npos = -1;
//string();构造相关
//构造函数
string::string(const char* str)
:_size(strlen(str))
{
_str = new char[_size + 1];
strcpy(_str, str);
_capacity = _size;
}
//析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
//拷贝构造
//传统写法
/*string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}*/
//现代写法
string::string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
//赋值运算符重载
//传统写法
/*string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}*/
//新版本
string& string::operator=(string s)
{
swap(s);
return *this;
}
//改size,赋值c
void string::resize(size_t newSize, char c)
{
if (newSize > _size)
{
// 如果newSize大于底层空间大小,则需要重新开辟空间
if (newSize > _capacity)
{
reserve(newSize);
}
memset(_str + _size, c, newSize - _size);
}
_size = newSize;
_str[newSize] = '\0';
}
//预开空间
void string::reserve(size_t n)
{
// n比_capcacity小就不开了
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//[]遍历
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::push_back(char ch)
{
/*if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
_size++;*/
insert(_size, ch);
}
//尾插字符串
void string::append(const char* str)
{
/*size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;*/
insert(_size, str);
}
//重载加等
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
//任意位置插入
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
for (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
//删除
void string::earse(size_t pos, size_t len)
{
assert(pos < _size);
// n太大, str太小pos后面全删完
if (pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else//只删除pos后一部分,要挪数据
{
size_t end = pos + len;
while (end <= _size)
{
_str[end - len] = _str[end];
end++;
}
_size -= len;
}
}
//查找
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
//类内的交换
void string::swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
//获取字串
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
//如果,len的长度大于pos位置开始之后的长度,就从pos取到结尾
if (len > _size - pos)
{
len = _size - pos;
}
jyf::string sub;
sub.reserve(len);
for (int i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
//类外交换
void swap(string& s1, string& s2)
{
s1.swap(s2);
}
//类外比较的重载
bool operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}
bool operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool operator< (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
bool operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}
//类外重载流插入,流提取
ostream& operator<< (ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
istream& operator>> (istream& is, string& str)
{
str.clear();
char ch = is.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}
//getline获取字符串
istream& getline(istream& is, string& str, char delim)
{
str.clear();
int i = 0;
char buff[256];
char ch;
ch = is.get();
while (ch != delim)
{
buff[i++] = ch;
if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = is.get();
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return is;
}
}
//test.cpp
#include "string.h"
#include <iostream>
using namespace std;
void TestString()
{
// 1. 构造函数测试
cout << "=== 构造函数测试 ===" << endl;
jyf::string s1;
cout << "默认构造: " << s1.c_str() << " (size: " << s1.size() << ")" << endl;
jyf::string s2("hello");
cout << "带参数构造: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;
jyf::string s3(s2);
cout << "拷贝构造: " << s3.c_str() << " (size: " << s3.size() << ")" << endl;
// 2. 赋值运算符测试
cout << "\n=== 赋值运算符测试 ===" << endl;
s2 = "world";
cout << "赋值: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;
// 3. 运算符[] 测试
cout << "\n=== 运算符[] 测试 ===" << endl;
cout << "s2[0]: " << s2[0] << ", s2[4]: " << s2[4] << endl;
// 4. push_back 测试
cout << "\n=== push_back 测试 ===" << endl;
s2.push_back('!');
cout << "push_back: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;
// 5. append 测试
cout << "\n=== append 测试 ===" << endl;
s2.append(" test");
cout << "append: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;
// 6. insert 测试
cout << "\n=== insert 测试 ===" << endl;
s2.insert(6, ' ');
cout << "insert char: " << s2.c_str() << endl;
s2.insert(12, " case");
cout << "insert string: " << s2.c_str() << endl;
// 7. earse 测试
cout << "\n=== earse 测试 ===" << endl;
s2.earse(6, 5);
cout << "earse部分删除: " << s2.c_str() << endl;
s2.earse(6); // 删除从位置6开始到结尾的所有字符
cout << "earse到末尾: " << s2.c_str() << endl;
// 8. find 测试
cout << "\n=== find 测试 ===" << endl;
size_t pos = s2.find('!', 0);
cout << "find '!': " << pos << endl;
pos = s2.find("ld", 0);
cout << "find 'ld': " << pos << endl;
// 9. substr 测试
cout << "\n=== substr 测试 ===" << endl;
jyf::string s5 = s2.substr(0, 5);
cout << "substr(0, 5): " << s5.c_str() << endl;
// 10. 类外比较运算符测试
cout << "\n=== 类外比较运算符测试 ===" << endl;
cout << "s2 == s2: " << (s2 == s2) << endl;
cout << "s2 != s3: " << (s2 != s3) << endl;
cout << "s3 < s2: " << (s3 < s2) << endl;
cout << "s2 > s3: " << (s2 > s3) << endl;
// 11. 类内 swap 测试
cout << "\n=== swap 测试 ===" << endl;
s2.swap(s3);
cout << "swap后 s2: " << s2.c_str() << ", s3: " << s3.c_str() << endl;
// 12. clear 测试
cout << "\n=== clear 测试 ===" << endl;
s2.clear();
cout << "clear后 s2: " << s2.c_str() << " (size: " << s2.size() << ")" << endl;
// 13. 类外 swap 测试
cout << "\n=== 类外 swap 测试 ===" << endl;
jyf::swap(s2, s3);
cout << "类外swap后 s2: " << s2.c_str() << ", s3: " << s3.c_str() << endl;
// 14. 类外流插入、提取运算符测试
cout << "\n=== 流插入与提取测试 ===" << endl;
jyf::string s6("stream test");
cout << "输出运算符: " << s6 << endl;
jyf::string s7;
cout << "输入一段字符串: ";
cin >> s7;
cout << "输入的字符串是: " << s7 << endl;
// 15. getline 测试
cout << "\n=== getline 测试 ===" << endl;
jyf::string s8;
cout << "输入一行文字 (以逗号结束): ";
jyf::getline(cin, s8, ',');
cout << "输入的内容是: " << s8 << endl;
}
int main()
{
TestString();
return 0;
}
到这里就结束啦~
谢谢大家!~
原文地址:https://blog.csdn.net/Jdxxwu/article/details/142747613
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!