【C++】STL--String

这一节主要总结string类的常见接口,以及完成了string类的模拟实现。

目录

标准库的String类 

string类常见接口

string类对象的常见构造

string析构函数:~string

string类对象的容量操作

string类对象的访问及遍历操作

string类对象的修改操作

string类非成员函数

string类的模拟实现

经典的string类问题

浅拷贝

深拷贝

传统版写法的String类

现代版写法的String类

string类的模拟实现


标准库的String类 

为了符合C++面向对象的特性,引入了string类,string是表示字符串的字符串类。下面说明一下string类的特点:

1.string是表示字符串的字符串类

2.string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数

3.string类独立于所使用的编码来处理字节,如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

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

string类常见接口

string类对象的常见构造

(重点)1.string()

默认构造,构造空的string类对象,即空字符串

(重点)2.string(const string& str)

拷贝构造,使用str拷贝构造string类对象

(重点)3.string(const char* s)

使用C-string来构造string类对象

4.string(const string& str, size_t pos, size_t len=npos)

从str的第pos个位置,取len个字符构造,如果len超过字符串长度,那么只取到字符串末尾,如果len未给出,那么采用默认参数npos(0xFFFFFFFF)

5.string(const char* s, size_t n)

使用C-string的前n个字符来构造string类对象

6.string(size_t n, char c)

string类对象包含n个字符c

void test_string1()
{string s0;string s1("hello world");string s2(s1);string s3(s1, 5, 3);string s4(s1, 5, 10);string s5(s1, 5);string s6(10, '$');cout << s0 << endl;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;cout << s6 << endl;
}

string析构函数:~string

在string类对象生命周期结束时,会被自动调用。

string类对象的容量操作

1.size()和length()

这两个均可以返回字符串有效字符长度,功能一样。可以通过size遍历字符串。有两个功能一样的函数是由于历史原因造成的!

2.max_size() 

返回string最大的大小

3.capacity()

返回string类对象的容量大小。

