string的使用和模拟实现| 细致到strcpy ,strstr,strcmp都不放过

2024-03-19 01:20

本文主要是介绍string的使用和模拟实现| 细致到strcpy ,strstr,strcmp都不放过,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我们模拟实现一个string 不是为了造一个比库里面更好的,而是熟悉语法,学习底层原理,复习数据结构等。

string的成员变量

string 就是有\0的顺序表,所以和顺序表的成员一样有,size,capacity,和一个指针

  1. 因为string的底层是字符串数组
    所以我们需要一个char*的指针指向数组
  2. size记录存放字符的个数不包括/0
  3. capacity作用是反应现在的容量以便后续扩容

string的构造方法

常用的就是第一个和第四个所以我们模拟实现也是这两个

在这里插入图片描述

用法

std::string s1; // 无参
std::string s2("hello word"); //字符串构造

无参的构造方法的实现

在这里插入图片描述

这样初始化可以吗?不可以 因为用空指针初始化_str则_str无法解引用,打印_str数组时会空指针异常。
怎么解决这个问题呢?可以给str开一个空间放/0

 string():_str(new char[1]),_size(0),_capacity(0){_str[0] = '/0';}

我们如果想传一个c字符串来构造字符串怎么做呢?

全缺省的构造参数的实现

就是用c字符串str的长度给_str开空间,再把str拷贝_str

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

可能有的同学已经忘了strcpy的原理了我简单复习下

strcpy的模拟实现

就是挨个拷贝,连同反斜杠/0一起,因为没有/0就不能称为一个c串了。

char* my_strcpy(char* dest, const char* src) 
{assert(dest != nullptr && src != nullptr);char* ret = dest;while (*src != '\0') {*(dest++ ) = *(src++);}*dest = *src;return ret;
}

为什么这里的_size要+1?

因为strlen算出的字符串大小不包括/0,所以开辟的空间的大小要_size+1

为什么这里是默认传空字符串?

如果传空指针解strlen(str)没有/0作为字符串结束标志则会崩溃,不知道在哪结束所以不能传空指针,而空字符串默认是有一个/0的。

赋值运算符重载

 void  operator=(const string& s1)
{_size = s1._size;_capacity = s1._capacity;char* tmp = new char[_capacity + 1];strcpy(tmp, s1._str);delete[]_str;_str = tmp;
}                     

析构函数

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

遍历字符串

operator[]

这个有个非常进步的地方,就是它会越界检查了,c字符串越界的读不会检查而越界的写是抽查。

使用

s1[2]  // 非常的方便

传统c语言字符串下标遍历的缺点

越界读不报错
在这里插入图片描述
越界写报错但是是抽查的,在边界检查严格些

模拟实现

 char& operator[](size_t pos) {assert(pos< _size);return _str[pos];}

有了断言这样不管是读还是写都会检查了

迭代器

使用

 string s2("hello word");string::iterator it;it = s2.begin();while (it != s2.end()) {cout << *it<<" ";it++;}

注意这个end是\0的那个位置,而不是最后一个字符,因为是最后一个字符的话 it!=s2.end()就不打印最后一个字符了

不同类的迭代器可能不一样,我们用指针简单的实现一个string的迭代器

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

范围for

当我们把迭代器写好后,范围for自动就成了。
因为范围for底层就是调用迭代器
在这里插入图片描述

成员函数

reserve

在这里插入图片描述
功能是给字符串的容量增长到n个字符的空间,当n<_capacity时不起作用。

reserve的使用

reserve(100);

reserve的模拟实现

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

push_back

在这里插入图片描述
功能追加一个字符给字符串

push_back的使用

string s1("hello word");
s1.push_back('a');

push_back的模拟实现

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

append

appen的功能是把字符串变长通过在现存的字符串的末尾后面添加额外的字符串。
!

apeend的模拟实现

 void append(const string& s) {int len = strlen(s._str);if (_size > _capacity - len){reserve(_size + len);}strcpy(_str + _size, s._str);}

insert

在这里插入图片描述
功能是在pos位置之前插入其他字符/字符串

insert的使用

 std::string s("hello");std::string b(" word");s.insert(0, b);cout << s;

insert的模拟实现

