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

C++—string(2):string类的模拟实现及底层剖析

目录

一. string类的模拟实现

1.1 构造/析构函数

1.2 string类对象容量的操作实现

1.3 string类对象遍历及访问操作的实现

1.4 string类对象修改操作的实现

1.5 string 类非成员函数的实现


一. string类的模拟实现

1.1 构造/析构函数

string.h

namespace MyString
{class string{public:// 不带参的构造//string(); // 带参的构造//string(const char* s);// 上面两个构造合并为这一个string(const char* s = "");// 析构函数~string();// 拷贝构造string(const string& str);// 赋值运算符重载string& operator=(const string& s);//string& operator=(string tmp);const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity;};
}

string.cpp

namespace MyString
{string::string(const char* str) // C语言规定,常量字符串末尾就有\0,空的字符串也有:_size(strlen(str)) // strlen也会对指针解引用,所以缺省值不能为nullptr{_str = new char[_size + 1];_capacity = _size;strcpy(_str, str); // 这里能用strcpy}string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}// 传统写法// string s2(s1); // this就是s2,s就是s1// 拷贝构造(深拷贝)//string::string(const string& s)//{//	_str = new char[s._capacity + 1]; // 要多开一个空间存储\0,因为capacity不包含\0//	// strcpy(_str, s._str); // 字符串中间有'\0'用这个不行//	memcpy(_str, s._str, s._size + 1); // size不包含'\0',要拷贝size+1个字符//	_size = s._size;//	_capacity = s._capacity;//}// 现代写法(本质没有区别,只是写法不一样)效率没有区别,形态上不一样// 拷贝构造(深拷贝)string::string(const string& s) // 如果中间有'\0'也是有问题的,这里没有解决{string tmp(s._str); // 调用构造,相当于传了一个字符串swap(tmp); // this和tmp交换,如果this中的值没有初始化,那么可能是随机值// 交换给tmp后,出作用域,tmp析构可能会产生问题// 保险起见,在声明时给缺省值}// s1 = s3; 有3种情况// 1、s1的空间和s3的空间相等,概率比较低,这时直接拷贝没有空技能的浪费// 2、s1的空间比s3的空间小,空间不够,无法直接拷贝// 3、s1的空间比s3的空间大的多,拷贝过去会造成空间的浪费// 综上:直接将s1空间释放// 传统写法:赋值运算法重载//string& string::operator=(const string& s)//{//	// 不允许自己给自己赋值//	if (this != &s)//	{//		char* tmp = new char[s._capacity + 1];//		// strcpy(tmp, s._str); // 不用strcpy而是memcpy,避免字符串中间有'\0'的情况而出错//		memcpy(tmp, s._str, s._size + 1);//		delete[] _str;//		_str = tmp;//		_size = s._size;//		_capacity = s._capacity;//	}//	return *this;//}// 现代写法:赋值运算法重载string& string::operator=(const string& s){// 不允许自己给自己赋值if (this != &s){string tmp(s._str);swap(tmp);// this->swap(tmp);}return *this;}// 现代写法的更简洁:赋值运算法重载// 效率没有提升,只是写法更简洁,另一种思路;算是复用的体现//string& string::operator=(string tmp) // 在传参时拷贝会调用拷贝构造//{//  // 这里没考虑自己给自己赋值,这种情况几乎没有,并且在传参时已经拷贝了,所以判不判断都一样//	swap(tmp);//	return *this;//}
}

1.2 string类对象容量的操作实现

string.h

namespace MyString
{class string{public:bool empty() const;void reserve(size_t n);void resize(size_t n, char ch='\0');const char* c_str() const{return _str;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}void clear(){_str[0] = '\0';_size = 0;}private:char* _str;size_t _size;size_t _capacity;};
}

string.cpp

namespace MyString
{// 扩容(缩容代价比较大,这里不实现)void string::reserve(size_t n){if (n > _capacity){// 打印扩容// cout << "reserve:" << n << end;// 扩容char* tmp = new char[n + 1];// strcpy(tmp, _str); // 不用strcpy而是memcpy,避免字符串中间有'\0'的情况而出错memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}}// 让size变到n,用来删除并不多,更多是插入和初始化void string::resize(size_t n, char ch){if (n <= _size){// 删除,保留前n个_size = n;_str[_size] = '\0';}else{reserve(n); // 调用扩容,不一定真的扩,n > _capacity时才会扩容// 插入数据for (size_t i = _size; i < n; i++){_str[i] = ch;}_size = n;_str[_size] = '\0';}}
}

1.3 string类对象遍历及访问操作的实现

string.h

namespace MyString
{class string{// 迭代器的实现(迭代器不一定是指针,这里使用指针实现的)只有底层时数组是才能用原生指针实现// 1、用指针实现(不是很好,比如链表++是无法找到下一个节点的,解引用也无法得到节点中的数据;后期讲解)// iterator叫做内嵌类型或成员类型// 一个类里面的类型只有两种情况:1、typedef出来的;2、内部类// 受类域和访问限定符的限制// string、list的容器中都有iterator,都是typedef出来的// 都重命名为同样的类型,这样用起来的方式是一样的,不用再单独记他们各自的名称,也不存在冲突,因为在各自的类域typedef char* iterator;typedef const char* const_iterator; // const迭代器并不是本身不能修改,而是指向的内容不能修改iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}}
}

1.4 string类对象修改操作的实现

string.h

namespace MyString
{class string{// 尾插void push_back(char ch);void append(const char* str);string& operator+=(char ch){push_back(ch);// += 可以连续的 +=,所以要返回一个值,返回自己return *this;}string& operator+=(const char* str){append(str);return *this;}void clear(){_str[0] = '\0';_size = 0;}// 指定位置插入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 ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);string substr(size_t pos = 0, size_t len = npos);}
}

string.cpp

namespace MyString
{// 尾插(一个字符)void string::push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0'; // 不要忘记}// 尾插(字符串)void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){// 如果每次插入的字符串比较短,而且是多次插入// 如果每次扩容都是_size+len,那么每插入一次就要扩容,效率比较低// 下面这样扩容比较好:避免:频繁扩容;开多浪费;开少不够用reserve(std::max(_size + len, _capacity * 2));}// strcpy(_str + _size, str); // strcpy拷贝时遇到'\0'终止,也会把'\0'拷贝过去,所以最后不用再加'\0'memcpy(_str + _size, str, len + 1);_size += len; }void string::Insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}// 挪动数据// 法1:// size_t end = _size; // size_t类型到0再减1就变成了-1,-1转换为无符号整数就是整型最大值,end+1机会越界/*int end = _size;while (end >= (int)pos) // 二元运算符如果两个运算数类型不一样,会尝试先将两个转换成一样的类型再进行运算,范围小的向范围大的转,无符号整型范围比有符号整型大{						// 强制类型转换时一种方法_str[end + 1] = _str[end];--end;}*/// 法2:让end不能小于0size_t end = _size + 1; // end = _size + strlen(ch)while (end > pos) // pos+1-1{_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(std::max(_size + len, _capacity * 2));}// 挪动数据// 法1/*int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}*/// 法2 size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}// strncpy(_str + pos, str, len);memcpy(_str + pos, str, len);_size += len;}void string::erase(size_t pos, size_t len){assert(pos < _size);if (len == npos || len >= _size - pos){// pos及之后的删完了_size = pos;_str[_size] = '\0';}else{// 删部分// strcpy(_str + pos, _str + pos + len);memcpy(_str + pos, _str + pos + len, _size - (pos + len) + 1);_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);// strstr()是库里面的一个函数const char* ptr = strstr(_str + pos, str);if (ptr){return ptr - _str;}else{return npos;}}string string::substr(size_t pos, size_t len){assert(pos < _size);if (len == npos || len > _size - pos){len = _size - pos;}string sub;sub.reserve(len);for (size_t i = 0; i < len; i++){sub += _str[pos + i];}return sub;}
}

1.5 string类非成员函数的实现

string.h

namespace MyString
{class string{void swap(string& s);// 这里是在类内部实现的,库里面的string是在全局实现的的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;}// 流插入std::ostream& operator<<(std::ostream& out, const string& s);// 流提取std::istream& operator>>(std::istream& in, string& s); // 不能加constistream& getline(istream& in, string& s, char delim = '\n');// 实现这个函数是为了防止调用swap函数时( swap(s1,s2) ),string类型调用到算法库里面针对所有类型的swap模板实例化出的函数,string作为需要深拷贝的类型用这个低效// 有了下面这个函数,在调用swap函数时, swap(s1,s2)这样写,因为如果有模板和现成的,会优先使用现有的// 所有不会再去用算法库里面的swap模板实例化,然后再深拷贝,而是,直接使用这个函数,这个函数也是调用string类里面针对string交换的函数,所以更高效inline void swap(string& a, string& b){a.swap(b);}// C++11 swap模板也是高效的
}

string.cpp

namespace MyString
{void string::swap(string& s){    std::swap(_str, s._str); // 这里是将资源交换了std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// 这里也可以不复用,但是还是复用比较好,这样如果strcmp要修改,只需修改少数的几个函数即可bool string::operator<(const string& s) const{return strcmp(_str, s._str) < 0; // 这里没有考虑有'\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 !(*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);}std::ostream& operator<<(std::ostream& out, const string& s){// 可以用迭代器或下标访问字符,不用非要用友元for (auto ch : s){out << ch;}return out;}std::istream& operator>>(std::istream& in, string& s){s.clear(); // 先清空原字符串,避免追加到已有内容// 如果输入很长的字符串,+=时需要多次扩容// s.reserve(1024); // 不能用这个方法解决,虽然可以解决解决输入字符串比较长时,多次扩容的问题,但是如果s的空间开大了,只输入几个字符,会造成空间浪费// 用下面这个方法解决,既可以解决输入字符串比较长时,多次扩容的问题;还可以避免空间开大的问题。以空间换时间,少扩容提高的是效率char buff[256]; // 这个大小给多大都可以,buff出了作用域就销毁了int i = 0;char ch;// in >> ch; // 这里拿不到空格或换行,这时输入运算符的特点ch = in.get();while (ch != '\n' && ch != ' ') // 空格或换行就结束{buff[i++] = ch;if (i == 255){buff[i] = '\0';s += buff;i = 0;}// in >> ch;ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}istream& getline(istream& in, string& s, char delim){s.clear(); char buff[256]; int i = 0;char ch;ch = in.get();while (ch != delim) {buff[i++] = ch;if (i == 255){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}
}

总结

如有不足或改进之处,欢迎大家在评论区积极讨论,后续我也会持续更新C++相关的知识。文章制作不易,如果文章对你有帮助,就点赞收藏关注支持一下作者吧,让我们一起努力,共同进步!

http://www.dtcms.com/a/561638.html

相关文章:

