很多应用程序都需要处理字符串。C语言在string.h(在C++中为cstring)中提供了一系列的字符串函数,很多早期的C++实现为处理字符串提供了自己的类。
string类是由头文件string支持的。要使用类,关键在于知道它的公有接口,而string类包含大量的方法,其中包括了若干构造函数,用于将字符串赋给变量、合并字符串、比较字符串和访问各个元素的重载运算符以及用于在字符串中查找字符和子字符串的工具等。简而言之,string类包含的内容很多。
1、标准库中string类
1、c语言中的字符串
c语言中,字符串是以‘\0’结尾的一些字符的集合,为了操作方便,c标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
2、string类
<1>、string是表示字符串的字符串类
<2>、该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
<3>、string在底层实际是:basic_string模板类的别名,typedef basic_string<char,char_traits,allocator>string;
<4>、不能操作多字节或者变长字符的序列
在使用string类时,必须包含头文件以及using namespace std;
3、string类的常用接口说明
函数名称 | 功能说明 |
---|---|
string() | 构造空的string类对象,即空字符串 |
string(const char* s) | 用C-string来构造string类对象 |
string(size_t n,char c) | string类对象中包含n个字符c |
string(const string&s) | 拷贝构造函数 |
string(const string&s,size_t n) | 用s中的前n个字符构造新的string类对象 |
void TestString() {
string s1; //构造空的string类对象s1
string s2("hello world"); //用c格式字符串string类对象s2
string s3(10, 'a'); //用10个字符‘a’构造string类对象s3
string s4(s2); //拷贝构造s4
string s5(s3, 5); //用s3中前5个字符构造string对象s5
}
4、string类对象的容量操作
函数名称 | 功能说明 |
---|---|
size_t size() const | 返回字符串有效字符长度 |
size_t length() const | 返回字符串有效字符长度 |
size_t capacity() const | 返回空间总大小 |
bool empty () const | 检测字符串释放为空串,是返回true,否则返回false |
void clear() | 清空有效字符 |
void resize(size_t n,char c) | 将有效字符的个数改成n个,多出的空间用字符c填充 |
void resize(size_t n) | 将有效字符的个数改成n个,多出的空间用0填充 |
void reserve(size_t res_arg=0 | 为字符串预留空间 |
// size/length/clear/resize
void TestString1() {
// 注意:string类对象支持直接用cin和cout进行输入和输出
string s("hello");
cout << s.length()<<endl; // 5
cout << s.size() << endl; // 5
cout << s.capacity() << endl; // 15
cout << s << endl;
// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间大小
s.clear();
cout << s.size() << endl;
cout << s.capacity() << endl;
// 将s中有效字符个数增加到10个,多出位置用 'a' 进行填充
// "aaaaaaaaaa"
s.resize(10, 'a');
cout << s.size() << endl;
cout << s.capacity() << endl;
// 将s中有效字符个数增加到15个,多出位置用缺省值 '\0' 进行填充
// "aaaaaaaaaa\0\0\0\0\0"
// 注意此时s中有效字符个数已经增加到15个
s.resize(15);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
// 将s中有效字符个数缩小到5个
s.resize(5);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
}
void TestString2() {
string s;
// 测试reserve是否会改变string中有效元素个数
s.reserve(100);
cout << s.size() << endl;
cout << s.capacity() << endl;
//测试reserve参数小于string的底层空间大小时,是否会将空间缩小
s.reserve(50);
cout << s.size() << endl;
cout << s.capacity() << endl;
}
注意:
1、size()与length()方法底层实现原理相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()
2、clear()只是将string中有效字符清空,不改变底层空间大小
3、resize(size_t n)与resize(size_t n,charc)都是将字符串中有效个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的的元素空间,resize(size_t n,char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4、reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserve不会改变容量大小。
2、string类的模拟实现
模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。
2.1 浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共同享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有小,所以,当继续对资源进行操作时,就会发生访问违规。要解决拷贝问题,C++中引入了深拷贝。
浅拷贝只关注了美人鱼美丽的上半身,而深拷贝探索到了美人鱼不为人知的下半身。
2.2 深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数,赋值运算符重载以及析构函数必须要显示给出。一般情况都是按照深拷贝方式提供。
每个string类对象都要用空间来存放字符串,而s2要用s1拷贝构造出来,因此
深拷贝:给每个对象独立分配资源,保证多个对象之间不会因共享资源而造成多次释放,造成程序崩溃问题
2.3 string类的写法
class String {
public:
String(const char* str = "") {
if (nullptr == str)
str = "";
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
:_str(nullptr)
{
String strTmp(s._str);
swap(_str, strTmp);
}
String& operator=(String s) {
swap(_str, s._str);
return *this;
}
~String() {
if (_str) {
delete[]_str;
_str = nullptr;
}
}
private:
char* _str;
};
2.4 写时拷贝
写实拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象是资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。