插入字符
 void 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 insert(size_t pos, char c) 
{assert(pos <= _size);if (_size >= _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}int end = _size;while (end >= (int)pos) //不强转pos end变成无符号数 一直>=0 {_str[end+1] = _str[end];end--;}_str[pos] = c;_size++;}

注意这里的pos一定要强制类型转换不然end会整型提升 当头插时pos = 0 end被提升为无符号数了 一直大于pos 0 所以会出错

插入字符串

 void  insert(size_t pos, string& s) {int len = s._size;assert(pos <= _size);if (_size+len  >= _capacity){reserve(_size + len);}size_t end = _size + len;while (end >= pos + len){_str[end] = _str[end - len];end--;}strncpy(_str + pos, s.c_string(),len);_size += len;}

注意这里要有strncp目的是不让\0拷贝下来

erase

功能:擦除pos位置起len长度的字符串
在这里插入图片描述
第一个用得很多我们模拟实现第一个

erase的使用

   std::string s1("hello word");s1.erase(12);

erase的模拟实现

首先判断pos位置是否合法 pose小于size才行 因为等于size就把\0擦除了。
判断 npos = -1 || len + pos > size 成功就全部擦除把o位置的值赋值成\0
或者把pos位置赋值成\0
再把 pos+len的位置拷贝到 pos处
size-= len

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

resize

功能是改变字符串的长度
如果当前的size小于n,就缩短到n
若n大于size,如果指定了字符就在后面插入指定的字符否则插入\0以达到n的长度
在这里插入图片描述

resize的使用

  std::string s1("hello word");s1.resize(100,'a');cout << s1;

rise的模拟实现

首先判断size和n的关系
n小于size 则把n位置的字符赋值\0
否则 扩容到n的长度
把下标size到下标n-1的字符全部赋值成ch
size 改为 n

void resize(size_t n, char ch ='\0')
{if (n <= _size) {_str[n] = '/0';_size = n;}else {reserve(n);for (size_t i = _size; i < n; i++) {_str[i] = ch;}_str[n] = '\0';_size = n;}
}

substr

在这里插入图片描述

功能:返回从主串截取的从pos位置长度为len的子串

substr的使用

std::string s1("hello word");
cout << s1.substr(0, 5);

substr的模拟实现

因为要返回一个新的字符串,所以我们先定义个新串
然后一个+=循环 ,循环条件从pos到下标pos+len-1

string substr(size_t pos = 0, size_t len = npos)
{string sub;//if (len == npos || len >= _size-pos)if (len >= _size - pos){for (size_t i = pos; i < _size; i++){sub += _str[i];}}else{for (size_t i = pos; i < pos + len; i++){sub += _str[i];}}return sub;

find

寻找子串在主从中从pos位置开始第一个出现的位置
或者找一个字符在字符串pos后的第一个位置
没有找到则返回npos

使用

     string s1("hello word");int ret1 = s1.find("word", 3);int ret2 = s1.find("w",3);

模拟实现

size_t find(const string& str, size_t pos = 0) const 
{assert(pos < _size);char * ret= strstr(_str + pos, str.c_string());if(ret)return ret - _str;return npos;
}size_t 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;
}

这里我们直接用的库函数strstr 可能有的同学忘了ststr的功能
我简单复习一下 就是在主串中在子串第一个出现的位置然后返回匹配到的第一个位置的指针。没有找到则返回空指针

strstr的模拟实现

这里我们使用暴力查找的方法简单的模拟一下

     char* my_strstr(const char* str, const char* substr) {int len = strlen(substr);int i = 0;while (i < strlen(str)) {size_t j = 0;for (; j < len; j++){if (substr[j] == str[i]){i++;}else{break;}}if (j == len) {return (char*) str+i - len;}i++;}return nullptr;}

swap

交换两个字符串

使用

string s1("hello word");
string s2;
s2.swap(s1);

改写

在这里插入图片描述
我们直接把T变为 string string c(a); a =b ;b = c;
这里会走一次拷贝构造 两次赋值运算符重载 总共三次构造 还外加一次析构 c 代价太大了 所以必须重写

  void swap(string s1){std::swap(_size, s1._size);std::swap(_capacity, s1._capacity);std::swap(_str, s1._str);}

为了防止别人调用库里面的swap(T a,T b)我们需要在类外面写一个相同参数的swap,这样调用的时候就优先调用我们写了的。
因为函数模板有现成的吃现成的。

	void swap(string& x, string& y){x.swap(y);}

string的赋值运算符重载

==

    bool operator==(const string& s1, const string& s2) {return strcmp(s1.c_string(), s2.c_string()) ==0;}

可能有的同学忘了strcmp的原理
就是两个字符串从头开始比较 相等就继续走 如果遇到串1的第一个字符大于串2的第一个字符 return 1 小于则return -1 全部走完了则return 0

strcmp的模拟实现

    int my_strcmp(const char* a, const char* b) {while (*a == *b && *a != '\0' && *b != '\0'){a++;b++;}if (*a > *b)return 1;if (*a < *b)return -1;return 0;}

<

bool operator<(const string& s1, const string& s2){int ret = strcmp(s1.c_str(), s2.c_str());return ret < 0;}

>>

这里不能用 scanf 和 cin 因为 cin 和scanf 都把空格和 回车作为分隔符 读不到 则程序永远不会结束。

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

改进

我们这个+=需要频繁扩容不太好。
如果我们用reserve 开个很大的空间 则输入很小的时候又浪费了
所以我们用一个数组暂存字符 然后根据数组的大小一次性开辟好空间,就不用频繁扩容了。

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

什么叫现代写法呢?就是假借他人的手完成相应的功能

拷贝构造函数的现代写法

在这里插入图片描述
借构造函数的手 构造一个和s1一样的字符串 然后和 this 交换

         /* string(const string &s1) {_size = s1._size;_capacity = s1._capacity;char* tmp = new char[_size + 1];strcpy(tmp, s1._str);this->_str = tmp;}*/string(const string& s1) {string tmp(s1.c_string());swap(tmp);}

赋值运算符重载的现代写法

第三个版本是由第二个版本而进过来的
第二个版本借助 s 拷贝构造 字符串ss
第三个版本 我们没有用引用编译器帮我们调用了拷贝构造

// version 1/*string& operator=(const string& s){string ss(s);swap(ss);return *this;}*/// version 2/*string& operator=(const string& s){string ss(s);swap(ss);return *this;}*/// version 3string& operator=( string ss){swap(ss);return *this;}

谢谢观看

这篇关于string的使用和模拟实现| 细致到strcpy ,strstr,strcmp都不放过的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

详解Vue如何使用xlsx库导出Excel文件

《详解Vue如何使用xlsx库导出Excel文件》第三方库xlsx提供了强大的功能来处理Excel文件,它可以简化导出Excel文件这个过程,本文将为大家详细介绍一下它的具体使用,需要的小伙伴可以了解... 目录1. 安装依赖2. 创建vue组件3. 解释代码在Vue.js项目中导出Excel文件,使用第三

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

Python脚本实现自动删除C盘临时文件夹

《Python脚本实现自动删除C盘临时文件夹》在日常使用电脑的过程中,临时文件夹往往会积累大量的无用数据,占用宝贵的磁盘空间,下面我们就来看看Python如何通过脚本实现自动删除C盘临时文件夹吧... 目录一、准备工作二、python脚本编写三、脚本解析四、运行脚本五、案例演示六、注意事项七、总结在日常使用

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

python管理工具之conda安装部署及使用详解

《python管理工具之conda安装部署及使用详解》这篇文章详细介绍了如何安装和使用conda来管理Python环境,它涵盖了从安装部署、镜像源配置到具体的conda使用方法,包括创建、激活、安装包... 目录pytpshheraerUhon管理工具:conda部署+使用一、安装部署1、 下载2、 安装3

Mysql虚拟列的使用场景

《Mysql虚拟列的使用场景》MySQL虚拟列是一种在查询时动态生成的特殊列,它不占用存储空间,可以提高查询效率和数据处理便利性,本文给大家介绍Mysql虚拟列的相关知识,感兴趣的朋友一起看看吧... 目录1. 介绍mysql虚拟列1.1 定义和作用1.2 虚拟列与普通列的区别2. MySQL虚拟列的类型2

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB