探索C嘎嘎的奇妙世界:第十四关---STL(string的模拟实现)

2024-06-18 22:44

本文主要是介绍探索C嘎嘎的奇妙世界:第十四关---STL(string的模拟实现),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. string类的模拟实现

1.1 经典的string类问题

        上一关已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。大家看下以下string类的实现是否有问题?
        
// 为了和标准库区分,此处使用String
class String
{
public:/*String():_str(new char[1]){*_str = '\0';}*///String(const char* str = "\0") 错误示范//String(const char* str = nullptr) 错误示范String(const char* str = ""){// 构造String类对象时,如果传递nullptr指针,可以认为程序非if (nullptr == str){assert(false);return;}_str = new char[strlen(str) + 1];strcpy(_str, str);}~String(){if (_str){delete[] _str;_str = nullptr;}}
private:char* _str;
};
// 测试
void TestString()
{String s1("hello bit!!!");String s2(s1);
}

        说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝

1.2 浅拷贝

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

1.3 深拷贝 

        如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

2.string各类主要接口的模拟实现

2.1 迭代器

	string.h:typedef char* iterator;typedef const char* const_iterator;iterator begin();//迭代器起始位置iterator end();//迭代器const_iterator begin()const;const_iterator end()const;string.cpp:string::iterator string::begin(){return _str;}string::iterator string::end(){return _str + _size;}string::const_iterator string::begin()const{return _str;}string::const_iterator string::end()const{return _str + _size;}

        上述代码中定义了一个类`string`,该类具有`begin()`和`end()`函数,用于返回迭代器对象。

        迭代器是一种用于遍历容器元素的对象。迭代器将容器中的元素组织起来,以便可以按顺序访问它们。

        在`string.h`中,`typedef char* iterator;`和`typedef const char* const_iterator;`定义了两种迭代器类型,分别用于可变和常量的字符串。

        `string::begin()`和`string::end()`函数分别返回迭代器的起始位置和结束位置。

        在`string.cpp`中,`string::begin()`和`string::end()`函数被实现。`string::begin()`函数返回字符串的起始位置,即指向第一个字符的指针。`string::end()`函数返回字符串的结束位置,即指向最后一个字符后面的位置的指针。

        `string::begin()const`和`string::end()const`函数是常量成员函数,用于返回常量字符串的迭代器的起始位置和结束位置。

        通过使用这些迭代器,可以在循环中遍历字符串中的每个字符,并执行相应的操作。

2.2 size、c_str、运算符[ ]的重载、构造函数以及析构函数

    string.h:string(const char* str = "");~string();size_t size()const;const char* c_str()const;char& operator[](size_t pos);const char& operator[](size_t pos)const;string.cpp:string::string(const char*str):_size(strlen(str)){_str=new char[_size+1];_capacity = _size;strcpy(_str, str);}string::~string(){delete[] _str;_str = nullptr;_capacity = _size = 0;}size_t string::size()const{return _size;}const char* string:: c_str()const{return _str;}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];}

        上述代码中定义了一个类`string`,该类包含了几个常用的字符串相关函数。

        在`string.h`中,构造函数`string(const char* str = "")`用于创建一个字符串对象。析构函数`~string()`用于销毁字符串对象并释放内存。`size_t size()const`函数用于返回字符串的长度`const char* c_str()const`函数用于返回字符串的C风格字符数组。

        `char& operator[](size_t pos)`和`const char& operator[](size_t pos)const`函数分别用于访问字符串中指定位置的字符。`operator[]`函数设计为“下标运算符重载”,允许使用类似数组下标的方式来访问字符串中的字符。

        在`string.cpp`中,构造函数`string::string(const char* str)`用于根据传入的C风格字符串创建一个新的字符串对象。函数内部首先计算传入的字符串的长度,然后动态分配空间并复制字符串内容。析构函数`string::~string()`用于释放字符串所占用的内存。`size_t string::size()const`函数返回字符串的长度。`const char* string::c_str()const`函数返回指向字符串的C风格字符数组的指针。

        `char& string::operator[](size_t pos)`和`const char& string::operator[](size_t pos)const`函数实现了通过下标访问字符串中特定位置字符的功能。函数内部使用断言`assert`来确保访问的位置在有效范围内。

        通过使用这些函数,可以方便地创建、访问和操作字符串对象。

