【本节目标】
- 1. 为什么要学习string类
- 2. 标准库中的string类
1. 为什么要学习string类
1.1 C语言中的字符串
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数, 但是这些库函数与字符串是分离开的,不太符合OOP(面向对象)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
2. 标准库中的string类
2.1 string类(了解)
总结:
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;
4. 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std。
2.2 string类的常用接口说明
1. string类对象的常见构造
void Teststring() { string s1; // 构造空的string类对象s1 string s2("hello bit"); // 用C格式字符串构造string类对象s2 string s3(s2); // 拷贝构造s3 string s4 = s3;// 拷贝构造s4 }
我们再来介绍一下string其他相关的构造
(3)Copies the portion(一部分) of str that begins at the character position pos and spans(跨越) len characters (or until the end of str, if either str is too short or if len is string::npos).
我们来使用一下该函数:
string (const string& str, size_t pos, size_t len = npos);
我们再来看一下npos是什么?
所以我们就可以完全理解上面的函数:复制字符串 str
从字符位置 pos
开始,跨越 len
个字符(或者直到 str
的末尾,如果 str
太短或 len
为 string::npos
)的部分。
(5)Copies the first n characters from the array of characters pointed by s.
我们来使用一下该函数:
string (const char* s, size_t n);
这个构造函数比较简单,就是复制字符数组 s 指向的前 n 个字符,不过这里要注意该函数的第一个参数是字符串,而不是string类滴对象。
(5)Fills the string with n consecutive(连续的) copies of characterc.
我们来使用一下该函数:
string (size_t n, char c);
这个构造函数就是用字符 c 的连续副本填充字符串,填充的数量为 n。
(7)Copies the sequence of characters in the range [first,last), in the same order.
这个构造函数涉及迭代器,我们后面再讲解。
2.string类对象的析构函数
析构函数会自动调用的,我们可以不用重点关注在这一块。
3. string类对象的赋值
这里赋值可以支持string类的对象,常量字符串和字符。
4. string类对象的遍历和访问
4.1.通过下标 + [ ]运算符重载实现
//遍历和访问 int main() { string str("Test string"); //下面两个函数结果相同,结果是不包含'\0'的 cout << "size:" << str.size() << endl;//推荐使用 cout << "length:" << str.length() << endl; for (size_t i = 0; i < str.size(); i++) { cout << str[i]; } cout << endl; return 0; }
运行结果:
我们再来写一个字符串的逆序
//字符串的逆序 int main() { string str("Test string"); int begin = 0, end = str.size() - 1; while (begin < end) { char tmp = str[begin]; str[begin] = str[end]; str[end] = tmp; ++begin; --end; } for (size_t i = 0; i < str.size(); i++) { cout << str[i]; } cout << endl; return 0; }
上面的逆序字符串交换的这个代码很繁琐,C++为我们提供了Swap函数接口
于是我们根据上面的swap函数就可以这样写
//字符串的逆序 int main() { string str("Test string"); int begin = 0, end = str.size() - 1; while (begin < end) { swap(str[begin], str[end]); ++begin; --end; } for (size_t i = 0; i < str.size(); i++) { cout << str[i]; } cout << endl; return 0; }
运行结果:
我们现在再来看一个细节,也就是我们之前提到的const的成员函数,这里的[ ]运算符重载实现了两个,这里是两个不同的函数,它们的参数是不同的,第二个隐形的this指针用了const修饰,这里我们要提一下参数匹配的问题。
#include <iostream> #include <string> using namespace std; int main() { string s1("hello world"); const string s2("hello world"); // 如果只实现了const char& operator[] (size_t pos) const; s1[0];//权限缩小 s2[0];//权限平移 // 但是此时返回值是const char& - 返回值不可修改 //s2[0] = 'x';//error:error C3892: “s2”: 不能给常量赋值 //所以就提供了char& operator[] (size_t pos); s1[0] = 'x'; return 0; }
4.2.迭代器(Iterator)
//迭代器iterator:遍历和访问 int main() { string str("Test string\0"); //定义在类域里面,需要域作用限定符才可以访问 //迭代器iterator是一个类型,用法像指针 //[begin,end)左边右开区间 string::iterator it = str.begin(); while (it != str.end()) { *it += 1;//访问修改 cout << *it;//遍历 ++it; } cout << endl; return 0; }
运行结果:
解析:
总结:虽然下标 + [ ]很方便,但是它仅仅适用于这些底层物理空间连续,比如string、vector等等。但是链式结构,树形和哈希结构,只能用迭代器,迭代器才是容器访问主流形态。
所以我们上面的逆序就不用写上面的这么多代码,C++为我们提供了逆置的函数:reverse
所以就可以直接这样写:
//字符串的逆序 int main() { string str("Test string"); string::iterator it = str.begin(); reverse(str.begin(), str.end()); while (it != str.end()) { cout << *it; ++it; } cout << endl; return 0; }
运行结果:
上面通过函数模板实现,注意泛型编程不是针对某个容器的迭代器实现的,函数模板是针对各个容器迭代器实现。关于我们的迭代器,begin获取一个字符的迭代器,end获取最后一个字符下一个位置,普通的迭代器是可读可写的,但是这里也有一个细节,我们这里迭代器也实现了重载,const重载的只能进行可读。
当我们对只读对象进行迭代器遍历的时候,就出现错误了。同时这里还有一个细节,我们发现上面是const_iterator,而不是const iterator,中间多了一个_,这里的const_iterator本质上是保护迭代器指向的数据"*it"不能被修改,而不是const iterator是迭代器本身不能被修改,也就"it"不能被修改,否则这样无法++,无法遍历,不符合我们的需求。
4.3.反向迭代器
我们也可以通过反向迭代器进行遍历和访问,但是此时的遍历是逆序的。
样例:
我们可以观察到反向迭代器也实现了两个版本,但是普通情况下我们基本上很少定义const对象,只有在传参的时候我们才最容易出现const对象。
//void fun(const string s1){}//调用拷贝构造,深拷贝,代价大 void fun(const string& s1)//引用,作为str的别名,开销小 { //error //string::reverse_iterator it = s1.rbegin(); //const的对象调用rbegin应该返回const_reverse_iterator string::const_reverse_iterator it = s1.rbegin(); while (it != s1.rend()) { *it = 1;//const迭代器不可修改,error:“it”: 不能给常量赋值 cout << *it; ++it; } cout << endl; } int main() { string str("Test string"); string::reverse_iterator it = str.rbegin(); while (it != str.rend()) { *it = 1;//普通迭代器不可修改 cout << *it; ++it; } cout << endl; fun(str); return 0; }
【编码艺术:掌握String类函数接口的妙用指南】(二):https://developer.aliyun.com/article/1425650