一、string类的作用
我们知道在C语言中, 字符串是一个以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离的, 不太符合面向对象编程的思想,并且底层空间需要用户自己进行管理, 稍不留神可能还会造成越界访问等问题,比较不方便。
而string类出现后我们能够向定义内置类型一样直接定义出一个字符串,不需要在使用C语言风格的char*类型的字符串了,并且string类里面包含了很多对字符串进行操作的成员函数,符合面向对象编程的思想,使得我们对字符串的控制更加简单,方便。
二、 标准库中的string类
2.1 string类
string类的基本介绍:
1. 字符串是表示字符序列的类。
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符串的设计特性。
3. string类是使用char作为它的字符类型。
4. string类是basic_string模版类的一个实例, 它使用char来实例化basic_string模版类,并用char_traits和allocator作为basic_string的默认参数(更多的模版信息参考basic_string)。
5. 注意:这个类独立于所使用的编码来处理字节: 如果用来处理多字节或者变长字符(如UTF-8)的序列,这个类的所有成员以及迭代器,仍然按照字节来操作而不是按照实际编码的字符来操作。
总结:
1.string类是表示字符串的字符串类
2.该类的接口与常规容器的接口基本相同,在此基础上又增加了专门用来操作string的常规操作。
3.string在底层实现时是:basic_string模版类的别名, typedef basic_string<char , char_traits, allocator> string;
4.不能操作多字节或者变长字符的序列。
在使用string类的时候必须包含头文件<iostream> 和 using namespace std;
2.2 string类的常用接口介绍
2.2.1 string类对象的常见构造
函数名称 | 函数功能 |
---|---|
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用c风格的字符串来构造string类的对象 |
string(size_t n, char c) | 构造string类对象中包含n个字符c |
string(const string& s) (重点) | 拷贝构造函数 |
下面我们来看一下代码实例:
void test()
{
string s1; //构造空的string类对象s1
string s2("hello");//使用c风格的字符串构造string类对象s2
string s3(s2); //使用拷贝构造函数用s2拷贝构造出s3
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
}
2.2.2 string类对象的容量操作
函数名称 | 函数功能 |
---|---|
size() (重点) | 返回字符串的有效字符长度 |
length() | 返回字符串的有效字符长度与size()的功能相同 |
capacity() | 返回字符串的容量 |
empty() (重点) | 检测字符串是否为空串,为空则返回true,不为空返回false |
clear() (重点) | 清空字符串中的有效字符 |
reserve(size_t n) (重点) | 为字符串预留空间,可以进行扩容 |
resize(size_t n, char c) (重点) | 将有效字符的个数改为n,多出来的空间用字符c来填充 |
下面来看下这些函数的使用实例:
void test1()
{
string s1;//空串
string s2("hello string");
string s3(s2);
cout << s1.size() << " " << s1.length() << endl;
cout << s2.size() << " " << s2.length() << endl;
cout << s3.size() << " " << s3.length() << endl;
cout << endl;
cout << s1.capacity() << endl;
cout << s2.capacity() << endl;
cout << s3.capacity() << endl;
cout << endl;
cout << s1.empty() << endl;
cout << s2.empty() << endl;
cout << s3.empty() << endl;
cout << endl;
s3.clear();
cout << s3.size() << " " << s3.capacity() << " " << s3.empty() << endl;
s3.reserve(8);
cout << s3.capacity() << endl;
cout << endl;
s2.resize(5, 'x');
cout << s2 << endl;
cout << s2.size() << " " << s2.capacity() << endl;
cout << endl;
}
注意:
1.size() 与 lenth()方法的底层实现原理是完全相同的, 引入size()的原因是为了与其他容器的接口保持一致,一般情况下都是使用size()。
2.clear()只是将string中的有效字符全部清空,但是不改变底层空间的大小,影响size()但不影响capacity()。
3.resize(size_t n) 与 resize(size_t n, char c)都是将字符串中的有效字符个数改变成n个,但是当字符个数增多时:resize(n)是用0来填充多出的元素空间,resize(size_t n, char c)是用字符c来填充多出的元素空间。
注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
2.2.3 string类对象的访问及遍历操作
函数名称 | 函数功能 |
---|---|
operator[] (重点) | 返回pos为之的字符,相当于将string类的对象当作数组使用 |
beign() 和 end() | begin获取一个字符的迭代器,end获取最后一个字符的下一个位置的迭代器,++迭代器是向后遍历 |
rbeign()和rend() | 反向迭代器,和begin与end相反 rbegin指向最后一个数据,rend指向第一个数据的前一个位置,++迭代器相当于向前遍历,相对于begin和end是相反的 |
范围for | C++11开始支持的更简洁的一种for循环的新遍历方式,可以使用迭代器遍历 |
注意:所有容器都支持范围for,for(auto e : s1);就是自动取s1中的对象赋值给e,自动++,自动判断结束,底层角度就是迭代器,在范围for里面如果需要修改数据的话auto后面要加上&。
begin() :任何容器返回第一个数据位置的iterator
end() : 任意容器返回最后一个数据的下一个位置的iterator
正向迭代器是iterator, 反向迭代器是reverse_iterator
iterator 可读可写的迭代器
const_iterator 只读 迭代器指向的数据不可写
const iterator 迭代器本身不能修改
下面看一下string类对象的访问和遍历的演示实例:
void test2()
{
string s1("hello string");
string s2(s1);
//operator[]
for (int i = 0; i < s1.size(); i++)
{
cout << s1[i];
}
cout << endl;
//迭代器遍历
string::iterator it1 = s2.begin();
string::iterator it2 = s2.end();
while (it1 != it2)
{
cout << *it1;
it1++;
}
cout << endl;
//反向迭代器
string::reverse_iterator it3 = s2.rbegin();
string::reverse_iterator it4 = s2.rend();
while (it3 != it4)
{
cout << *it3;
it3++;
}
cout << endl;
}
2.2.4 string类对象的修改工作
函数名称 | 函数功能 |
---|---|
push_back | 在字符串后尾插字符c |
append | 在字符串后面追加一个字符串 |
operator+= (重点) | 在字符串后追加字符窜str |
c_str(重点) | 返回c风格的字符串 |
find和npos | 从pos位置开始找字符c,返回该字符在字符串中的位置,npos代表末尾,值是size_t类型的-1,是整型最大值 |
rfind | 从字符串pos位置开始向前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
下面我们来看下string类对象的修改操作的使用演示实例:
void test3()
{
string s1("hello string");
string s2(s1);
s1.push_back('x');
s1.push_back('y');
cout << s1 << endl;
s2.append("abcde");
cout << s2 << endl;
s2.append(s1);
cout << s2 << endl;
s2.clear();
s2 += s1;
cout << s2 << endl;
cout << s2.c_str() << endl;
size_t pos = s2.find('x');
cout << pos << endl;
size_t rpos = s2.rfind('x');
cout << rpos << endl;
string str = s1.substr(0, 5);
cout << str << endl;
}
注意:
1. 在string尾部追加字符时, s.push_back(c) / s.append(1, c) / s += 'c' 这三种的实现方式差不多,一般情况下string类的 += 操作用的比较多, += 操作不仅可以连接单个字符而且可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少个字符,可以事先通过reserve把空间给预留好。
2.2.5 string类非成员函数
函数名称 | 函数功能 |
---|---|
operator+ | 尽量少用,因为是传值返回,导致深拷贝效率低 |
operator>> (重点) | 流提取运算符重载(输入运算符重载) |
operator<< (重点) | 流插入运算符重载(输出运算符重载) |
getline (重点) | 获取一行字符串 |
relational operators (重点) | 大小比较 |
下面是这些非成员函数的使用演示:
void test4()
{
string s1("hello string");
string s2("abcdefghijkmn");
string s3;
s3 = s1 + s2;
cout << s3 << endl;
s3.clear();
getline(cin, s3);
cout << s3 << endl;
}
2.3补充
C++中的迭代区间都是左闭右开的,例如[first, last);
对于C++对象的修改工作中还有一个按pos位置进行插入的函数:insert函数
insert函数效率不够,时间复杂度是o(N),慎用,实践中的需求也不高。
还有一个按pos位删除的函数:erase函数
时间复杂度同样为o(N),效率也不高慎用。
需要注意的是以上两种函数都是string类中的成员函数,下面我们来看下这两个函数的使用实例:
void test5()
{
string s1("abced");
string s2("yyyy");
s1.insert(s1.begin(), 'x');//在开头插入字符x
cout << s1 << endl;
s1.insert(0, s2);
cout << s1 << endl;
s1.erase(0, 4);//从0位置处删除4个字符
cout << s1 << endl;
}
这篇博客到这里就结束了,后面的内容将在下一篇博客继续讲解。