当前位置: 首页 > ops >正文

string类的模拟实现

一. string类的构造和析构函数

在进行模拟实现string类的时候,重中之重就是它的构造函数和析构函数,这两个函数就像是地基,首先要构建好我们才能往后继续搭建。第一我们要确认string类的成员有哪些,首先最基本的就是底层的一个char指针:

private:char* _str;

然后我们再来实现一下构造函数

class MyString
{
public:MyString(const char* str = ""){if (nullptr == str){assert(false);return;}_str = new char[strlen(str) + 1];strcpy(_str, str);cout << "MyString()" << endl;}void string_print(){cout << _str << endl;}~MyString(){if (_str){delete[] _str;_str = nullptr;}cout << "~MyString()" << endl;}
private:char* _str;
};
int main()
{MyString s1("abcd");s1.string_print();MyString s2(s1);  //调用默认拷贝构造函数s2.string_print();return 0;
}

这里细心的同学可能就发现了,虽然程序是正常的输出,但是到最后还是崩溃了,而且只析构了一次,这里到底出了什么问题?

1.1 浅拷贝和深拷贝

上图就分析了为什么我们的程序会崩溃,原因就是在一个类中会有默认的构造函数和拷贝构造函数,上述的MyString类中没有显式的定义其拷贝构造函数以及赋值运算重载符,这就导致了编译器自己去调用默认的拷贝构造函数,致使s1在拷贝构造s2的时候调用了同一块空间,而同一块空间的多次销毁引起程序的崩溃,这种拷贝方式就称之为浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

与此对应的就是深拷贝,即每个对象都有单独的一份空间资源,不会于其他对象共处一块资源。在一个类中如果有资源管理的需求,一般来说不管是构造函数还是拷贝构造函数亦或者是赋值运算重载符都需要显式的去定义,而且都是按深拷贝的方式去提供。

1.2 string类的两种写法

MyString(const MyString& str)    //拷贝构造函数: _str(new char[strlen(str._str) + 1])
{strcpy(_str, str._str);
}MyString& operator=(const MyString& s)
{if (this != &s){char* Ptemp = new char[strlen(s._str) + 1];strcpy(Ptemp, s._str);delete[] _str;_str = Ptemp;}return *this;
}

上面的是比较传统的写法,思路和写法也是比较清楚易懂的。但是现代写法更适用于在实际项目中进行开发, 下面就是更现代的写法:

MyString(const MyString& s): _str(nullptr)
{MyString strTmp(s._str);swap(_str, strTmp._str);
}// 对比下和上面的赋值那个实现比较好?
MyString& operator=(MyString s)
{swap(_str, s._str);return *this;
}MyString& operator=(const MyString& s)
{if(this != &s){MyString strTmp(s);swap(_str, strTmp._str);}return *this;
}

对于拷贝构造来说先将_str初始化为nullptr,通过临时对象strTmp间接分配内存,再通过swap转移资源所有权,复用了已有的构造函数的逻辑,增强了代码的使用性,在实际的项目中用的更多。然后再来说两种赋值运算符重载,第一个赋值运算符重载(String& operator=(String s))通过值传递参数,利用拷贝 / 移动构造函数自动创建临时对象,再通过swap交换资源,无需手动检查自赋值,代码更简洁且健壮第二个版本(String& operator=(const String& s))采用引用传递,需显式检查自赋值,且需额外实现移动赋值重载才能支持右值,更适用于性能敏感且频繁处理大型对象,引用传递可避免一次拷贝构造(但需手动实现移动赋值运算符)

二.string类的模拟实现

在了解完string类中各个函数的作用,我们就来用自己的方法去一个个实现这些函数,下面就是我的实现代码:

