文章目录
- 📝前言
- 🌠 string的基本要素
- 🌉构造函数和析构函数
- 🌠string()
- 🌉string(const char* str = "");
- 🌠~string()
- 🌉深拷贝string(const string& s);
- 🌠三个成员函数的实现
- 🌉const size_t size() const;
- 🌉const size_t capacity() const;
- 🌉 const char* c_str() const;
- 🌠迭代器
- 🌉两个遍历下标实现
- 🌠总代码
- 🌉string.h
- 🌉string.cpp
- 🌉test.cpp
- 🚩总结
📝前言
前面我们学习了string
的用法,本节我们将实现string
的模拟实现,话不多说,直接上手,因此我们先了解我们是多文件进行编写,因此需要注意命名空间的控制,这是文件分布图:OK,我们开始~
注:标注声明的是加在类的声明(string.h),定义在类的定义(string.cpp)如果漏写,那就是小标题就是声明🥰:
🌠 string的基本要素
我们看 成员变量三部分:
char* _str
: 指向存储字符串内容的动态内存空间的指针。size_t _size
: 记录字符串的长度(不包括结尾的空字符'\0'
)。size_t _capacity
: 记录当前动态内存空间的容量。
因为模拟实现,我们避免跟库里的std::string冲突,我们需要定义在自己的命名空间,这样的好处除了避免冲突,在多个文件中,命名空间的内容是可以合并的,
# define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;
namespace self
{
class string
{
public:
--------
private:
char* _str;
size_t _size;
size_t _capacity;
}
🌉构造函数和析构函数
string有多种构造函数,默认构造函数,拷贝构造函数,我们可以实现string()
,或者可以实现全缺省构造函数string(const char* str = "");
我们只需要实现一个就可以了,防止调用错误:
🌠string()
声明:string();
空字符串构建:
string::string()
{
_str = new char[1]{'\0'};
_size = 0;
_capacity = 0;
}
🌉string(const char* str = “”);
声明:string(const char* str = "");
定义:
第一种实现:
string(const char* str)
: _str(new char[strlen(str)+1])
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str);
}
缺点:
- 在初始化列表中调用了
strlen(str)
两次,这可能会造成不必要的性能损耗。 - 在初始化列表中分配内存空间时,使用了
strlen(str) + 1
的大小,这可能会造成内存浪费。
优化后的代码:
string::string(const char* str)
: _size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
优点:
- 在初始化列表中只调用了一次
strlen(str)
,减少了不必要的性能损耗。 - 在构造函数体内分配内存空间时,使用了
_size + 1
的大小,更加精确地满足了字符串的需求,避免了内存浪费。
🌠~string()
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
🌉深拷贝string(const string& s);
String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构
造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块
空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
既然这样,开多一块空间存储分开存储s1,s2不就好了。
声明:string(const string& s);
定义实现:
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
- 在初始化列表中,使用
new char[s._capacity + 1]
分配了一块新的内存空间,大小为参数s
的容量加1(用于存储字符串结尾的空字符)。 - 使用
strcpy()
函数将参数s
的字符串内容复制到新分配的内存空间中。 - 将新创建的字符串长度(
s._size
)和容量(s._capacity
)赋值给当前对象的成员变量_size
和_capacity
。
🌠三个成员函数的实现
🌉const size_t size() const;
返回当前string对象中存储的字符串的长度。
const size_t string::size() const
{
return _size;
}
🌉const size_t capacity() const;
返回当前string对象的容量,即可以存储的最大字符数。
const size_t string::capacity() const
{
return _capacity;
}
🌉 const char* c_str() const;
返回一个指向string对象内部存储的 C 风格字符串的指针。
const char* string::c_str() const
{
return _str;
}
const size_t string::size() const
{
return _size;
}
🌠迭代器
迭代器是一种抽象的概念,用于遍历容器中的元素。在string
类中,迭代器用于遍历字符串中的字符。
typedef char* iterator;
typedef const char* const_iterator;
iterator begin();
iterator end();
iterator begin() const;
iterator end() const;
- 类型定义:
typedef char* iterator;
: 定义了一个名为iterator
的类型,它是一个指向char
类型的指针。这个迭代器可以用于修改字符串中的字符。typedef const char* const_iterator;
: 定义了一个名为const_iterator
的类型,它是一个指向const char
类型的指针。这个迭代器只能用于读取字符串中的字符,不能修改它们。
- 成员函数:
iterator begin();
: 返回一个指向字符串首字符的迭代器。iterator end();
: 返回一个指向字符串末尾字符的下一个位置的迭代器。iterator begin() const;
: 返回一个指向字符串首字符的只读迭代器。iterator end() const;
: 返回一个指向字符串末尾字符的下一个位置的只读迭代器。
定义实现:
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::iterator string::begin() const
{
return _str;
}
string::iterator string::end() const
{
return _str + _size;
}
这些迭代器可以用于遍历string
对象中的字符,例如使用for
循环或标准库算法。非常量版本的迭代器可以用于修改字符串中的字符,而常量版本的迭代器只能用于读取字符串中的字符。这种设计提供了灵活性,使用户可以选择合适的迭代器来满足不同的需求。
🌉两个遍历下标实现
下标运算符[]
允许用户通过下标访问字符串中的字符。
声明:
char& operator[](size_t pos);
const char& operator[](size_t pos) const;
成员函数声明:
char& operator[](size_t pos);
: 重载了下标运算符[]
,接受一个size_t
类型的下标作为参数,返回一个对字符串中对应位置的字符的引用。这个版本的下标运算符可以用于修改字符串中的字符。const char& operator[](size_t pos) const;
: 重载了下标运算符[]
,接受一个size_t
类型的下标作为参数,返回一个对字符串中对应位置的字符的常量引用。这个版本的下标运算符只能用于读取字符串中的字符,不能修改它们。
char& string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& string::operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
成员函数实现:
char& string::operator[](size_t pos)
: 首先使用assert
断言检查下标是否在字符串的范围内,然后返回_str[pos]
的引用。这允许用户通过下标修改字符串中的字符。const char& string::operator[](size_t pos) const
: 同样使用assert
断言检查下标是否在字符串的范围内,然后返回_str[pos]
的常量引用。这只允许用户通过下标读取字符串中的字符,不能修改它们。
🌠总代码
string的接口函数远远不止我介绍的这几种,阿森也会加更其他接口函数,以下是全代码的的接口和测试:
🌉string.h
// 禁用安全警告,这通常用于使用某些可能被认为是不安全的函数时
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream> // 引入输入输出流库
#include <assert.h> // 引入断言库
using namespace std; // 使用标准命名空间
// 自定义命名空间self
namespace self {
// 自定义字符串类
class string {
public:
// 定义迭代器类型
typedef char* iterator;
typedef const char* const_iterator;
// 构造函数
string(const char* str = ""); // 使用C风格字符串初始化
string(const string& s); // 拷贝构造函数
// 赋值运算符重载
string& operator=(const string& s);
// 析构函数
~string();
// 获取字符串大小
const size_t size() const;
// 获取C风格字符串
const char* c_str() const;
// 获取容量
const size_t capacity() const;
// 索引运算符重载
char& operator[](size_t pos);
const char& operator[](size_t pos) const;
// 获取迭代器指向字符串开始和结束位置
iterator begin();
iterator end();
// 获取常量迭代器指向字符串开始和结束位置
iterator begin() const;
iterator end() const;
// 预留空间
void reserve(size_t n);
// 在字符串末尾添加一个字符
void push_back(char ch);
// 追加一个C风格字符串
void append(const char* tmp);
// 加法赋值运算符重载
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 erase(size_t pos = 0, size_t len = npos);
// 查找字符或字符串
size_t find(char c, size_t pos = 0);
size_t find(const char* s, size_t pos = 0);
// 交换两个字符串
void swap(string& str);
// 获取子字符串
string substr(size_t pos = 0, size_t len = npos);
// 比较运算符重载
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;
// 清空字符串
void clear();
private:
// 字符串数据
char* _str;
// 字符串当前大小
size_t _size;
// 字符串容量
size_t _capacity;
// 静态常量npos,表示位置无效
const static size_t npos;
//特例
//const static size_t npos = -1;
//只有size_t能支持,其他类型都不可以
//const static double npos = -1;
};
// 输入运算符重载
istream& operator>>(istream& is, string& str);
// 输出运算符重载
ostream& operator<<(ostream& os, const string& str);
}
// 注意:这里只是声明了npos常量,并未初始化。它应该在类的实现文件(.cpp)中进行初始化,
// 初始化为一个表示无效位置的常量值,通常是size_t的最大值或-1。
🌉string.cpp
# define _CRT_SECURE_NO_WARNINGS 1
#include "string.h"
namespace self
{
const size_t string::npos = -1;
//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);
}
//s2(s1)
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// s1 = s3
// s1 = s1
string& string::operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[_capacity + 1];
strcpy(_str, s._str);
delete[] _str;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* string::c_str() const
{
return _str;
}
const size_t string::capacity() const
{
return _capacity;
}
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::iterator string::begin() const
{
return _str;
}
string::iterator string::end() const
{
return _str + _size;
}
const size_t string::size() const
{
return _size;
}
char& string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& string::operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
_str[_size] = ch;
_str[_size + 1] = '\0';
++_size;
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//for (size_t i = 0; i <= len; i++)
//{
// _str[_size + i] = tmp[i];
//}
strcpy(_str + _size, str);
_size += len;
}
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)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
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);
}
size_t lenth = strlen(str);
size_t end = _size + lenth;
while (end > pos + lenth - 1)
{
_str[end] = _str[end - lenth];
--end;
}
memmove(_str + pos, str, lenth);
_size += lenth;
}
void string::erase(size_t pos , size_t len )
{
assert(pos < _size);
//要删除的长度比pos位置长,有多少删多少
if (len > _size - pos)
{
_str[pos] = '\0';
_size = len;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
//找到返回下标
size_t string::find(char c, size_t pos)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* pch = strstr(_str + pos , str);
if (pch == nullptr)
{
return npos;
}
else
{
return pch - _str;
}
}
//std::swap(string)
//void swap(string& x, string& y);
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string string::substr(size_t pos, size_t len)
{
len = (len == npos) ? (_size - pos) : len;// 如果 len 为 npos,则设置为字符串剩余长度
if (pos >= _size)// 如果 pos 超出字符串长度,则返回空字符串
return string::string();
len = (pos + len >= _size) ? (_size - pos) : len;// 如果子字符串超出原始字符串的末尾,则调整 len
char* new_str = new char[len + 1];
strncpy(new_str, _str + pos, len);
new_str[len] = '\0';
return string(new_str); //返回子字符串的 bit::string 对象
}
//这个sub为局部对象,返回会析构,
//string string::substr(size_t pos, size_t len)
//{
// // len大于后面剩余字符,有多少取多少
// if (len > _size - pos)
// {
// string sub(_str + pos);
// return sub;
// }
// else
// {
// string sub;
// sub.reserve(len);
// for (size_t i = 0; i < len; i++)
// {
// sub += _str[pos + i];
// }
// return sub;
// }
//}
bool string::operator<(const string& s) const
{
return strcmp(_str, s._str) < 0;
}
bool string::operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
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 strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s) const
{
return !(*this == s);
}
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
//istream& operator>>(istream& is, string& str)
//{
// str.clear();
// char ch ='a';
// scanf("%c", &ch);
// while (ch != ' ' && ch != '\n')
// {
// str += ch;
// scanf("%c", &ch);
// }
// return is;
//}
istream& operator>>(istream& is, string& str)
{
str.clear();
char ch = is.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}
ostream& operator<<(ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
}
🌉test.cpp
# define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include "string.h"
namespace self
{
void test_string01()
{
self::string s1("hello world");
cout << s1.c_str() << endl;
string::iterator it = s1.begin();
while (it != s1.end())
{
//it = 'y';
cout << *it << " ";
it++;
}
cout << endl;
string::const_iterator it2 = s1.begin();
while(it2 != s1.end())
{
//*it2 = 'y';
cout << *it2;
it2++;
}
cout << endl;
for (auto e : s1)
{
cout << e << endl;
e++;
}
cout << endl;
const self::string s2("xxxxxxx");
for (size_t i = 0; i < s2.size(); i++)
{
//s2[i] = 'y';
}
for (size_t i = 0; i < s2.size(); i++)
{
cout << s2[i] << ' ';
}
for (auto& e : s2)
{
cout << e << endl;
e++;
}
cout << endl;
}
void test_string02()
{
self::string s3("hello world");
cout << s3.c_str() << endl;
s3.push_back('x');
cout << s3.c_str() << endl;
s3.push_back('y');
cout << s3.c_str() << endl;
s3.push_back('z');
cout << s3.c_str() << endl;
self::string s4("hello world");
cout << s4.c_str() << endl;
s4.append("xxxxxx");
cout << s4.c_str() << endl;
s4.append("yyyyyy");
cout << s4.c_str() << endl;
}
void test_string03()
{
self::string s5("hello world");
cout << s5.c_str() << endl;
s5 += 'x';
cout << s5.c_str() << endl;
s5 += "yyyyyy";
cout << s5.c_str() << endl;
}
void test_string04()
{
self::string s6("hello world");
cout << s6.c_str() << endl;
s6.insert(2, 'x');
cout << s6.c_str() << endl;
s6.insert(3, "yyy");
cout << s6.c_str() << endl;
s6.erase(2, 1);
cout << s6.c_str() << endl;
s6.erase(2, 3);
cout << s6.c_str() << endl;
}
void test_string05()
{
self::string s7("hello world");
cout << s7.c_str() << endl;
size_t location = s7.find('h', 1);
cout << location<< endl;
size_t location02 = s7.find("he", 1);
cout << location02 << endl;
}
void test_string06()
{
self::string buyer("money");
self::string seller("goods");
cout << "Before the swap, buyer has " << buyer.c_str();
cout << " and seller has " << seller.c_str() << '\n';
buyer.swap(seller);
cout << " After the swap, buyer has " << buyer.c_str();
cout << " and seller has " << seller.c_str() << '\n';
}
void test_string07()
{
self::string str = "We think in generalities, but we live in details.";
string str2 = str.substr(3, 5); // "think"
cout << str2.c_str() << ' ' ;
}
void test_string08()
{
self::string url("https://legacy.cplusplus.com/reference/string/string/");
size_t pos1 = url.find(':', 1);
self::string url1 = url.substr(0, pos1 - 0);
cout << url1.c_str() << endl << endl;
cout << pos1 << endl;
size_t pos2 = url.find('/', pos1 + 3);
cout << pos2 << endl;
self::string url2 = url.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << url2.c_str() << endl << endl;
self::string url3 = url.substr(pos2 + 1);
cout << url3.c_str() << endl;
}
void test_string09()
{
self::string s1("hello world");
cout << s1 << endl;
cin >> s1;
cout << s1 << endl;
}
void test_string10()
{
self::string s8("hello world");
cout << s8.c_str() << endl;
string s9(s8);
cout << s9.c_str() << endl;
string s10 = s8;
cout << s10.c_str() << endl;
}
}
int main()
{
//self::test_string01();
//self::test_string02();
//self::test_string03();
//self::test_string04();
//self::test_string05();
//self::test_string06();
//self::test_string07();
//self::test_string08();
//self::test_string09();
//self::test_string10();
string s1("hello world");
return 0;
}
测试一个部分: