对于C语言中的字符串,我们只能使用char类型数组保存,并且是以'\0'结尾的.
操作起来非常不方便而且底层空间需要用户自己访问,非常造成容易越界访问.
这个时候,C++的STL中的string类就很好解决了这些.
目录
string的使用
1.string类对象的常见构造
2. string类对象的容量操作
3. string类对象的访问及遍历操作
4. string类对象的修改操作
5.string类非成员函数
string的使用
string的使用主要是使用一些接口,下面将列出一些常用的接口使用.
1.string类对象的常见构造
当我们用string初始化对象时,可采用以下方法:
string() 构造空的string类对象,即空字符串
string(const char* s) 用C-string来构造string类对象
string(size_t n,char c) string类对象中包含n个字符c
string(const string& s) 拷贝构造函数
看下列代码
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello hmylq"); // 用C格式字符串构造string类对象s2,即包含'\0'
string s3(s2); // 拷贝构造s3
}
来分别看一下它们的值
这样就成功初始化了.
与从同时初始化的方法还有一种,就是赋值运算符重载"=",也可以进行初始化.
string s = "lqhmy";
2. string类对象的容量操作
size(重点) 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
empty (重点) 检测字符串释放为空串,是返回true,否则返回false
clear (重点) 清空有效字符
reserve (重点) 为字符串预留空间
resize (重点) 将有效字符的个数该成n个,多出的空间用字符c填充
void Teststring()
{
// 注意:string类对象支持直接用cin和cout进行输入和输出
string s("hello, hmylq!");
cout << s.size() << endl;
cout << s.length() << endl;
cout << s.capacity() << endl;
cout << s << endl;
}
capacity为当前底层空间容量,而size和length为当前字符串大小.
可以看到已经将字符串s的大小输出出来了.
void Teststring()
{
string s("hello, hmylq!");
// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s.clear();
cout << s.size() << endl;
cout << s.capacity() << endl;
}
此时clear是将字符串清空,大小变为0,但是容量不会改变.
void Teststring()
{
string s("hello, hmylq!");
// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s.clear();
// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
// “aaaaaaaaaa”
s.resize(10, 'a');
cout << s.size() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
}
注意是将有效字符增加到10个,并不是容量!
可以看到resize已经成功把10个字符用’a'进行了填充.
resize还有注意的是,如果第二个参数不给值,会默认缺省值为'\0'
看下面的例子
void Teststring()
{
string s("hello, hmylq!");
// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s.clear();
// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
// “aaaaaaaaaa”
s.resize(10, 'a');
// 将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;
}
此时虽然字符串显示的是10个‘a',但是有效字符长度已经到了15,再向后插入字符将会在第16个字符进行插入.
void Teststring()
{
string s("hello, hmylq!");
// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s.clear();
s.resize(10, 'a');
// 将s中有效字符个数缩小到5个
s.resize(5);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
}
可以看到有效长度已经缩小到了5个,但是总容量没有变化,而且这次是将有效字符长度缩小,所以会将多余的字符舍弃掉,只会留下前5个字符.
reserve
void Teststring()
{
string s;
// 测试reserve是否会改变string中有效元素个数
s.reserve(100);
cout << s.size() << endl;
cout << s.capacity() << endl;
}
来看结果
可以看到reserve并没有改变size即有效元素的个数,只改变了容量大小,但我们开辟了100个空间,但为什么容量是111呢?
其实系统开辟空间默认是15个字节,当不满足需求时,将每次以1.5倍的速度扩容,直到大于或者等于指定的容量.
void Teststring()
{
string s;
s.reserve(100);
// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
s.reserve(50);
cout << s.size() << endl;
cout << s.capacity() << endl;
}
结果如下:
可以看到空间并不会缩小,依然是111.
与此同时有需要注意的几个点如下:
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
3. string类对象的访问及遍历操作
operator[] (重点) 返回pos位置的字符,const string类对象调用
begin+ end begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rend rbegin获取最后一个字符的迭代器 + rend获取第一个字符前一个位置的迭代器
范围for C++11支持更简洁的范围for的新遍历方式
void Teststring()
{
string s1("hello lq");
const string s2("Hello hmy");
cout << s1 << " " << s2 << endl;
cout << s1[0] << " " << s2[0] << endl;
s1[0] = 'H';
cout << s1 << endl;
// s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
}
可以看到我们直接用下标访问到string类中的第0个字符,当然还可以是别的下标位置.
获取到之后不仅可以使用,还可以进行修改.
例如我们将s1中的第一个字符'h'改成了'H'.
非常的方便.
下面说string的遍历.
一共有三种方法:
需要注意的以下三种方式除了遍历string对象,还可以遍历时修改string中的字符,
另外以下三种方式对于string而言,第一种使用最多
1.for+operator[]
for (size_t i = 0; i < s.size(); ++i)
cout << s[i] << endl
2.迭代器
string::iterator it = s.begin();
while(it != s.end())
{
cout<<*it<<endl;
++it;
}
string::reverse_iterator rit = s.rbegin();
while(rit != s.rend())
cout<<*rit<<endl;
3.范围for
for(auto& ch : s)
{
cout << ch << endl;
}
4. string类对象的修改操作
push_back 在字符串后尾插字符c
append 在字符串后追加一个字符串
operator+= (重点) 在字符串后追加字符串str
c_str(重点) 返回C格式字符串
find + npos(重点) 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr 在str中从pos位置开始,截取n个字符,然后将其返回
接下来看各接口的使用
void Teststring()
{
string str;
str.push_back(' '); // 在str后插入空格
str.append("hello"); // 在str后追加一个字符"hello"
str += 'h'; // 在str后追加一个字符'h'
str += "lq"; // 在str后追加一个字符串"lq"
cout << str << endl;
cout << str.c_str() << endl; // 以C语言的方式打印字符串
}
看输出结果
可以发现都已经成功插入了
在string中push_back只能插入字符,append只能追加字符串,当然字符串可以是一个字母.
而+=既可以插入字符也可以插入字符串,所以我们平常使用+=居多.
void Teststring()
{
// 获取file的后缀
string file("string.cpp");
size_t pos = file.rfind('.');
string suffix(file.substr(pos, file.size() - pos));
cout << suffix << endl;
}
我们都知道文件后缀是以 . 结尾
我们想获取文件的后缀名,首先要先找到.的位置,利用find或者rfind(这里使用rfind因为.一般都是偏后,所以是从后向前找),然后如果找到了,会返回这个位置的下标,否则返回npos,然后我们想获取.后面的内容,可以利用substr切取子串,从第6个位置开始,向后截取(总大小-6)个长度,即.后面的长度,这样就可以获得了.
其中的npos 是string中的一个静态成员变量
static const size_t npos = -1;
void Teststring()
{
// 取出url中的域名
string url("http://www.cplusplus.com/reference/string/string/find/");
cout << url << endl;
size_t start = url.find("://");
//如果start等于了npos,说明没有找到,即这个url是非法的.
if (start == string::npos)
{
cout << "invalid url" << endl;
return;
}
//如果找到了,因为此时位置是在':'这个位置,所以必须将这个位置+=3跳过这个位置,此时的位置在"://www"中的第一个w位置
start += 3;
//然后开始寻找第一个/位置
size_t finish = url.find('/', start);
//此时从start到finsh之间的东西就是我们所需要的url域名
string address = url.substr(start, finish - start);
cout << address << endl;
}
上面的就是取出url域名的操作了
在这个操作中需要注意的是:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
5.string类非成员函数
operator+ 尽量少用,因为传值返回,导致深拷贝效率低
operator>> (重点) 输入运算符重载
operator<< (重点) 输出运算符重载
relational operators(>,>=,<,<=,==) (重点) 大小比较
这里前三个前面已经说过了,主要说一下最后一个,就是字符串可以利用大于、小于、等于号等进行比较(按字典序比较)
void Teststring()
{
string s1("abcde");
string s2("bcdef");
if (s1 > s2)
cout << "s1 > s2" << endl;
else if (s1 < s2)
cout << "s1 < s2" << endl;
else
cout << "s1 == s2" << endl;
}
结果应当是s1<s2
符合了我们预期的结果.
这里想说明字符串可以直接利用运算符进行比较大小.
stl的string的使用就到此结束了,下一章将详细讲述它的模拟实现.
如果有疑问或者错误的地方,欢迎提出或指正哦.