//string.h
using namespace std;
namespace zda
{class string{public:string()   :_str(new char[1]{'\0'})  ,_size(0),_capacity(0){}string(const char* str):_size(strlen(str)) {_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);}~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 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 erase(size_t pos, size_t len);size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);char& operator[](size_t i){assert(i < _size);return _str[i];}const char& operator[](size_t i) const{assert(i < _size);return _str[i];}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;}size_t size() const{return _size;}void clear(){_str[0] = '\0';_size = 0;}const char* c_str() const{return _str;}string substr(size_t pos, size_t len=-1);  //截取一部分字符private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;};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);
}
#include "string.h"
namespace zda
{// s2(s1)string::string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// s2 = s1 = s3// s1 = s1;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;}void string::reserve(size_t n)  //扩容{if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;    //释放之前的内存_str = tmp;}}void string::push_back(char ch){if (_size == _capacity){reserve(_capacity = 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;}void string::append(const char* str){size_t len = strlen(str);size_t newCapacity = 2 * _capacity; //如果二倍不够,则需要多少if (newCapacity < _size + len)newCapacity = _size + len;reserve(newCapacity);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){reserve(_capacity = 0 ? 4 : _capacity * 2);}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){size_t newcapacity = 2 * _capacity;if (newcapacity < _size + len){newcapacity = _size + len;}reserve(newcapacity);}size_t end = _size + len;while (end>pos+len-1){_str[end] = _str[end - len];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;}void string::erase(size_t pos, size_t len){if (len >= _size - pos)  //全删{_str[pos] = '\0';_size = pos;}else       //只删除一部分{size_t end = pos + len;while (end<=_size){_str[end-len] = _str[end];++end;}}}size_t string::find(char ch, size_t pos = 0)    //找一个字符{assert(pos < _size);for (size_t i = 1; i < _size; i++){if (ch == _str[i]){return i;}}return -1;}size_t string::find(const char* str, size_t pos = 0)  //找一个字符串{assert(pos<_size);const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return -1;}else{return ptr - _str;}}string string::substr(size_t pos, size_t len)  //在pos位置截取len长度的字符串{assert(pos < _size);if (len > (_size - pos)){len = _size - pos;}zda::string sub;sub.reserve(len);for (int i = pos + i; i < len; i++)//从_str的pos位置开始赋值给sub{sub+= _str[pos + i];}return sub;}bool zda::operator== (const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) == 0;}bool zda::operator!= (const string& lhs, const string& rhs){return !(lhs == rhs);}bool zda::operator> (const string& lhs, const string& rhs){return !(lhs <= rhs);}bool zda::operator< (const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) < 0;}bool zda::operator>= (const string& lhs, const string& rhs){return !(lhs < rhs);}bool zda::operator<= (const string& lhs, const string& rhs){return lhs < rhs || lhs == rhs;}ostream& zda::operator<<(ostream& os, const string& str){//os<<'"';//os << "xx\"xx";for (size_t i = 0; i < str.size(); i++){//os << str[i];os << str[i];}return os;}istream& zda::operator>>(istream& is, string& str){str.clear();char ch;//is >> ch;ch = is.get();while (ch != ' ' && ch != '\n'){str += ch;ch = is.get();}return is;}}
http://www.xdnf.cn/news/16589.html

相关文章:

  • 标准七层网络协议和TCP/IP四层协议的区别
  • TP-Link Archer C50路由器曝安全漏洞,硬编码DES密钥可解密敏感配置
  • JavaScript语法、关键字和变量
  • 外网访问文档编辑器Docsify(Windows版本),内网穿透技术应用简便方法
  • AD里面出现元器件PCB封装不能编辑的情况
  • 湖北大学暑期实训优秀作品:面向美丽中国的数据化可视平台
  • Ubuntu LNMP
  • 《安富莱嵌入式周报》第356期:H7-TOOL的250M示波器模组批量生产中,自主开发QDD执行器,开源14bit任意波形发生器(2025-07-28)
  • 【Linux】重生之从零开始学习运维之Mysql事务
  • Kubernetes自动扩容方案
  • 【C语言进阶】题目练习
  • 《 java 随想录》| LeetCode链表高频考题
  • Linux文件归档和备份
  • 云原生MySQL Operator开发实战(五):扩展与生态系统集成
  • 基于Matlab图像处理的静态雨滴去除与质量评估系统
  • windows下Docker安装路径、存储路径修改
  • Docker初学者需要了解的几个知识点(三):Docker引擎与Docker Desktop
  • 实时行情接口使用教程
  • deepseek+飞书多维表格 打造小红书矩阵
  • python优秀案例:基于机器学习算法的景区旅游评论数据分析与可视化系统,技术使用django+lstm算法+朴素贝叶斯算法+echarts可视化
  • 移动端 WebView 调试实战,多平台行为差异排查与统一调试流程
  • zoho crm为什么xx是deal的关联对象但是调用函数时报错说不是关联对象
  • p5.js 三角形triangle的用法
  • 【RAG搭建Agent应用实战】基于检索增强生成(RAG)搭建特定场景Agent应用
  • Git分支
  • c++ nlohmann/json读写json文件
  • 溶解能计算
  • 【24】C# 窗体应用WinForm ——日历MonthCalendar属性、方法、实例应用
  • 电磁兼容(EMC):整改案例(十三)屏蔽外壳开孔解决433MHz无线通信问题
  • hive专题面试总结