自学内容网 自学内容网

C++之String类(上)

片头

嗨!好久不见~ 今天我们来学习C++的Sting类,不过,在学习它之前,我们先来对STL库有一个简单的了解。

STL(standard template library--标准模板库),是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

STL的版本

  • 原始版本
Alexander Stepanov Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许
任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原
始版本一样做开源使用。 HP 版本 -- 所有 STL 实现版本的始祖。
  • P.J版本
P. J. Plauger 开发,继承自 HP 版本,被 Windows Visual C++ 采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
  • RW版本
Rouge Wage 公司开发,继承自 HP 版本,被 C+ + Builder 采用,不能公开或修改,可读性一
般。
  • SGI版本
Silicon Graphics Computer Systems Inc 公司开发,继承自 HP 版 本。被 GCC(Linux) 采用,可
移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。 我们后面学习
STL 要阅读部分源代码,主要参考的就是这个版本
STL有六大组件

一、为什么学习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)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作

总结:

  1. string是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
  3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char,char_traits,allocator>string
  4. 不能操作多字节或者变长字符的序列

在使用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;
}

运行结果如下:


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个参数默认拷贝到结尾


string(const char* s,size_t n)函数

我们测试一下:

void test_string3() {
string s1("hello world!",5);
cout << s1 << endl;
}


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!";

//引用的是生成的临时对象
//临时对象具有常性,因此,需要在前面添加const
const 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+endbegin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器
rbegin+rendbegin获取一个字符的迭代器+rend获取最后一个字符下一个位置的迭代器
范围forC++11支持更简洁的范围for的新遍历方式

如果,我想遍历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: 范围for
for (auto e : s1) {
cout << e << " ";
}
cout << endl;
}

范围for的底层,它就是迭代器。因此,看上去有3种方法,实质上就只有2种---operator[]和迭代器

使用范围for的时候,是将s1里面的值依次拷贝给e,e相当于是s1里面的值的一份临时拷贝,对e进行修改不影响s1里面的值。如果我们需要通过e来改变s1里面的值,需要传引用&

//遍历方式3: 范围for
for (auto& e : s1) {
e++;//将s1里面的每一个字符都+1
cout << 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


好啦,接下来,我们想要对字符串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类(上),希望看完这篇文章能对友友们有所帮助!!!

点赞收藏加关注!!!

谢谢大家!!!


原文地址:https://blog.csdn.net/qq_74336191/article/details/142488267

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!