C++之String类(上)
片头
嗨!好久不见~ 今天我们来学习C++的Sting类,不过,在学习它之前,我们先来对STL库有一个简单的了解。
STL(standard template library--标准模板库),是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
STL的版本
- 原始版本
- P.J版本
- RW版本
- SGI版本

一、为什么学习string类?
1.1 C语言中的字符串
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP思想(面向对象编程),而且底层空间需要用户自己清理,稍不留神可能还会越界访问。
例如:C语言中的strcpy和strcat函数
strcpy:把一个字符串的内容复制到另一个字符串中
①空间必须自己提供,并且要保证,2块空间至少一样大
②如果如果目标字符串的空间不足以容纳源字符串,就会导致内存溢出的问题
③ 在使用strcpy函数时,应保证目标字符串有足够的空间
strcat:将一个字符串拼接到另一个字符串的末尾
①从头到尾找源字符串的'\0',如果源字符串很长,那么效率会非常低下
②目标字符串必须有足够的空间来容纳源字符串,否则会导致缓冲区溢出的问题
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单,方便,快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
二、标准库中的string类
2.1 string类
(1)字符串是表示字符序列的类
(2)标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性
(3)string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型)
(4)string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数
(5)注意:这个类独立于所使用的编码来处理字节:如果用来处理多字节或变成字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作
总结:
- string是表示字符串的字符串类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
- string在底层实际是:basic_string模板类的别名,typedef basic_string<char,char_traits,allocator>string
- 不能操作多字节或者变长字符的序列
在使用string类时,必须包含#include头文件以及using namespace std;
2.2 string类对象的常见构造
1.string类对象的常见构造
(constructor)函数名称 | 功能说明 |
string() 默认构造 (不传参就可以调用) | 构造空的string类对象,即空字符串 |
string(const char* s) 带参构造 | 用C-string来构造string类对象 |
string(const string& s) 拷贝构造 | 拷贝构造函数 |
string(size_t n,char c) | string类对象中包含n个字符c |
string(const string& s,size_t pos,size_t len = npos) | 从pos位置开始,拷贝len个字符去构造(初始化) |
string(const char* s,size_t n) | 拷贝字符串的前n个字符 |
我们先把前3个给试试看~
void test_string1() {string s1; //默认构造string s2("hello world!"); //带参构造string s3(s2); //拷贝构造//支持流插入和流提取cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cin >> s1;cout << s1 << endl;
}
运行结果如下:
(3)string(const string& s,size_t pos,size_t len = npos)函数
string(const string& s,size_t pos,size_t len = npos);
我们可以尝试一下:
那么,当len为npos是什么意思呢?
如果我们不传第3个参数的值,那么len就默认是npos,就从pos位置开始,拷贝42亿个字符。
但是根本不可能啊!所以,当出现这种省略第3个参数的情况,编译器默认拷贝到源字符串的结尾。
那如果我传递的第3个参数的值大于源字符串的长度,会怎么样?很明显,也是拷贝到字符串的结束位置。
void test_string2() {string s1("beautiful!");string s2(s1, 4, 6);string s3(s1, 4);string s4(s1, 4, 30);cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;
}
总结:
①len>后面的字符长度,有多少拷贝多少,拷贝到结尾
②缺省参数npos是整型最大值,一定大于后面的长度,不传第3个参数默认拷贝到结尾
(5)string(const char* s,size_t n)函数
我们测试一下:
void test_string3() {string s1("hello world!",5);cout << s1 << endl;
}
(6)string(size_t n,char c)函数
我们测试一下:
void test_string4() {string s1(10,'x');cout << s1 << endl;
}
小试牛刀:
看看这2个函数,是不是感觉很熟悉?
void test_string5() {//带参构造string s1("hello world!");//隐式类型转换string s2 = "hello world!";
}
其实,这2个看似相同,但是里面的逻辑是不一样的~
那如果我想用引用&符号呢?
void test_string6() {//带参构造string s1("hello world!");//隐式类型转换string s2 = "hello world!";//引用的是生成的临时对象//临时对象具有常性,因此,需要在前面添加constconst string& s3 = "hello world!";
}
此时,s3为临时对象的别名,因此,这里是直接构造,不需要优化
2.3 string类对象的容量操作
函数名称 | 功能说明 |
size | 返回字符串有效长度 |
length | 返回字符串有效长度 |
capacity | 返回空间总大小 |
empty | 检测字符串是否为空串,是返回true,否则返回false |
clear | 清空有效字符 |
reserve | 为字符串预留空间 |
resize | 将有效字符的个数改成n个,多出的空间用字符c填充 |
(1)size函数和length函数
size_t size() const;
返回字符串有效字符长度
void test_string7() {string s1("hello world");cout << s1.size() << endl;cout << s1.length() << endl;
}
注意:1.size()和length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下都是用size()
(2)capacity函数
size_t capacity() const;
返回字符串的容量
void test_string30() {string s1("hello");cout << s1.capacity() << endl;
}
(3)empty函数
bool empty() const;
检测字符串是否为空串,为空返回true,否则返回false
void test_string31() {string s1;string s2("hello");cout << s1.empty() << endl;cout << s2.empty() << endl;
}
(4)clear函数
void clear();
用于清空有效字符,不改变字符串容量的大小
void test_string32() {string s1("hello");s1.clear();cout << s1.size() << endl;
}
(5)reserve函数
void reserve(size_t n = 0);
为字符串预留空间
void test_string33() {string s1("hello");cout << s1.capacity() << endl;s1.reserve(10);cout << s1.capacity() << endl;s1.reserve(50);cout << s1.capacity() << endl;
}
如果n比原容量小,则不做改变
在vs上常常会开比n更大一些的空间
(6)resize函数
void resize(size_t n);
void resize(size_t n,char c);
将有效字符的个数修改为n,并且如果n大于原来的_size,多出来的地方用字符c填充,不改变字符串容量的大小
如果没有给出字符c,则用'\0'填充
void test_string34() {string s1("hello");cout << s1.c_str() << endl;s1.resize(2);cout << s1.c_str() << endl;s1.resize(10,'x');cout << s1.c_str() << endl;
}
2.4 string类对象的访问及遍历操作
函数名称 | 功能说明 |
operator[] | 返回pos位置的字符,const string类对象调用 |
begin+end | begin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器 |
rbegin+rend | begin获取一个字符的迭代器+rend获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
at | 返回字符串中pos位置的字符的引用 |
back | 返回字符串最后一个字符的引用 |
front | 返回字符第一个字符串的引用 |
如果,我想遍历s1字符串,该怎么做呢?
①首先,我们需要获取字符串的长度,运用size函数
②调用operator[]函数重载,可以使自定义类型像内置类型一样打印
void test_string8() {string s1("hello world");cout << s1.size() << endl;for (int i = 0; i < s1.size(); i++) {cout << s1[i] << " ";}cout << endl;
}
它的底层逻辑大概是这个样子:
引用返回,不仅可以减少拷贝,而且可以修改返回的对象
为什么可以用引用&返回呢?因为字符出了作用域并不会销毁,它是在堆上开辟的空间,返回的是堆上的字符,引用相当于是这个字符的别名
那么还有另一种方法遍历字符串么?有!使用iterator迭代器~
void test_string9() {string s1("hello world");cout << s1.size() << endl;//遍历方式1: 下标+[]for (int i = 0; i < s1.size(); i++) {cout << s1[i] << " ";}cout << endl;//遍历方式2: 使用iterator迭代器string::iterator it1 = s1.begin();while (it1 != s1.end()) {cout << *it1 << " ";++it1;}cout << endl;
}
此外,我们还可以使用范围for对字符串进行循环遍历~
void test_string9() {string s1("hello world");cout << s1.size() << endl;//遍历方式1: 下标+[]for (int i = 0; i < s1.size(); i++) {cout << s1[i] << " ";}cout << endl;//遍历方式2: 使用iterator迭代器string::iterator it1 = s1.begin();while (it1 != s1.end()) {cout << *it1 << " ";++it1;}cout << endl;//遍历方式3: 范围forfor (auto e : s1) {cout << e << " ";}cout << endl;
}
范围for的底层,它就是迭代器。因此,看上去有3种方法,实质上就只有2种---operator[]和迭代器
使用范围for的时候,是将s1里面的值依次拷贝给e,e相当于是s1里面的值的一份临时拷贝,对e进行修改不影响s1里面的值。如果我们需要通过e来改变s1里面的值,需要传引用&
//遍历方式3: 范围forfor (auto& e : s1) {e++;//将s1里面的每一个字符都+1cout << e << " ";}cout << endl;
当然啦,迭代器也分为被const修饰的和不被const修饰。
我们举一个例子,假如字符串s1被const修饰,也就是说,字符串s1的内容不允许改变。
所以,这里应该修改成这样:
void test_string11() {const string s1("hello world");string::const_iterator st1 = s1.begin();while (st1 != s1.end()) {cout << *st1 << " ";++st1;}cout << endl;
}
同时,因为是iterator是被const修饰的,因此它指向的内容不允许修改,也就是不能对*st1进行修改
还有一种更简便的方法,就是直接使用关键字auto,来帮助我们自动匹配类型
正着遍历,我们知道一些了,反向遍历呢?
那就要请出我们的一个朋友了---->rbegin函数和rend函数
就拿刚刚的s1字符串举一个例子吧~
我们可以尝试一下:
void test_string12() {string s1("hello world");string::reverse_iterator st1 = s1.rbegin();while (st1 != s1.rend()) {cout << *st1 << " ";++st1;}cout << endl;
}
同理, 如果s1被const修饰的话,reverse_iterator也应该被const修饰,变成const_reverse_iterator
(5)at函数
char& at(size_t pos);
const char& at(size_t pos)const;
返回字符串中pos位置的字符的引用
例如:
void test_string35() {string s1("hello");for (int i = 0; i < s1.size(); i++) {cout << s1.at(i);}cout << endl;
}
(6) back函数
char& back();
const char& back() const;
返回字符串最后一个字符的引用
例如:
void test_string36() {string s1("hello world!");cout << s1.back() << endl;
}
(7) front函数
char& front();
const char& front() const;
返回字符串第一个字符的引用
例如:
void test_string37() {string s1("hello world!");cout << s1.front() << endl;
}
好啦,接下来,我们想要对字符串s1里面的内容进行字典序排序,该怎么做呢?
首先,我们需要包含一个头文件#include<algorithm>
其次,我们需要使用sort函数来帮助我们完成字符排序
void test_string13() {string s1("hello world");cout << s1 << endl;//s1按字典序排序sort(s1.begin(), s1.end());cout << s1 << endl;
}
排完序的结果如下:
假设,我不想让第一个字符和最后一个字符不参与排序,只想让中间的字符进行排序,怎么做?
void test_string13() {string s1("hello world");cout << s1 << endl;//除了第一个和最后一个不参与,//其余的字符都要参与排序sort(++s1.begin(), --s1.end());cout << s1 << endl;
}
排序结果如下:
如果,我只想要前面的"hello"进行排序,那么区间就是下标[0,4],那么begin从0开始,end为5
void test_string13() {string s1("hello world");cout << s1 << endl;//前5个字符排序sort(s1.begin(), s1.begin() + 5);cout << s1 << endl;
}
片尾
今天我们学习了C++之stirng类(上),希望看完这篇文章能对友友们有所帮助!!!
求点赞收藏加关注!!!
谢谢大家!!!