  • 建个大型网站要多少钱模板网字体库
  • 上海网站建设服务多少钱没有网站 淘宝客
  • 如何查看网站的死链接中企动力提供网站建设
  • 你的第一个 Linux 系统程序:从进度条开始
  • 企业网站域名在哪申请网站搭建徐州百都网络搭建
  • Linux同步机制:POSIX 信号量 与 SystemV信号量 的 对比
  • Vim实用技巧补充1
  • UEC++屏幕打印输出Debug信息
  • 相电流采样电阻对电流噪声影响
  • 怎么用AI制作三宫格图片,附“山的后面是什么”同款提示词
  • 哪些网站是discuz做源代码如何做网站
  • 做轻时尚的网站哪个网站可以做加工代理的
  • CCF CSP-J/S复赛----时间复杂度计算方法
  • 佳易王洗车店会员专用管理系统软件应用实例:免安装多项目一卡搞定#洗车#洗车会员管理
  • 线性代数 - LU分解(LU-Factorization、LU Decomposition)
  • html网站要怎么做衡水哪儿专业做网站
  • 网站制作公司哪家价钱合理大庆百度做网站多少钱
  • 2025国赛获奖名单和优秀论文
  • 响应式布局新利器:CSS Grid 的 grid-template-areas 实战
  • 网站支付功能怎么做青岛官网seo价格
  • UART编程
  • 【WRF数据准备】生成 LCZ 对应的FRC_URB2D
  • 做淘宝客的网站怎么备案网站怎么做cdn
  • 做电影资讯网站算侵权吗电影网站页面seo
  • 物流网站哪个好泰州商城网站开发
  • 信誉好的网站建设公司搭建一个企业网站
  • 2025.10.27-2025.11.02第44周:输出倒逼输入
  • 达梦数据库10046事件使用
  • 爆炸焊接参数优化算法实现
  • 顺序表相关的算法题