【C++第八课 - string的底层实现】

2024-05-05 14:04

本文主要是介绍【C++第八课 - string的底层实现】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 基础知识
  • string构造函数和析构函数的坑
    • 构造函数
    • 析构函数
  • 迭代器、范围for
  • 运算符重载
    • operator []
  • const
  • 增删查改
    • push_back
    • reserve
    • append
    • +=
    • insert
    • erase
    • swap
    • find
    • substr
    • 拷贝构造
    • =
  • 流插入和流提取
    • <<流插入
    • >>流提取
      • clear
  • 深浅拷贝
    • 传统写法
    • 现代写法
  • 赋值
    • 传统写法
    • 现代写法
  • string的声明和定义分离

基础知识

capacity:指可以存储有效字符的个数,不包含/0
size:指现有的有效字符个数,不包含/0

string构造函数和析构函数的坑

构造函数

1、重复使用strlen()造成效率降低
在这里插入图片描述
上述构造函数中使用的strlen(s)的时间复杂度为(O(n),没算一次都要从头数到尾)
为了解决上述问题,提出了下面的解决方法
在这里插入图片描述
上述解决方法是不对的,这样声明和初始化列表的顺序必须一致

	private:size_t _size;size_t _capacity;char* _str;

上述解决方法的不足:如果动了声明的顺序或初始化列表的顺序就会报错
我们可以不用初始化列表,直接在里面构造就好了
在这里插入图片描述
2、无参的构造函数
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们写了上述的无参构造函数,但这样写是存在问题的。因为在cout的时候要对c_str()返回的char*进行解引用,而无参时返回的是空指针,这也就造成了空指针解引用问题

在这里插入图片描述
但是平时写构造函数的时候也没有两个分开写的时候,所以要把两个合起来
在这里插入图片描述
上述还有个问题s的内容没有copy给_str

		string(const char* s = ""){_size = strlen(s);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, s);}

析构函数

		~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}

迭代器、范围for

		typedef char* iteractor;iteractor begin(){return _str;}iteractor end(){return _str + _size;}

在这里插入图片描述
范围for的底层实际上就是迭代器,他只会傻瓜式的替换成迭代器
如果把我们string里面的begin换掉Begin,那么范围for就会无法替换,产生报错。
在这里插入图片描述

在这里插入图片描述

运算符重载

operator []

在这里插入图片描述

const

const char* a这表示指针指向的内容不能被修改
char* const a这里表示指针不能被修改

namespace zyh
{class string{public:string(const char* s = ""){_size = strlen(s);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, s);}~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}typedef const char* const_iterator; //指向的内容不能修改,指针本身可以修改const_iterator begin() const {return _str;}const_iterator end() const{return _str + _size;}const char* c_str() const  //不需要修改,因此只有一个const类型就可以,const对象可以调用,非const对象也可以调用{return _str;}char& operator[](size_t pos)//const对象调用时不能修改,非const对象调用时要能修改,因此需要两种类型{assert(pos <= _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos <= _size);return _str[pos];}private:char* _str;size_t _size;size_t _capacity;};
}

增删查改

push_back

        void push_back(char ch){if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';};

reserve

        void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}

append

		void append(char* s){size_t len = strlen(s);//没有\0if ((_size + len) > _capacity){reserve(_size + len + 1);}strcpy(_str + _size, s);_size += len;}

+=

		string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(char* s){append(s);return *this;}

insert

插入字符串时:扩容写错

strcpy会把\0也拷贝进去
如果不想把\0也拷进去,就用strncpy

		string& insert(size_t pos, const char* s){assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity){//size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(_size+len);}size_t end = _size+1;while (end > pos){_str[end + len-1] = _str[end-1];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = s[i];}return *this;}

上述代码在拷贝上可以简化,其次在返回值上不太对

        void insert(size_t pos, const char* s){assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];end--;}strncpy(_str + pos, s, len);}
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
using namespace zyh;void test1()
{string s1("hello world");s1.insert(5, "zyh");std::cout << s1.c_str() << std::endl;
}int main()
{test1();return 0;
}

在这里插入图片描述

erase

1、缺省值忘写

