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

相关文章

Python如何实现读取csv文件时忽略文件的编码格式

《Python如何实现读取csv文件时忽略文件的编码格式》我们再日常读取csv文件的时候经常会发现csv文件的格式有多种,所以这篇文章为大家介绍了Python如何实现读取csv文件时忽略文件的编码格式... 目录1、背景介绍2、库的安装3、核心代码4、完整代码1、背景介绍我们再日常读取csv文件的时候经常

Golang中map缩容的实现

《Golang中map缩容的实现》本文主要介绍了Go语言中map的扩缩容机制,包括grow和hashGrow方法的处理,具有一定的参考价值,感兴趣的可以了解一下... 目录基本分析带来的隐患为什么不支持缩容基本分析在 Go 底层源码 src/runtime/map.go 中,扩缩容的处理方法是 grow

Go 1.23中Timer无buffer的实现方式详解

《Go1.23中Timer无buffer的实现方式详解》在Go1.23中,Timer的实现通常是通过time包提供的time.Timer类型来实现的,本文主要介绍了Go1.23中Timer无buff... 目录Timer 的基本实现无缓冲区的实现自定义无缓冲 Timer 实现更复杂的 Timer 实现总结在

基于Python实现多语言朗读与单词选择测验

《基于Python实现多语言朗读与单词选择测验》在数字化教育日益普及的今天,开发一款能够支持多语言朗读和单词选择测验的程序,对于语言学习者来说无疑是一个巨大的福音,下面我们就来用Python实现一个这... 目录一、项目概述二、环境准备三、实现朗读功能四、实现单词选择测验五、创建图形用户界面六、运行程序七、

如何使用Docker部署FTP和Nginx并通过HTTP访问FTP里的文件

《如何使用Docker部署FTP和Nginx并通过HTTP访问FTP里的文件》本文介绍了如何使用Docker部署FTP服务器和Nginx,并通过HTTP访问FTP中的文件,通过将FTP数据目录挂载到N... 目录docker部署FTP和Nginx并通过HTTP访问FTP里的文件1. 部署 FTP 服务器 (

Vue中动态权限到按钮的完整实现方案详解

《Vue中动态权限到按钮的完整实现方案详解》这篇文章主要为大家详细介绍了Vue如何在现有方案的基础上加入对路由的增、删、改、查权限控制,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、数据库设计扩展1.1 修改路由表(routes)1.2 修改角色与路由权限表(role_routes)二、后端接口设计

MySQL 日期时间格式化函数 DATE_FORMAT() 的使用示例详解

《MySQL日期时间格式化函数DATE_FORMAT()的使用示例详解》`DATE_FORMAT()`是MySQL中用于格式化日期时间的函数,本文详细介绍了其语法、格式化字符串的含义以及常见日期... 目录一、DATE_FORMAT()语法二、格式化字符串详解三、常见日期时间格式组合四、业务场景五、总结一、

C#集成DeepSeek模型实现AI私有化的流程步骤(本地部署与API调用教程)

《C#集成DeepSeek模型实现AI私有化的流程步骤(本地部署与API调用教程)》本文主要介绍了C#集成DeepSeek模型实现AI私有化的方法,包括搭建基础环境,如安装Ollama和下载DeepS... 目录前言搭建基础环境1、安装 Ollama2、下载 DeepSeek R1 模型客户端 ChatBo

Qt实现发送HTTP请求的示例详解

《Qt实现发送HTTP请求的示例详解》这篇文章主要为大家详细介绍了如何通过Qt实现发送HTTP请求,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、添加network模块2、包含改头文件3、创建网络访问管理器4、创建接口5、创建网络请求对象6、创建一个回复对

Python中配置文件的全面解析与使用

《Python中配置文件的全面解析与使用》在Python开发中,配置文件扮演着举足轻重的角色,它们允许开发者在不修改代码的情况下调整应用程序的行为,下面我们就来看看常见Python配置文件格式的使用吧... 目录一、INI配置文件二、YAML配置文件三、jsON配置文件四、TOML配置文件五、XML配置文件