//查看扩容机制
void test_string4()
{string s;size_t sz = s.capacity();cout << "capacity change:" << sz << endl;cout << "making s growing" << endl;for (int i = 0; i < 100; i++){s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capacity changed" << sz << endl;}}
}

 

 虽然是15、31...,但是没有包括‘\0’,所以实际大小应该是16、32、48...,第一次是2倍扩容,以后每次是1.5倍扩容(VS平台下)。但是在Linux平台下,扩容机制不太一样:

这就说明,STL是一个标准,具体怎么实现由编译器决定! 

4.clear()

清除对象的数据,但是不会清空间。

 

 5.shrink_to_fit()

在clear()后,为了释放一些已开辟的空间,可以缩容

 

6.reserve()

如果提前知道要开辟空间的大小,可以用reserve()为字符串提前预留空间,防止频繁扩容,因为扩容一般是异地扩容,效率低下。

 避免了频繁扩容!

还有一个问题,reserve会不会缩容呢?不会的!当reserve()比capacity大,才会扩容!

7. resize()

调整字符串大小。

 

 如果采用第一种,那么默认用‘\0’插入,第二种用指定的char插入。

string类对象的访问及遍历操作

 1.operator[],返回pos位置的字符

string s1("hello world");
for (size_t i = 0; i < s1.size(); i++)
{cout << s1[i] << " ";
}

2.迭代器,begin+end,可以通过迭代器的方式遍历字符串,begin获取第一个字符的迭代器,end获取最后一个字符下一个位置的迭代器

string::iterator it1 = s1.begin();while (it1 != s1.end()){cout << *it1 << " ";++it1;}

我们感觉到迭代器很像指针,但是并不是指针!

3.rbegin+rend,支持倒着遍历,循环中用++rit,而不是--rit,对于反向迭代器而言,++就是倒着走

string s1("hello world");
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{cout << *rit << " ";++rit;
}

除了正向和反向的正常iterator外,还有正向和反向的const_iterator,是用来为const string构造迭代器,其特点是只能读不能写

//只读
const string s3("hello world");
string::const_iterator it3 = s3.begin();
while (it3 != s3.end())
{//*it3 += 3;//报错,因为只读cout << *it3 << " ";
}
cout << endl;
//string::const_reverse_iterator

4.用范围for遍历字符串 

for (auto s : s1){cout << s << " ";}

 但是范围for并没有什么特别之处,其底层是迭代器!

string类对象的修改操作

1.pushback()

在字符串末尾尾插字符

2.append()

在字符串后追加字符串

3.operator+=

在字符串后追加字符串str

4.assign()

用新内容覆盖原字符串

5.insert()

在某一个位置插入字符串

注意:insert()尽量不要使用,因为在使用过程中会挪动数据,效率低。

6.erase()

删除字符

同样,erase()函数能不用就不用。 

7.replace()

字符的替换 

8.find()

查找某个字符的位置

小练习:将一个字符串中的空格替换成“%20”。

总结一下,insert、erase、replace能少用就少用,因为基本都要挪动数据,效率不高。

9.c_str

返回C格式字符串

在C++程序中,我们有可能会调用C语言的函数,比如fopen函数,但是在程序中文件名可能是string对象,需要转换成C字符串才行。

void test_string()
{string filename("test.cpp");FILE* fout = fopen(filename.c_str(), "r");
}

10.find

找到某一个字符或字符串第一次出现的位置

例如:获取后缀

void test_string()
{string s1("test.cpp");size_t pos = s1.find('.');if (pos != string::npos){string suffix = s1.substr(pos);cout << suffix << endl;}else{cout << "没有后缀" << endl;}
}

11.rfind

从后往前找某一个字符或字符串第一次出现的位置

例如:获取后缀“file.c.tar.zip”的zip

void test_string()
{string s1("test.c.tar.zip");size_t pos = s1.rfind('.');if (pos != string::npos){string suffix = s1.substr(pos);cout << suffix << endl;}else{cout << "没有后缀" << endl;}
}

string类非成员函数

1.operator+

尽量少用,因为传值返回,导致深拷贝效率低

2.operator>>&&operator<<

输入和输出运算符重载

3.getline

获取一行字符串。弥补了cin>>的缺点,cin遇到了空格或者换行就提取结束。

4.==  != > >= < <=

大小比较

string类的模拟实现

经典的string类问题

首先来看一个经典关于浅拷贝的问题:

class String
{
public:
/*String()
:_str(new char[1])
{*_str = '\0';}
*/
//String(const char* str = "\0") 错误示范
//String(const char* str = nullptr) 错误示范,使用strlen时会涉及解引用
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指向同一块内存空间,在释放空间时,同一块空间会被释放两次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

浅拷贝

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

可以用深拷贝解决浅拷贝问题,也就是:每个对象都有一份独立的资源,不要和其他对象共享

深拷贝

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

传统版写法的String类

namespace ghs
{class string{public:string(const char* str=""):_size(strlen(str)){_str = new char[_size + 1];strcpy(_str, str);_capacity = _size;}string(const string& str){_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str._size;_capacity = str._capacity;}//s1 = s2string& operator=(const string& str){char* tmp = new char[str._capacity + 1];strcpy(tmp, str._str);delete[] _str;_str = tmp;_size = str._size;_capacity = str._capacity;return *this;}~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}private:char* _str = nullptr;size_t _size = 0; size_t _capacity = 0;public:static const int npos;};const int string::npos = -1;
}

在传统版的写法中,拷贝构造和赋值构造都是通过自己手动开空间,然后使用strcpy函数拷贝完成的,下面的现代写法在实现上更为简洁: 

现代版写法的String类

namespace ghs
{class string{public:string(const char* str=""):_size(strlen(str)){_str = new char[_size + 1];strcpy(_str, str);_capacity = _size;}string(const string& s){string tmp(s._str);swap(tmp);}//s1 = s2//string& operator=(const string& str)//{//	string ss(str);//	swap(ss);//	return *this;//}//上面的再优化,这种更推荐string& operator=(string ss)//传值传参,调用拷贝构造{swap(ss);return *this;}~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}private:char* _str = nullptr;size_t _size = 0; size_t _capacity = 0;public:static const int npos;};const int string::npos = -1;
}

在现代写法中,拷贝构造是通过调用string(const char* str="")构造函数来生成临时的string对象(tmp),再将*this和tmp交换,完成拷贝。在赋值构造中,使用传值传参,其实调用拷贝构造,生成临时对象ss,再将ss和*this交换,完成拷贝。

总结:传统版的现代版的效率一样,只是现代版调用了之前已经实现好的函数,所以看起来更加简洁!

string类的模拟实现
 

namespace ghs
{class string{public:/*string():_str(nullptr),_size(0),_capacity(0){}*//*string():_str(new char[1]),_size(0),_capacity(0){_str[0] = '\0';}*/typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}string(const char* str=""):_size(strlen(str)){_str = new char[_size + 1];strcpy(_str, str);_capacity = _size;}//string(const string& str)//{//	_str = new char[str._capacity + 1];//	strcpy(_str, str._str);//	_size = str._size;//	_capacity = str._capacity;//}string(const string& s){string tmp(s._str);swap(tmp);}//s1 = s2//string& operator=(const string& str)//{//	char* tmp = new char[str._capacity + 1];//	strcpy(tmp, str._str);//	delete[] _str;//	_str = tmp;//	_size = str._size;//	_capacity = str._capacity;//	return *this;//}string& operator=(const string& str){string ss(str);swap(ss);return *this;}//上面的再优化,这种更推荐//string& operator=(string ss)//传值传参,调用拷贝构造//{//	swap(ss);//	return *this;//}const char* c_str()const{return _str;}size_t size()const{return _size;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t sz){if (sz > _capacity){char* p = new char[sz + 1];strcpy(p, _str);delete[] _str;_str = p;_capacity = sz;}}void push_back(char ch){//扩容2倍/*if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';*/insert(_size, ch);}void append(const char* str){//扩容//size_t len = strlen(str);//if (_size + len > _capacity)//{//	reserve(_size + len);//}//strcpy(_str + _size, str);///*for (size_t i = 0; i < len; i++)//{//	push_back(str[i]);//}*///_size += len;insert(_size, str);}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}size_t capacity()const{return _capacity;}void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}for (size_t end = _size+1; end > pos; end--){_str[end] = _str[end - 1];}_str[pos] = ch;_size++;}void insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(len + _size);}for (size_t end = _size+len; end > pos+len-1; end--){_str[end] = _str[end-len];}/*for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}*/strncpy(_str + pos, str, len);_size += len;}void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos ||  len>=_size-pos){_size = pos;_str[_size] = '\0';}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}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;}}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}size_t find(char ch, size_t pos = 0)const{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t find(const char* str, size_t pos = 0)const{assert(pos < _size);char* tmp = strstr(_str+pos, str);if (tmp != nullptr){return tmp - _str;}else{return npos;}}string substr(size_t pos = 0, size_t len = npos){string s;if (len == npos || len >= _size-pos){for (size_t i = pos; i < _size; i++){s += _str[i];}}else{for (size_t i = pos; i < pos + len; i++){s += _str[i];}}return s;}void clear(){_size = 0;_str[_size] = '\0';}/*bool operator==(const string& str){int ret = strcmp(_str, str._str);return ret == 0;}*/~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}private:char* _str = nullptr;size_t _size = 0; size_t _capacity = 0;public:static const int npos;};const int string::npos = -1;void swap(string& x, string& y){x.swap(y);}bool operator==(const string& s1,const string& s2){int ret = strcmp(s1.c_str(), s2.c_str());return ret == 0;}bool operator<(const string& s1, const string& s2){int ret = strcmp(s1.c_str(), s2.c_str());return ret < 0;}bool operator<=(const string& s1, const string& s2){return s1 < s2 || s1 == s2;}bool operator>(const string& s1, const string& s2){return !(s1 <= s2);}bool operator>=(const string& s1, const string& s2){return !(s1 < s2);}ostream& operator<<(ostream& out, const string& str){for (auto ch : str){out << ch; }return out;}istream& operator>>(istream& in, string& str){char ch;str.clear();//in >> ch;ch = in.get();char buff[100];size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 99){buff[99] = '\0';str += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';str += buff;}return in; }istream& getline(istream& in, string& str){char ch;str.clear();char buff[100];//in >> ch;/*ch = in.get();while (ch != '\n'){str += ch;ch = in.get();}*/ch = in.get();size_t i = 0;while (ch != '\n'){buff[i++] = ch;if (i == 99){buff[99] = '\0';str += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';str += buff;}return in;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/537610.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

防火墙中的SNAT 与DNAT

SNAT&#xff1a;让内网机器可以访问外网服务器 DNAT:让外网机器可以访问内网服务器 SNAT的原理 源地址转换&#xff0c;根据指定条件修改数据包的源IP地址&#xff0c;通常被叫做源映射 数据包从内网发送到公网时&#xff0c;SNAT会把数据包的源IP由私网IP转换成公网IP 当响应…

前端项目,个人笔记(一)【定制化主题 + 路由设计】

目录 1、项目准备 1.1、项目初始化 1.2、elementPlus按需引入 注&#xff1a;使用cnpm安装elementplus及两个插件&#xff0c;会报错&#xff1a;vueelement-plus报错TypeError: Cannot read properties of null (reading isCE ) &#xff0c;修改&#xff1a; 测试&#…

旅游管理系统|基于SpringBoot+ Mysql+Java+Tomcat技术的旅游管理系统设计与实现(可运行源码+数据库+设计文档+部署说明+视频演示)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 用户功能 管理员功能登录前台功能效果图 系统功能设计 数据库E-R图设计 lunwen参考 …

【MySQL】MySQL索引特性

文章目录 一、没有索引&#xff0c;可能会有什么问题二、认识磁盘1.MySQL与存储2.磁盘 三、MySQL与磁盘交互基本单位四、索引的理解五、索引操作1.创建主键索引2.创建唯一索引3.创建普通索引4.查询索引5.删除索引6.使用案例7.创建全文索引8.索引创建原则 一、没有索引&#xff…

前后端交互理解 简易表白墙(servlet)

前后端交互理解 简易表白墙&#xff08;servlet&#xff09; 文章目录 前后端交互理解 简易表白墙&#xff08;servlet&#xff09;后端核心内容前后端交互接口约定后端代码展示 上期介绍过 Servlet API &#xff0c;本篇文章目的是借助 servlet 做出一个完整的网站。在一个网站…

GooGLE留痕助手是什么?#川圣SEO#蜘蛛池

baidu搜索&#xff1a;如何联系八爪鱼SEO&#xff1f; baidu搜索&#xff1a;如何联系八爪鱼SEO&#xff1f; baidu搜索&#xff1a;如何联系八爪鱼SEO&#xff1f; Google留痕助手&#xff0c;也被称为Google搜索留痕&#xff0c;是一种搜索引擎优化&#xff08;SEO&#x…

线性代数 --- 特征值与特征向量(下)

特征值与特征向量 Eigen Values & Eigen Vectors Part III:如何求解特征向量与特征值 The Key Equation 对于一般矩阵A&#xff0c;如何找到他的特征值与特征向量&#xff1f; Step I: Find λ first! 首先&#xff0c;我们有方程&#xff1a; 但这里有两个未知数&…

【gpt实践】同时让chatgpt和claude开发俄罗斯方块

最近chatgpt和claude都在使用&#xff0c;其实大部分日常使用场景表现都没有相差太多&#xff0c;想搞一个有趣的小实验&#xff0c;如果同时让chatgpt和claude开发俄罗斯方块谁会表现的更好呢&#xff0c;说干就干&#xff01; prompt 我选择了用英文描述&#xff0c;毕竟英…

图像处理与图像分析—图像统计特性的计算(纯C语言实现灰度值显示)

根据输入的灰度图像&#xff0c;分别计算图像的均值、方差等统计特征&#xff0c;并计算图像的直方图特征并以图形方式显示图像的直方图&#xff08;用C或C语言实现&#xff09;。 学习将会依据教材图像处理与图像分析基础&#xff08;C/C&#xff09;版内容展开 在上个笔记中&…

windows批处理脚本(bat脚本指令)

一、常用批处理内部命令简介 批处理文件是将一系列命令按一定的顺序集合为一个可执行的文本文件&#xff0c;其扩展名为BAT或者CMD。这些命令统称批处理命令。 可以在键盘上按下CtrC组合键来强行终止一个批处理的执行过程。 二、常用命令 1、rem 和 ∷ 2、echo 和 3、…

【Python】成功解决NameError: name ‘plt‘ is not defined

【Python】成功解决NameError: name ‘plt’ is not defined &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您…

Java基础_内部类

文章目录 1.基本介绍1.定义&#xff1a;2.基本语法&#xff1a;3.内部类的分类 2.局部内部类1.快速入门2.局部内部类的使用 3.匿名内部类1.匿名内部类实现接口2.匿名内部类继承抽象类3.匿名内部类细节4.匿名内部类最佳实践1.匿名内部类作为实参传入函数2.匿名内部类课堂练习 4.…