npos不属于某个对象,它是属于整个类,因此要写成静态的
可以认为是编译器的特殊处理,当为const的时候既算是也算是定义
在成员变量那里定义,而且这个用法只支持整型

const static size_t npos = -1;

在这里插入图片描述

普通静态成员变量,在类里面声明,在类外面定义,不能给缺省值,因为缺省值是初始化列表初始化时用的
2、使用strcpy、strncpy

3、不判断len!=npos
当len为npos时是不能加的,因为一加就溢出了
在这里插入图片描述

		void erase(size_t pos = 0, size_t len = npos){assert(pos <= _size);size_t end = _size;if (pos + len >= _size || len == npos){end = _size;}else{end = pos + len;}strcpy(_str + pos, _str + end);}

swap

1、类里面swap的写法

		void swap(string& y){std::swap(_str, y._str);}

2、三种swap的意义
成员函数里面的swap
在这里插入图片描述
非成员函数的swap
在这里插入图片描述
公共的swap
在这里插入图片描述
在这里插入图片描述

第二种swap底层实现上还是s1.swap(s2)

find

		size_t find(const char* str, size_t pos = 0){assert(pos <= _size);const char* ptr = strstr(_str + pos, str);if (ptr == nullptr)return npos;elsereturn ptr - _str;}

使用了strstr

substr

从某个位置取len个字符
1、考虑空间大小

		string substr(size_t pos = 0, size_t len = npos){assert(pos <= _size);size_t end = _size;if (pos + len < _size && len != npos){end = pos + len;}string tmp;tmp.reserve(end - pos);for (size_t i = pos; i < end; i++){tmp += _str[i];}return tmp;}

上述代码存在问题

2、传值返回 - 浅拷贝问题
当没写拷贝构造的时候,默认生成的是浅拷贝
在这里插入图片描述

拷贝构造

		string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_capacity = s._capacity;_size = s._size;}

=

		string& operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_capacity = s._capacity;_size = s._size;}return *this;}

流插入和流提取

流插入和流提取不用非要设计为友元,因为不需要访问私有成员,用范围for、迭代器什么的就可以

<<流插入

	std::ostream& operator<<(std::ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}

>>流提取

1、对于空格或换行不能提取

cin不能提取空格和换行
为了解决上面的问题可以用cin.get()
这个get是不管是什么都提取
在这里插入图片描述

	std::istream& operator>>(std::istream& in, string& s){char ch = in.get();while (ch != ' ' && ch != '\n'){s +=  ch;ch = in.get();}return in;}

2、多次对同一个string进行cin,没有覆盖,而是在后面增加

解决方法:
写一个clear,每次cin的时候先clear一下

	std::istream& operator>>(std::istream& in, string& s){s.clear();char ch = in.get();while (ch != ' ' && ch != '\n'){s +=  ch;ch = in.get();}return in;}

3、如果一次性输入内容太多,会一直扩容

    std::istream& operator>>(std::istream& in, string& s){s.clear();char buff[128];char ch = in.get();int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}

在这里插入图片描述

clear

为什么要置\0,如果不置\0会出现下面问题

cout是按_size一个字符一个字符的走
c_str则是看\0
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

		void clear(){_size = 0;_str[0] = '\0';}

深浅拷贝

传统写法

		string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_capacity = s._capacity;_size = s._size;}

现代写法

问题:如果初始化时指向是空那没有问题,如果是随机值就会出问题
解决方法:
初始化列表处理一下或给缺省值

		string(const string& s){string tmp(s._str);swap(tmp);}

赋值

传统写法和现代写法没什么区别,只是更简洁一点

传统写法

		string& operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_capacity = s._capacity;_size = s._size;}return *this;}

现代写法

没必要检查是否是自己赋值给自己了,因为已经早就拷贝构造了,在传参的时候

		string& operator=(string s){swap(s);return *this;}

在这里插入图片描述

string的声明和定义分离

1、不认识官方库的内容
(1)没包头文件
(2)命名空间的问题
2、缺省参数
只能在声明给,不能声明定义同时给
3、重定义问题
所以的重定义都是由于在多个cpp里面定义导致的

这篇关于【C++第八课 - string的底层实现】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/961901

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque