hello!这里是王_哈_哈 Jw ! (C++在学中)𓆡𓆝𓆟𓆜𓆞 𓆝𓆟...
本篇主要通过C++文档学习C++的string,通过了解与使用各种接口学习string类的增删查改。(下一篇通过模拟实现string更深入理解)
一、string简介
C语言通常用一个字符数组来进行对字符串的操作,而C++对其封装形成了string,使用起来更加方便了。
string是C++标准库提供的一个类,用于处理和操作字符串。它封装了一个动态字符数组,同时提供了许多成员函数和操作符重载。我们可以通过在cplusplus上查看各种接口,来了解其使用。string虽然在设计和使用上具有容器的一些特征,但C++标准库并未直接归类为容器,因为他主要用于表示和操作字符串而不是任意类型的集合。
C++学习推荐网站(参考文档):
前者覆盖了整个C++标准,内容全面深入,后者侧重于解释C++标准库的用法,简明易懂。
最重要的就是看文档!!!以下几乎废话!
二、string的使用
1 头文件
使用前包含头文件
#include<string>
2 初始化
string可以使用 构造函数 以及 operator= 进行初始化。
2.1 文档内容
2.2 使用示例
//构造函数初始化
string s1;
string s2("Hello World");
string s3(s2);
string s4(s2,6,5);
string s5("123456",3);
string s6(6,'x');
string s7(s2.begin(),s2.end());
initializer_list<char> ilist = { 'h','e','l','l','o' };
string s12(ilist.begin(), ilist.end());
//operator=
string s8 = s2;
string s9 = "hahahaha";
//string s10 = 'a'; 无法通过编译
string s11 = s8 + s9;
2.3 注意事项
✎在构造函数的(3)中出现了npos,它是string中的静态成员常量 库中是这样定义的:static const size_t npos = -1; 它表示的是整型的最大值,在string (const string& str, size_t pos, size_t len = npos);最后的len给的是缺省值,如果没有传参数,那么默认为npos,此时函数内部处理后,直接从pos处取到字符串结束去初始化。 ✎注意第(7),迭代区间是 [first,last) 左闭右开的。 ✎operator的(3)可以使用单个字符初始化string,但是我在使用VS编译时,使用示例的s10无法通过,如果想用单个字符初始化string还是使用 s1(1,'c') 或 s1 = "c" 吧 ✎文档解释的第(8)initializer list(初始化列表)是C++11中的内容,使用示例中的s12是一个简单用法。 |
3 string的遍历
3.1 operator[],at
string重载了运算符[]使它能像字符串一样访问其中的字符。此外还有at函数和[]的功能几乎一样。
使用&区别
它们使用起来并没有什么区别,不同的是越界检查时,operator[]是用断言报错,而at函数是抛异常。
string s1("Hello world");
cout << s1[2] << endl;
cout << s1.at(2) << endl;
//最后都会输出‘l’
3.2 迭代器
迭代器有些类似于指针,它可以通过begin和end获取字符的第一个位置和最后一个位置的地址,然后解引用遍历/修改。
使用
string s1("Hello world");
//通过begin和end实现正向遍历
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1 << " ";
it1++; //指向下一个位置
}
cout << endl;
//通过rbegin和rend实现反向遍历
string::reverse_iterator it2 = s1.rbegin();
while (it2 != s1.rend())
{
cout << *it2 << " ";
it2++;
}
cout << endl;
输出结果:
注意事项
✎迭代器不仅可以遍历访问,也可以修改内容。不是只能是s1.bengin()也可以s1.bengin()++、s1.bengin() + 3...
✎const对象要调用const类型的迭代器const_iterator.
✎刚开始可以简单的理解为 typedef char* iterator; typedef const char* iterator; 也是这个原因在类外面使用时需要加域作用限定符string::),所以它们只是类型名,通常用it作其变量名,一个程序中不能有两个相同的
✎反向遍历string时可以使用rbegin和rend,迭代器的类型为reverse_inerator,此时迭代器也还是++往后走而不是--
✎结合上两条,当我们需要对const类型的string进行反向迭代时迭代器的类型为string::const_reverse_inerator,很长,这一整段可以用auto直接替代。
✎cbegin和cend就是const_interator,是C++11中的,提升了代码可读性,使人一眼就能看出是只读的
|
3.3 范围for
范围for在底层就是迭代器的傻瓜式替代,它的使用也很简单:(依次取s1中的值复制给ch,自动判断结束,自动++往后走)如果需要对string的内容进行修改需要使用auto&。直接char& / char作为ch的类型必然是可以的。
for (auto ch : s1)
{
cout << ch << " ";
}
4 与string大小和容量有关的操作
string是一个类,我们可以认为他的成员变量有一个指向字符串的指针 char* str; 有一个int size; 存储字符串的大小,还有一个 int capacity; 存储它的空间大小(已分配的内存容量)。
能对其进行查看和改变的成员函数主要有这些☟
4.1 使用
string s1("Hello world");
cout << s1.size() << endl;
cout << s1.length() << endl;
cout << s1.capacity() << endl;
cout << s1.max_size() << endl;
cout << s1.empty() << endl;
s1.resize(5);
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.reserve(20);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.shrink_to_fit();
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.clear();
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1.empty() << endl;
//扩容机制测试
size_t old_capacity = s1.capacity();
for (int i = 0; i < 100; ++i)
{
s1 += 'a';
if (s1.capacity() != old_capacity)
{
cout << "Capacity changed:" << old_capacity << "->" << s1.capacity() << endl;
old_capacity = s1.capacity();
}
}
//15 -> 31 -> 47 -> 70 -> 105 ->157
以上程序的运行结果如下(一些结果将会在“注意事项”中解释):
4.2 注意事项
✎字符串的大小以'\0'为标识,但size和capacity的计算都是不包含'\0'的。所以string内部实际分配的内存空间通常会比其capacity略大一些。 ✎在以上代码的第一部分中,capacity显示的是15,因为在vs下的stl扩容机制就是一开始就为你分配15个空间,不够了扩为31,然后每次以1.5倍倍增。 ✎size和length都是一样的,计算字符串的长度,max_size表示的是该string对象理论可以存储的字符串长度,因编译环境而不同,而且一般是无法存下那么多的 ✎empty是判断字符串是否为空,返回值为bool类型,返回0/1 ✎resize是修改字符串长度的,resize有两个重载函数,可以使用s1.resize(100); s1.resize(100,'x'); 当resize后跟的数值n<size,那么size就会缩小至n,但capacity不变,当n>size时,size就会扩大至该n,如果此时capacity的值也比n小,capacity的值也会增加,n>size时如果后面没有跟字符,就用'\n'填充,跟了字符,就用该字符填充 ✎reserve(分配空间)只会改变capacity,reserve(n)如果n>capacity,就扩容至n,capacity也变为n,但是当n < capacity时,因stl的版本而异,vs使用的PJ版本不会缩容,g++使用的SGI版本会缩容,不过相同的是它们都不会改变size的大小,不会删除string中的字符信息 ✎shrink_to_fit是C++11的内容,有时候size和capacity相差过大,使用shrink_to_fit可以使capacity等于size |
5 string内容的修改(增、删、查、改)
这部分主要介绍了string的增加,删除,替换,查找...几乎包含了所有增删查改。
使用&注意事项
其实string的使用都大差不差,只要知道它的作用,了解它的接口,忘了就看文档就可以轻松使用。所以我就简单描述一下,并写一些注意事项。
- operator+=、append、push_back 实现的功能都是字符串,字符数组或字符的尾插功能;assign用于在对象已经存在的情况下,给对象重新赋值,他会清除对象当前的内容,并用新值替代;insert是插入,erase是清除,replace是替换其中的一些字符,swap是进行两个string的交换。c_str用于返回c风格的字符串的指针,即一个以空格字符结尾的字符数组(因为c和c++对于字符串的管理虽然相似,但仍然不同,而c++需要兼容c所以提供了这样一个接口);data返回指向字符数组的指针,这个数组表示字符串对象的内容(c++11以后与c_str返回的内容一致);get_allocator返回用于分配string对象内存的分配器对象,这通常用于自定义内存管理;copy复制字符串char str1[10]; str2.copy(str1,5); ←将str2中的前5个字符复制到str1中;find查找字符或字符串第一次出现的位置,返回下标;rfind从后往前找;find_first_of查找字符串中第一次出现任意一个给定字符,返回位置下标...; substr 返回指定的子串字符,会对对象自己的内容造成修改;compare比较两个字符串,相等返回0,大于返回正数,小于返回负数。
- c_str返回的是地址,所以如果有s1 = "abc"; s2 = s1; s1.c_str == s2.c_str; 时,结果是false。
- 整个c++中一共有3个swap函数,有一个是算法库中的它有两个参数,string有两个swap,是string对象的交换,其中一个就是上述swap使用时s1.swap(s2),另外一个不是sring的成员函数,有两个参数,但是内部的实现方法是与成员函数相关,是为了防止不小心使用了外部算法库中的swap函数形成不必要的拷贝造成空间浪费效率低,所以如果有人写swap(s1,s2);使用的也不是算法库中的交换。
- 如果我们使用cin/scanf来读取一串字符会发现,遇到空格就终止了,这是因为它们默认用' '/'\n'最为分割,此时我们可以使用getline来进行读取,string str; getline(cin,str); 此时就可以以换行符来判断读取结束。
-END-