string讲解和实现

embedded/2024/9/22 10:53:00/

认识string

string是将basic_string<char>重新定义了

basic_string是一个类模板,里面包括了一些列的有关字符的函数

注意:insert/erase/replace能不要就不用,他们都涉及挪动数据,效率不高

size_t注意

面对无符号整形size_t在使用--时,在使用进判断条件时,需要考虑到最后--到0后减不到负的的情况,判断条件还是否成立

解决方法:

将该判断语句的所有无符号整形部分换成或者强转成int类型

构造、析构、拷贝函数

namespace tt//避免与std里的string识别错误,采用命名空间封装  tt->test
{class string {public://构造函数string():_str(new char[1]), _size(0), _capacity(0)//构造时开一个空间存放\0,用于标记//_size和_capacity=0此时没有字符存入{_str[0] = '\0';}//析构函数~string(){_size = _capacity = 0;delete[] _str;_str = nullptr;//记住在delete后要将指针置空}//拷贝构造//之前的拷贝构造格式是:A(const A& ct),此时我们拷贝的是同一个类型的A,所以用A&去接收//此时我们拷贝的是字符串,所以里面也用能接收字符串的表示。而char* 类似A&string(const char* str):_size(strlen(str))//算出str长度,可用于改变_size、_capacity, _capacity(_size ){_str = new char[_capacity + 1];//比容量多1用于存放\0,之前构造的\0已经被覆盖strcpy(_str, str);//会自动拷贝\0}private:char* _str;size_t _size;size_t _capacity;};void Test1(){string s1("hello world");//直接调用拷贝。"hello world"常量字符串传递过去的是他的地址}
}int main()
{tt::Test1();return 0;
}

迭代器Iterator

迭代器一共有四种

begin()

返回第一个字符的地址

1.返回第一个字符的地址

2.迭代器是string类中的一个函数

3.iterator是一个类模板的类名,可自动识别不同类型(类似int,一种新定义的对象名称)

测试原函数效果:

#include <iostream>
#include <string>int main()
{std::string str("Test string");std::string::iterator it = str.begin();//迭代器的类名iterator,对象名it->接受begin返回的地址std::cout << *it;//begin()返回第一个字符的地址,读取时需要解引用std::cout << '\n';return 0;
}

模拟实现:

	namespace tt
{class string {public:string():_str(new char[1]), _size(0), _capacity(0){_str[0] = '\0';}~string(){_size = _capacity = 0;delete[] _str;_str = nullptr;}string(const char* str):_size(strlen(str)), _capacity(_size ){_str = new char[_capacity + 1];strcpy(_str, str);}//迭代器typedef char* iterator;iterator begin(){return _str;}private:char* _str;size_t _size;size_t _capacity;};void Test1(){string s1("hello world");string::iterator it = s1.begin();//it拿到s1第一个字符的地址std::cout << *it;}
}int main()
{tt::Test1();return 0;
}

end()

返回最后一个有效字符的下一个字符的地址

测试原函数效果:

注意:using namespace std;==解开全部限制

using  std::cout;解开(using)单独的限制

模拟实现:

public:	iterator end(){return _str + _size;}
string类外:void Test2(){string s1("hello world");string::iterator it = s1.end();std::cout << *it;}
结果:

范围for

底层还是迭代器

自动判断结束,自动++,自动赋值

能用下标+方括号[]就能用范围for

auto

麻烦长的话,可以用auto识别,因为begin()返回的就他他的类型,能自动识别

字符串长度size()

计算字符串长度,不包括\0,只计算有效字符(单位:字节)

测试原函数效果:

模拟实现:

public://计算有效字符字节数size_t size()const{return _size;}//测试sizevoid Test3(){string s1("hello world");size_t num = s1.size();std::cout << num;}

修改容量reserve()

测试原函数效果:

改变后有时会比reserve的n要大有时刚好。

模拟实现:

		//修改容量reservevoid reseve(size_t n = 0){if (n > _capacity){char* tmp = new char[n + 1];//开n+1多的1存放\0strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}

修改字符串长度resize()

1.resize多加的无法打印出来,加的都是\0,可以通过监视窗口查看

2.加实际值则会显示出来

3.如果比容量大会自动扩容

4.比容量少相当于保留前n个字符(删除的意义)

测试原函数效果:

模拟实现:

		//修改字符串长度//复杂void resize(size_t n){if (n <= _size){_size = n;}else if (n > _size && n < _capacity){while (_size<n){_str[_size] = '\0';_size++;}_str[_size] = '\0';}else if (n >= _capacity){_capacity = n;char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;while (_size < n){_str[_size] = '\0';_size++;}_str[_size] = '\0';}}//修改容量reservevoid reserve(size_t n = 0){if (n > _capacity){char* tmp = new char[n + 1];//开n+1 多的1存放\0strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}//发现两个可以合二为一,但是在扩大capacity中我们需要另外一个函数reservevoid resize(size_t n, char c = '\0')//函数重载{if (n < _size){_str[n] = '\0';_size = n;}else//>分两种,在capacity里和超出capacity{reserve(n);//不管大小,直接进行重新拷贝while (_size<n){_str[_size] = c;//存第n个数下标为n-1就可以存++_size;//++后存的是第n+1个数}_str[_size] = '\0';//n+1存\0,下标为n}}

清楚clear()

容量不变,size改变

测试原函数效果:

模拟实现:

		//清空内容void clear(){_str[0] = '\0';_size = 0;}

重载【】

测试原函数效果:

模拟实现:

		//重载【】char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}

附加append()

模拟实现:

//附加appendstring& append(const char* s){size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, s);//从末尾附加新字符串_size += len;return *this;}

重载+=

测试原函数效果:

模拟实现:

//重载+=string& operator+=(const char* s){append(s);return *this;}//尾插push_backvoid push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);//新开空间要注意为0的情况}_str[_size] = ch;++_size;_str[_size] = '\0';}string& operator+=(const char c){push_back(c);return *this;}

返回字符串地址(指针)c_str

模拟实现:

		//返回字符串地址(指针)const char* c_str() const{return _str;}

npos

查找find()

1.返回的是查找到的第一个字符或字符串出现位置的下标

2.pos是下标

3.从pos的位置向后查找

模拟实现:

size_t find(char ch, size_t pos = 0){for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t find(const char* sub, size_t pos = 0){const char* p = strstr(_str + pos, sub);if (p){return p - _str;}else{return npos;}}

使用:

cin注意

scanf和cin面对空格和\0都是间隔,读取时一个变量就无法读取间隔后面的内容,所以使用getline

识别到!才结束

>>输入重载

//输入重载istream& operator>>(istream& in, string& s){s.clear();//将原s中的字符串清空char ch;//in >> ch;ch = in.get();//先从这里拿一个字符,用于下面while判断s.reserve(128);while (ch != ' ' && ch != '\n')//修改结束条件{s += ch;//in >> ch;ch = in.get();//一个字符一个字符拿}return in;}

巧用数组当缓冲

理解想法就行

如果直接用reserve可能会造成输入的数少但是开的空间大,而用数组的话,在拿取时更加符合输入内容的空间大小,并且在出了作用域就会销毁

    istream& operator>>(istream& in, string& s){s.clear();//s.reserve(128);char buff[129];//开一个数组,存储128个字符,第129位存储\0,当做缓存size_t i = 0;char ch;ch = in.get();while (ch != ' ' && ch != '\n')//识别到空格或者换行出循环后,判断if{buff[i++] = ch;//输入的字符大于128个时,将缓存部分全部放入字符串中,//继续读取之后的字符并覆盖之前buff的字符if (i == 128)//可以存储129为对应下标为128,同时避免输入的超出数组{buff[i] = '\0';s += buff;i = 0;//标志}//s += ch;ch = in.get();}if (i != 0)//说明buff数组没满,保险{buff[i] = '\0';s += buff;}return in;}

<<输出重载

	ostream& operator<<(ostream& out, const string& s){for (size_t i = 0; i < s.size(); i++){out << s[i];}return out;//为了连续<<时可用}

赋值的现代写法(swap)

swap数组交换优点核心:

不仅交换了,还将自己要释放的交换给了别人

老版本:打工人自己搬砖

         //传统写法s2(s1)string(const string& s){_str = new char[s._capacity+1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// s2 = s3string& operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}

现代版本:

          //拷贝构造1       string(const char* str):_size(strlen(str))//算出str长度,可用于改变_size、_capacity, _capacity(_size ){_str = new char[_capacity + 1];//比容量多1用于存放\0,之前构造的\0已经被覆盖strcpy(_str, str);//会自动拷贝\0}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// s2(s1)拷贝构造2//两个拷贝构造构成重载string(const string& s):_str(nullptr), _size(0), _capacity(0){string tmp(s._str);swap(tmp);}// s2 = s3string& operator=(const string& s){if (this != &s){string tmp(s);//this->swap(tmp);swap(tmp);}return *this;}//更极致的现代写法string& operator=(string tmp){swap(tmp);return *this;}void Test7(){string s1("hello world");string s2;s2 = s1;cout << s1 << endl;cout << s2 << endl;}

这一部分转换的swap在s2转移给了tmp,而tmp又是在局部的变量,出了作用域会销毁(析构函数),顺便就解决了

并且通过简单地一个局部变量tmp,再利用swap,将拷贝和赋值简化


http://www.ppmy.cn/embedded/37999.html

相关文章

phpstudy靶场访问显示404 Not Found

涉及靶场 upload-labd sqli-labs pikachu dvwa 以及所有部署在phpstudy中的靶场 一、检查phpstduy设置 localhost——管理——修改 1、根目录&#xff08;默认设置&#xff0c;不要改&#xff09; localhost这个域名必须保留&#xff0c;并且把根目录设置为phpstudy的WWW文…

5.12 VUE项目实现Google 第三方登录

VUE项目实现Google 第三方登录 目录一、Google开发者平台配置1. 新建项目2. 配置 OAuth 权限请求页面并选择范围3. 启动API 和 服务 二、 登录代码实现1. 参考Google官网文档2. Google官网代码生成器3. 项目中实装 目录 一、Google开发者平台配置 Google Cloud: https://conso…

以gitee为例的git入门使用指北

安装git 在linux中我们首先需要使用 sudo apt install git来下载git 在windows中可以下载msysGit 链接&#xff1a;https://git-scm.com/download/win gitee准备 申请账号 建立仓库 ​ 点击新建仓库 这里一般是私有库&#xff0c;点击创建&#xff0c;这时你就拥有一个线上…

一起长锈:3 类型安全的Rust宏(从Java与C++转Rust之旅)

讲动人的故事,写懂人的代码 故事梗概:在她所维护的老旧Java系统即将被淘汰的危机边缘,这位在编程中总想快速完事的女程序员,希望能转岗到公司内部使用Rust语言的新项目组,因此开始自学Rust;然而,在掌握了Rust编程知识之后,为了通过Rust项目组的技术面试,使得转岗成功而…

ReactFlow的ReactFlow实例事件传参undefined处理状态切换

1.问题 ReactFlow的ReactFlow实例有些事件我们在不同的状态下并不需要&#xff0c;而且有时候传参会出现其它渲染效果&#xff0c;比如只读状态下我们不想要拖拉拽onEdgesChange连线重连或删除的功能。 2.思路 事件名称类型默认值onEdgesChange(changes: EdgeChange[]) >…

【C语言】动态分配内存

内存的五大分区 1、堆区&#xff08;heap&#xff09;——由程序员分配和释放&#xff0c; 若程序员不释放&#xff0c;程序结束时一般由操作系统回收。注意它与数据结构中的堆是两回事 2、栈区&#xff08;stack&#xff09;——由编译器自动分配释放 &#xff0c;存放函数的…

VM 安装Ubuntu20

1、VM 新建虚拟机 类型配置 - 典型 安装源选择 &#xff08;安装包获取&#xff1a;Ubuntu桌面系统 | Ubuntu&#xff09; 设置计算机名与用户账号密码 为虚拟机命一个名&#xff0c;设置虚拟机文件保存的位置 设置磁盘相关信息 最后一步&#xff0c;确定虚拟机的相关参数 设置…

如何安全的使用密码登录账号(在不知道密码的情况下)

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 1、打开工具&#xff0c;进入账号密码模块&#xff0c;如图 2、看到鼠标移动到密码那一栏有提示&#xff0c;按住Ctrl或者Alt点击或者双击就能复制内容&…