2.3 reserve、push_back、append以及运算符+=重载

    string.h:void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char*str);string.cpp: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 : _capacity * 2;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);}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;}

        在上述代码中定义了一些用于修改字符串对象的函数。

        在`string.h`中,`void reserve(size_t n)`函数用于保留至少能容纳n个字符的空间。`void push_back(char ch)`函数在字符串的末尾插入一个字符。`void append(const char* str)`函数在字符串的末尾插入一个C风格的字符数组。`string& operator+=(char ch)`函数用于在字符串末尾添加一个字符,并返回修改后的字符串对象的引用。`string& operator+=(const char* str)`函数用于在字符串末尾添加一个C风格的字符数组,并返回修改后的字符串对象的引用。

        在`string.cpp`中,`void string::reserve(size_t n)`函数用于在需要的情况下扩展字符串的容量。如果n大于当前容量,它将创建一个新的更大的字符数组,并将原字符串的内容复制到新的数组中。然后释放原来的字符数组,并将指针指向新的数组,同时更新容量变量。`void string::push_back(char ch)`函数在字符串的末尾插入一个字符。如果字符串当前的大小已经等于容量,则先扩展容量,然后插入字符。`void string::append(const char* str)`函数在字符串的末尾插入一个C风格的字符数组。如果插入后的长度超过容量,则先扩展容量,然后将字符数组的内容复制到字符串中。`string& string::operator+=(char ch)`函数利用`push_back()`函数在字符串末尾添加一个字符,并返回修改后的字符串对象的引用。`string& string::operator+=(const char* str)`函数利用`append()`函数将C风格的字符数组添加到字符串末尾,并返回修改后的字符串对象的引用。

        通过使用这些函数,可以方便地修改字符串对象,包括扩展容量、在末尾插入字符和字符数组等操作。

2.4 insert、erase以及find

	string.h: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.cpp:void string::insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}//memmove(_str + pos + 1, _str + pos, sizeof(char) * (_size - pos + 1));//法一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);}//memmove(_str + pos + len, _str + pos, sizeof(char) * (_size - pos + 1));//法一size_t end = _size+len;//法二while (end >pos+len-1){_str[end] = _str[end-len];end--;}memcpy(_str + pos, str, len);_size+=len;}void string::erase(size_t pos, size_t len){assert(pos < _size);if (len >= _size-pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}size_t string::find(char ch, size_t pos){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){char* p = strstr(_str + pos, str);return p - _str;}

        在上述代码中定义了一些用于在字符串对象中查找和修改字符的函数。

        在`string.h`中,`void insert(size_t pos, char ch)`函数在指定位置插入一个字符。`void insert(size_t pos, const char* str)`函数在指定位置插入一个C风格的字符数组。`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)`函数从指定位置开始,查找一个C风格的字符数组在字符串中第一次出现的位置。

        在`string.cpp`中,`void string::insert(size_t pos, char ch)`函数在指定位置插入一个字符。如果字符串的大小已经等于容量,则先扩展容量,然后将插入位置后的字符依次后移,并将指定位置处的字符替换为插入的字符。`void string::insert(size_t pos, const char* str)`函数在指定位置插入一个C风格的字符数组。如果插入后的长度超过容量,则先扩展容量,然后将插入位置后的字符依次后移,并将指定位置处的字符替换为插入的字符数组中的字符。`void string::erase(size_t pos, size_t len)`函数从指定位置开始,删除指定长度的字符。如果删除的长度大于等于从指定位置到字符串末尾的长度,则将指定位置处的字符设为'\0',并更新字符串的大小。否则,将删除位置后的字符依次前移,覆盖被删除的字符,并更新字符串的大小。`size_t string::find(char ch, size_t pos)`函数从指定位置开始,在字符串中查找字符第一次出现的位置。遍历字符串中从指定位置开始的字符,如果找到与目标字符相同的字符,则返回该位置的索引。如果没有找到,返回`npos`。`size_t string::find(const char* str, size_t pos)`函数从指定位置开始,在字符串中查找一个C风格的字符数组第一次出现的位置。利用`strstr()`函数找到指定字符数组在字符串中的地址,并计算地址和字符串的地址差值,即为指定字符数组第一次出现的位置的索引。

        通过使用这些函数,可以方便地在字符串对象中插入、删除和查找字符和字符数组。

2.5 swap、substr以及运算符< > <= >= == !=的重载

    string.h:void swap(string& s);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;string.cpp: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大于后面剩余字符,有多少取多少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 !(*this <= s);}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 swap(string& s)`:交换当前字符串和参数字符串的内容、大小和容量。
        - `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`:重载不等于操作符,判断当前字符串是否不等于参数字符串。

        这些功能和操作符重载使得字符串类更加方便实用,可以更灵活地进行字符串的操作和比较。

2.6 clear以及运算符<< >>重载

    string.h:void clear();istream& operator>> (istream& is, string& str);ostream& operator<< (ostream& os, const string& str);string.cpp:void string::clear(){_str[0] = '\0';_size = 0;}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;}

        在上述代码中定义了一个简单的字符串类 string,并在 string 类中实现了 clear() 方法。
在 string 类的实现文件 string.cpp 中,clear() 方法将字符串数组 _str 的第一个字符设置为 '\0',并将字符串的大小 _size 设置为 0。
        另外,还重载了输入流运算符 >> 和输出流运算符 <<。
        在输入流运算符的实现中,先调用 string 类的 clear() 方法清空字符串,然后使用 istream 对象的 get() 方法逐个读取字符,直到遇到空格或换行符,将字符添加到字符串中。
        在输出流运算符的实现中,使用 ostream 对象的 << 运算符逐个输出字符串中的字符。
        最后,返回相应的输入流或输出流对象。

        到此我们只是简单的模拟实现了一下STL中string的相关接口~,后续我们会一一展开学习的,希望这篇博客能给您带来一些启发和思考!那我们下次再一起探险喽,欢迎在评论区进行讨论~~~

这篇关于探索C嘎嘎的奇妙世界:第十四关---STL(string的模拟实现)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug