当前位置: 首页 > news >正文

【C++闯关笔记】STL:string的学习和使用(万字精讲)

​系列文章目录

第零篇:从C到C++入门:C++有而C语言没有的基础知识总结-CSDN博客

第一篇:【C++闯关笔记】封装①:类与对象-CSDN博客

第二篇:【C++闯关笔记】封装②:友元与模板-CSDN博客

第三篇:【C++闯关笔记】STL:string的学习和使用(万字精讲)-CSDN博客


目录

​系列文章目录

前言

一、什么是string类?

二、使用string:string类的常用函数

1.创建string对象

2.修改string对象

3.遍历string对象

 1)operator[ ]

2)迭代器遍历

3)范围for

4.string类对象容量相关函数

5.其他常用函数

1.查找字符或字符串:find和rfind

2.截取子字符串:substr

3.返回C格式字符串:c_str

4.获取一行字符:getline

三、模拟实现string

1.private成员

2.构造函数与析构函数

2.修改类函数

3.遍历类函数

4.其他常用函数

完整代码

总结



前言

什么是STL?

STL是C++标准库的重要组成部分,是一个可复用的组件库,它里面包罗有众多数据结构与算法。STL由六大组件构成:容器、算法、迭代器、函数对象、适配器、内存分配器这 6 部分构成,其中容器与算法最为重要,后面 4 部分都是为它两服务的。这些组件相互协作,共同实现了STL的强大功能。

为什么要学习STL?

STL是C++中的优秀作品代表,如果能够熟练的使用STL乃至了解底层原理,那么许多底层的数据结构以及算法都不需要自己重新编码,可以直接复用前人的优秀成果,相当于站在巨人的肩膀上,大幅提升开发速度。

本系列将从STL的各个容器与算法着手,先详细介绍它们使用方法然后通过自实现的方式理解它们的底层逻辑。

本文主要介绍string的学习与使用。


一、什么是string类?

在C语言中,字符串是char[ ]数组以'\0'结尾的一些字符的集合,C标准库有一些str系列的库函数专门用以操作这些字符串。 但是这些库函数与字符串是分离开的,不太符合C++面对对象的思想。

于是在C++中将原C语言中的字符串与相关函数并结合数据结构顺序表,将它们整合打造成为一个类——用以专门表示字符串的字符串类即string类,以便使用。

对于刚接触string的新人而言,可以将string先理解为一种特化的顺序表——专门为存储和操作字符数据而设计。结合数据结构顺序表的原理,先在脑海中有个大致的模型,之后在学习string相关接口时更加游刃有余。

string类与C语言的数组有什么区别?

①char s[ ]本质上还是数组,长度固定;string长度不限

②string本质是类,其中包含有大量函数,方便操作;

③string中重载了如‘+’ ’>' '<"等符号,便于操作;

可以将string理解为包含了大量函数的char s[ ]。


二、使用string:string类的常用函数

在使用string类时,必须包含#include<string>头文件以及using namespace std;

1.创建string对象

在日常编码中,我们常用到的四种方法创建string对象。

string类对象的常见构造函数

构造函数功能说明
string()构造空的string类对象,即空字符串
string(const char* s)用C语言的字符串来构造string类对象
string(size_t n, char c)创建的string类对象中包含n个字符c
string(const string&s)拷贝构造函数,将已有的对象拷贝给一个新的对象

示例

	string s1;string s2("hello String");string s3(7, 'a');string s4(s3);

解释:string s3(7,‘a'),这里将’a‘字符复制了7遍后存入字符串对象s3.

string的构造函数除了上述最常用的四种外还包括:

2.修改string对象

string类对象的常见修改函数

①增加类型函数

函数名称功能说明
push_back('c')在字符串后尾插字符c
appden(“字符串” )在字符串末尾追加一个字符串
operator+=将两个字符串合并

示例

int main()
{string s1("he");string s2("String");s1.push_back('l');s1.append("lo ");s1 += s2;cout << s1 << endl;//打印结果为“hello String”return 0;
}

②减删类型函数

函数名称功能说明
pop_back( )删除字符串末尾字符

erase(size_t pos,size_t n);

erase(size_t pos)

从索引pos位置开始,往后删除n个字符(包括pos);

将pos位置及以后的字符删除。

clear( )清空整个字符串

示例

int main()
{string s1("hello string");s1.pop_back();s1.erase(7, 4);s1.erase(5);s1.clear();return 0;
}

细节注意:

1.确保你的删除位置pos在 [0,size()] 范围内。

2.调用 pop_back() 之前,最好检查一下检查一下字符串是否为空 !str.empty( );

3.遍历string对象

string对象常用的遍历方法包括三种:

函数名称功能说明
operator[pos ]返回pos位置的字符

begin+end;

rbegin+rend;

迭代器遍历

for(auto:str)范围for

 1)operator[ ]

string重载了[ ],可以通过下标访问。

使用示例

2)迭代器遍历

这里的迭代器可以理解为封装过后的“指针”;

(1)正着遍历:begin+end

使用语法

string::iterator 变量名 = 对象.begin( );(end同理)

string::iterator it = s.begin();//iterator为关键字,it为变量名

使用示例

(2)反向遍历:rbegin+rend

使用语法

string::reverse_iterator 变量名 = 对象.rbegin( );(rend同理)

string::reverse_iterator rit = s.rbegin();//reverse_iterator为关键字

使用示例

3)范围for

语法

for(auto 变量名 : 对象)

auto,自动推测变量类型,中间由‘ :’隔开

(这里简述auto与范围for,主要目的是能介绍这种遍历方法,有兴趣的读者可参看:)从C到C++入门:C++有而C语言没有的基础知识总结-CSDN博客

范围for的底层是由迭代器实现的。

使用示例

4.string类对象容量相关函数

函数名功能
size( )返回字符串有效字符长度
length( )返回字符串有效字符长度
capacity( )返回该对象占用空间大小
empty()检测该string对象是否为空,是返回true,否则返回false
reserve(size_t n)将字符串空间拓展为n
resize(size_t n,char c)将有效字符的个数改成成n个,多出的空间用字符c填充

说明:

①size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size();

②resize与reserve的核心区别

  • resize(n,c):改变字符串的“逻辑长度” (即size())。 它会直接让你看到的字符串内容变长或变短。当 n 比目前字符串长度长时,多出空间用字符c填充;当 n 比目前字符串长度短时,会截取字符串长度至n。

  • reserve(n):改变字符串的“总容量” (即capacity())。 它只是为你将来要放入的字符预先申请一大块内存(可能不会严格申请至 n 空间),但不会改变你当前看到的字符串内容。

使用示例

int main()
{string s("hello");cout <<s <<' ' << s.size() << ' ' << s.capacity() << endl;s.reserve(64);//将总空间大小拓展为64,多出的部分为空白;s.resize(32, 'a');//将实际使用空间拓展至32,多出部分填字符'a';cout << s << ' ' << s.size() << ' ' << s.capacity() << endl;return 0;
}

陷阱注意

①reserve() 不会初始化内存

reserve()只会申请空间,不会将申请出来的空间赋值。如果通过 [] 操作符访问 [size(), capacity()) 范围内的内存是未定义行为

reserve() 只增不减

reserve(n) 是将容量拓展至 至少是 n。如果你传入一个比当前 capacity() 还小的值,是不会实现的。

resize(n)   如果未指定初始化字符,则新添加的字符会被默认初始化为空字符 '\0'。

5.其他常用函数

1.查找字符或字符串:find和rfind

作用:在字符串中寻找带查找字符或字符串出现的位置,找到则返回其首下标,没有则返回-1。

使用方法:

s.find("你要找到字串",pos);pos,指从该位置往后找,若没有输入pos则默认从头开始找。

s.rfind("你要找到字串",pos);pos是指从该位置往前找,若没有输入pos则默认从头开始找。

示例:

注意:若没有输入pos,则find默认从首字符开始找;rfind从尾字符开始找。

2.截取子字符串:substr

作用:截取子字符串,当长度超过原字符串时,截取已有剩余的全部字符。

使用方法:

s.substr(pos,len);len是你要截取的长度,若不给len,则默认从pos位置取完剩下的。

示例:

3.返回C格式字符串:c_str

为了保持与C语言的兼容性,c_str函数便孕育而出了。

作用:返回一个指向当前字符串内容的、以空字符 '\0' 结尾的 C风格字符串(即 const char*)。

使用方法:

const char *s =对象.c_str();

示例:

4.获取一行字符:getline

在平常通过 cin 输入字符串时,如果遇到空格就会停止输入,如

int main()
{string s;//输入“hello world”;cin >> s;//只打印出“hello”cout << s << endl;return 0;
}

getline函数的作用就是将一整行输入都存入字符串。

使用方法:

getline(cin,string对象);//注意getline并非string类的成员函数!

示例


三、模拟实现string

1.private成员

类似于数据结构顺序表:

char* _s用于存储字符串;

size_t _capacity表示在堆上申请的空间数量;

size_t _size表示实际使用了多少空间。

size_t 可以看成没有负数的int型。

namespace karsen
{class string{private:size_t _size;size_t _capacity;char* _s;};
}

2.构造函数与析构函数

两种构造函数以实现:①空对象;②括号内赋初值;③创建的string类对象中包含n个字符c。

以及拷贝构造函数,析构函数。

注意:

①new 与 delete,new[ ]与delete[ ]配合使用,否则无法正确释放空间。

②必须实现拷贝构造函数,因为默认拷贝构造是按字节拷贝的“浅拷贝”,他会将原对象的数据原模原样的拷贝进新对象,当析构函数释放空间时就会造成释放两次导致报错。

namespace karsen
{class string{public://传入字符串构建string(const char* s = nullptr){//空对象给它两个空间if (!s || s[0] == '\0'){_s = new char[2];_s[0] = '\0';_size = 0;_capacity = 2;return;}_size = strlen(s);_capacity = _size + 1;_s = new char[_size + 1];strcpy(_s, s);}//创建的string类对象中包含n个字符cstring(int n, char c){if (n > 0){_size = n;_s = new char[_size + 1];for (int i = 0; i < n; ++i)_s[i] = c;_s[_size] = '\0';}}string(const string& s){_size = s._size;_capacity = s._capacity + 1;_s = new char[_capacity];strcpy(_s, s._s);}~string(){delete[] _s;_size = _capacity = 0;_s = nullptr;}private:size_t _size;size_t _capacity;char* _s;};

2.修改类函数

注意在push_back中,当_size==_capacity-1时就要扩容,因为push之后需要设置_s[_size] = '\0';防止越界。

erase函数使用了函数重载,以满足在不传入参数二时的删除工作。

    	void push_back(char c){if (_size == _capacity-1){size_t newcapa = _capacity * 2;reserve(newcapa);}_s[_size++] = c;_s[_size] = '\0';}void reserve(size_t n){if (n > _capacity){_capacity = n;char* news = new char[_capacity];strcpy(news, _s);delete[] _s;_s = news;}}void pop_back(){if (_size > 0){_size--;}}void append(const char* s){if (s){size_t len = strlen(s);size_t newsize = len + _size;if (newsize > _capacity){reserve(newsize);}strcpy(_s + _size, s);_size += len;//_s[_size] = '\0';//+=那可能用到}}string& operator+=(const char* s){append(s);return *this;}string& operator+=(const char s){push_back(s);return *this;}void erase(size_t pos){_size = pos;_s[_size] = '\0';}void erase(size_t pos, int n){if (n > 0){size_t end = pos + n;if (end < _size){while (end < _size){_s[pos++] = _s[end++];}_size = pos;_s[_size] = '\0';}else{erase(pos);}}else return;}void clear(){_size = 0;_s[_size] = '\0';}

3.遍历类函数

注意:范围for底层调用的是迭代器。

		char& operator[](size_t n){if (n < _size){return _s[n];}else{//越界抛异常throw std::out_of_range("Index out of range");}}//用于const型对象const char& operator[](int& n)const{if (n < _size){return _s[n];}else{//越界抛异常throw std::out_of_range("Index out of range");}}typedef char* iterator;//迭代器iterator begin(){return _s;}iterator end(){return _s + _size;}

4.其他常用函数

解释:

①resize分两大类:n是否大于capacity。在n小于capacity一类中又可分为:n是否大于size。

②通过重载find,以达到查找单个字符与字符串的目的。在第二个find函数中,利用指针变量本质存储的是地址数据且每个地址空间正好等于char大小,将前后地址相减取得间距。

③在substr函数的第一个if中,如果len的值没变还是-1,或者pos+len的大小超过实际对象中存储字符串的大小,那么将pos之后的字符串(_s+pos)传递给一个临时变量返回。

如果不满足第一个if,那么po与len之间一定在已有的数据中,将这些数据push进临时变量再返回。

注意:不要返回临时变量的引用。

		size_t size(){return _size;}size_t capacity(){return _capacity;}bool empty(){if (_size > 0)return true;else return false;}void resize(size_t n, char c){if (n > _capacity){reserve(n);}if (n > _size){while (_size < n){_s[_size++] = c;}}else{_size = n;_s[_size] = '\0';}}int find(const char a,int pos = 0){if (pos < 0 || pos > _size)return -1;for (int i = pos; i < _size; ++i){if (_s[i] == a)return i;}return -1;}int find(const char* a, int pos = 0){if (pos < 0 || pos > _size)return -1;const char* temp = strstr(_s + pos, a);if (!temp)return -1;else return (int)(temp - _s);}//会传下标int rfind(const char a, int pos){if (pos < 0 || pos > _size){return -1;}for (int i = pos; i > -1 ; --i){if (_s[i] == a)return i;}return -1;}//不会传下标int rfind(const char a, size_t pos = 1){pos = _size;for (size_t i = pos; i > -1; --i){if (_s[i] == a)return (int)i;}return -1;}const char* c_str(){return _s;}string substr(int pos, int len = -1){if (pos<0 || pos>_size)return nullptr;if (len == -1 || pos + len >=_size){string ret(_s + pos);return ret;}else{string ret;for (int i = pos; i < pos + len; ++i){if (i < _size)ret.push_back(_s[i]);else break;}return ret; }}//重载输入输出符std::ostream& operator<<(std::ostream& out, const string& s){out << s._s;return out;}std::istream& operator>>(std::istream& in, string& s){s.clear(); char c;while (in.get(c)){if (c == '\n' || c ==' ')break;s.push_back(c);}//在push_back中已经处理过了,这里就不用,也不能(越界)//s[s._size] = '\0';return in;}

完整代码

如果不需要namespace,可以直接将其中的代码复制到外部。

//#define  _CRT_SECURE_NO_WARNINGS//这个宏用于去除vs编译器的不安全提醒
#pragma once
#include<iostream>namespace karsen
{typedef char* iterator;typedef char* reverse_iterator;class string{friend std::ostream& operator<<(std::ostream& out, const string& s);friend std::istream& operator>>(std::istream& in,string& s);public://传入字符串构建string(const char* s = nullptr){if (!s || s[0] == '\0'){_s = new char[2];_s[0] = '\0';_size = 0;_capacity = 2;return;}_size = strlen(s);_capacity = _size + 1;_s = new char[_size + 1];strcpy(_s, s);}//创建的string类对象中包含n个字符cstring(int n, char c){if (n > 0){_size = n;_s = new char[_size + 1];for (int i = 0; i < n; ++i)_s[i] = c;_s[_size] = '\0';}}string(const string& s){_size = s._size;_capacity = s._capacity + 1;_s = new char[_capacity];strcpy(_s, s._s);}void push_back(char c){if (_size == _capacity-1){size_t newcapa = _capacity * 2;reserve(newcapa);}_s[_size++] = c;_s[_size] = '\0';}void pop_back(){if (_size > 0){_size--;}}size_t size(){return _size;}size_t capacity(){return _capacity;}void append(const char* s){if (s){size_t len = strlen(s);size_t newsize = len + _size;if (newsize > _capacity){reserve(newsize);}strcpy(_s + _size, s);_size += len;//_s[_size] = '\0';//+=那可能用到}}string& operator+=(const char* s){append(s);return *this;}string& operator+=(const char s){push_back(s);return *this;}char& operator[](size_t n){if (n < _size){return _s[n];}else{//越界抛异常throw std::out_of_range("Index out of range");}}const char& operator[](int& n)const{if (n < _size){return _s[n];}else{//越界抛异常throw std::out_of_range("Index out of range");}}void erase(size_t pos){_size = pos;_s[_size] = '\0';}void erase(size_t pos, int n){if (n > 0){size_t end = pos + n;if (end < _size){while (end < _size){_s[pos++] = _s[end++];}_size = pos;_s[_size] = '\0';}else{erase(pos);}}else return;}void reserve(size_t n){if (n > _capacity){_capacity = n;char* news = new char[_capacity];strcpy(news, _s);delete[] _s;_s = news;}}void clear(){_size = 0;_s[_size] = '\0';}//迭代器iterator begin(){return _s;}iterator end(){return _s + _size;}//方向迭代需要重载++变--//reverse_iterator rend()//{//	return _s;//}//reverse_iterator rbegin()//{//	return _s + _size-1;//}bool empty(){if (_size > 0)return true;else return false;}void resize(size_t n, char c){if (n > _capacity){reserve(n);}if (n > _size){while (_size < n){_s[_size++] = c;}}else{_size = n;_s[_size] = '\0';}}int find(const char a,int pos = 0){if (pos < 0 || pos > _size)return -1;for (int i = pos; i < _size; ++i){if (_s[i] == a)return i;}return -1;}int find(const char* a, int pos = 0){if (pos < 0 || pos > _size)return -1;const char* temp = strstr(_s + pos, a);if (!temp)return -1;else return (int)(temp - _s);}//会传下标int rfind(const char a, int pos){if (pos < 0 || pos > _size){return -1;}for (int i = pos; i > -1 ; --i){if (_s[i] == a)return i;}return -1;}//不会传下标int rfind(const char a, size_t pos = 1){pos = _size;for (size_t i = pos; i > -1; --i){if (_s[i] == a)return (int)i;}return -1;}const char* c_str(){return _s;}string substr(int pos, int len = -1){if (pos<0 || pos>_size)return nullptr;if (len == -1 || pos + len >=_size){string ret(_s + pos);return ret;}else{string ret;for (int i = pos; i < pos + len; ++i){if (i < _size)ret.push_back(_s[i]);else break;}return ret; }}~string(){delete[] _s;_size = _capacity = 0;_s = nullptr;}public://以下作为组件函数使用private:size_t _size;size_t _capacity;char* _s;};std::ostream& operator<<(std::ostream& out, const string& s){out << s._s;return out;}std::istream& operator>>(std::istream& in, string& s){s.clear(); char c;while (in.get(c)){if (c == '\n' || c ==' ')break;s.push_back(c);}//在push_back中已经处理过了,这里就不用,也不能(越界)//s[s._size] = '\0';return in;}}


总结

本文先详细介绍了什么是STL以及string是什么,然后再介绍了string的一些常用函数的使用方法,最后通过模拟实现string以求深入理解string。

整理不易,希望能帮到你。

读完点赞,手留余香~

http://www.dtcms.com/a/348895.html

相关文章:

  • 开发软件安装记录
  • Kubernetes v1.34 前瞻:资源管理、安全与可观测性的全面进化
  • golang6 条件循环
  • R语言rbind()和cbind()使用
  • 信贷策略域——信贷产品策略设计
  • 【数据结构】排序算法全解析
  • 【链表 - LeetCode】206. 反转链表【带ACM调试】
  • HTTP URL 详解:互联网资源的精准地址
  • 当AI遇上终端:Gemini CLI的技术魔法与架构奥秘
  • 在 vue3 和 vue2 中,computed 计算属性和 methods 方法区别是什么
  • 打响“A+H”双重上市突围战,云天励飞实力如何?
  • JUC并发编程07 - wait-ify/park-un/安全分析
  • 《CF1120D Power Tree》
  • Spirng Cloud Alibaba主流组件
  • 【ElasticSearch】springboot整合es案例
  • 企业出海第一步:国际化和本地化
  • springBoot如何加载类(以atomikos框架中的事务类为例)
  • JavaScript数据结构详解
  • Docker知识点
  • 【数据分享】中国地势三级阶梯矢量数据
  • 【无标题】对六边形拓扑结构中的顶点关系、着色约束及量子隧穿机制进行严谨论述。
  • 深度剖析Spring AI源码(七):化繁为简,Spring Boot自动配置的实现之秘
  • MySQL--基础知识
  • 基础篇(下):神经网络与反向传播(程序员视角)
  • 多机多卡微调流程
  • Node.js依赖管理与install及run命令详解
  • 【文献阅读】生态恢复项目对生态系统稳定性的影响
  • CI/CD持续集成及持续交付详解
  • Jwt令牌设置介绍
  • 关于熵减 - 电子圆柱