目录
一、网站上查看string类
1.网站
2.网站上的string类
二、string类的成员函数
1.默认成员函数
(1)构造函数、拷贝构造函数和析构函数
(2)赋值运算符重载
(3)容量操作
(4)迭代器(iterator)
(5)元素访问
(6)修改操作
(7)其他成员函数
三、string类的非成员函数
一、网站上查看string类
1.网站
还记得在C语言中我推荐的两个查看库函数的工具吗?其中一个是一个网站叫cplusplus;另一个是一个应用程序叫MSDN。自此我们在学习C++的过程中要经常用到cplusplus这个网站。
网址是:https://cplusplus.com
打开后是这样的,最近这个网站改版了,新版的网站不好用,我们可以点击右上角的[legacy version]切换到旧版本。
切换到旧版本
2.网站上的string类
我们可以在上面搜索string,得到这样的一个类
下面还有各种成员函数,也都给出了各自的使用方法。
二、string类的成员函数
1.默认成员函数
在成员函数的第一部分就是string的三个默认成员函数,从上到下为构造函数、拷贝构造函数、析构函数和赋值运算符重载
(1)构造函数、拷贝构造函数和析构函数
string的构造与拷贝构造函数有很多,常用的有以下四个:
string();——————不传参的默认构造,构造空string对象,即空字符串
string(const string& str);——————用一个string变量初始化另一个string变量
string(const char* s);——————用一个C语言字符串初始化一个string变量
string(size_t n, char c);——————用一个字符初始化一个string变量让其包含n个字符c
这是官方库中实现的构造函数,在网站上我们可以看到它们的参数和使用,每个接口的使用都有详细的解释。
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test()
{
string s1;
cout << s1 << endl;
string s2("hello world");
cout << s2 << endl;
string s3(s2);
cout << s3 << endl;
string s4(10, 'x');
cout << s4 << endl;
}
int main()
{
test();
return 0;
}
测试结果:
string类的底层组织形式与顺序表一样,由于析构函数不可重载的特性也只能有一个:~string(),负责释放字符串内容占用的空间。
(2)赋值运算符重载
string& operator= (const string& str);——————将str的内容赋值给左侧变量
string& operator= (const char* s);——————将字符串的内容赋值给左侧变量
string& operator= (char c);——————将字符赋值给左侧变量
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("hello world");
string s2;
s2 = s1;
cout << s2 << endl;
string s3;
s3 = "hello";
cout << s3 << endl;
string s4;
s4 = 'x';
cout << s4 << endl;
}
int main()
{
test2();
return 0;
}
测试结果:
(3)容量操作
容量操作包含以下内容,其中C++11标准中新出现的函数后面有标注
size_t size() const;——————返回string有效数据的长度
size_t length() const;——————返回string有效数据的长度
length和size在使用上没有区别,因为我们以后会知道STL的其他类和string是类似的,有些数据结构使用比如顺序表、链表为基础的类使用length表示元素个数更好理解,而对于书和图等数据结构使用size表示元素个数更好理解,所以这两个函数都被保留了下来。
size_t max_size() const;——————返回string可储存的字符串长度上限
就是无符号整形最大值,此接口在使用中没有意义
size_t capacity() const;——————返回开辟空间的大小
void reserve (size_t n = 0);——————如果n值大于当前的空间大小,便扩大空间到n个字符,小于则不做改变。但输入的改变后的空间大小不能小于字符串的长度
void clear();——————清除string的内容为空字符串,但不释放空间
bool empty() const;——————检测当前string是否是空字符串
void resize (size_t n, char c);——————改变空间大小,若新空间大于旧空间插入对应字符,小于则直接截断
void shrink_to_fit();——————将string的容量缩小到有效数据的大小
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("hello world!");
cout << "字符串长度:" << s1.size() << endl;
cout << "空间可容纳元素的个数:" << s1.capacity() << endl;
cout << "字符串空间最大值:" << s1.max_size() << endl;
cout << "0为假,1为真:" <<s1.empty() << endl;
s1.reserve(13);
cout << "字符串长度:" << s1.size() << endl;
cout << "空间可容纳元素的个数:" << s1.capacity() << endl;
s1.reserve(18);
cout << "字符串长度:" << s1.size() << endl;
cout << "空间可容纳元素的个数:" << s1.capacity() << endl;
s1.resize(20, 'x');
cout << s1 << endl;
s1.clear();
cout << s1;
}
int main()
{
test2();
return 0;
}
测试结果:
(4)迭代器(iterator)
迭代器是一个类似于指针的东西,它的使用与指针非常类似。
我们看一段代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("hello world!");//s1初始化为hello world
string::iterator it = s1.begin();
//it是string类的迭代器变量s1.begin()表示s1字符串内容的首个字符位置
while (it != s1.end())//s1.end()表示s1内字符串的末尾位置的后一位
{
printf("%c", *it);
it++;
}
cout << endl;
}
int main()
{
test2();
return 0;
}
这个程序可以遍历string字符串的每一个字符,也就相当于遍历数组的每个元素,迭代器可以像指针一样进行加减,也可以通过解引用得到相应内容。但为什么说迭代器是一个类似于指针的东西呢?因为在string类中的迭代器使用与指针相同,但是在STL的链表中也存在list的迭代器,链表在遍历的时候也可以用加一的形式找下一个元素位置,指针加一的遍历方式就不对了,所以说很像但不是。
我们主要学习两个迭代器:正向和反向迭代器
string的正向迭代器
iterator begin();——————string字符串的头,可读可写
const_iterator begin() const;——————string字符串的头,只读
iterator end();——————string字符串的尾,可读可写
const_iterator end() const;——————string字符串的尾,只读
string的反向迭代器
reverse_iterator rbegin();——————string字符串逆向的头,可读可写
const_reverse_iterator rbegin() const;——————string字符串逆向的头,只读
reverse_iterator rend();——————string字符串逆向后的尾,可读可写
const_reverse_iterator rend() const;——————string字符串逆向的尾,只读
所有迭代器的头都是正向或反向的首元素,尾都是尾元素的位置。
- 创建迭代器对象it1时,必须指明是哪个类的正向或反向迭代器。
- 使用类成员函数的begin获得数据当前顺序的起始位置。
- 使用类成员函数的end获得数据当前顺序的结束位置。
- 将迭代器解引用可访问字符串中的特定元素。
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("hello world!");
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
printf("%c", *it1);
it1++;
}
cout << endl;
string::reverse_iterator it2 = s1.rbegin();
while (it2 != s1.rend())
{
printf("%c", *it2);
it2++;
}
cout << endl;
string::iterator it3 = s1.begin();
while (it3 != s1.end())
{
*it3 = 'x';
it3++;
}
cout << s1 << endl;
}
int main()
{
test2();
return 0;
}
测试结果:
下面的cbegin、cend、crbegin、crend其实就是将只读的迭代器独立又实现了一遍,我们的使用中很少会用到它们。
(5)元素访问
operator[]——————直接重载[]使s[i]就可以直接访问对应下标元素,而且也加上了越界的检查,提高了程序的安全性。
char& operator[] (size_t pos);——————可读可写
const char& operator[] (size_t pos) const;——————只读
at函数——————与[]作用相同
char& at (size_t pos);——————可读可写
const char& at (size_t pos) const;——————只读
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("hello world!");
cout << s1[1] << endl;
cout << s1.at(1) << endl;
s1[1] = 'a';
cout << s1 << endl;
s1.at(1) = 'b';
cout << s1 << endl;
}
int main()
{
test2();
return 0;
}
测试结果:
(6)修改操作
operator+=—————不同数据的尾插,这个用的最多
string& operator+= (const string& str);——————尾插string变量
string& operator+= (const char* s);——————尾插字符串
string& operator+= (char c);——————尾插字符
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("hello");
cout << s1 << endl;
s1 += '_';
cout << s1 << endl;
string s2("wor");
s1 += s2;
cout << s1 << endl;
s1 += "ld!";
cout << s1 << endl;
}
int main()
{
test2();
return 0;
}
测试结果:
string& append(const char* s)——————尾插多个多个字符,内部重载函数很多
assign——————将原本的内容删除然后放入相应数据
string& assign (size_t n, char c);——————将对象赋值为n个c字符
string& assign (size_t n, char c);——————将对象赋值为n个c字符
string& assign (size_t n, char c);——————将对象赋值为n个c字符
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("xxxxxxxxxxxxx");
string s2("hello world!");
s1.assign(s2);
cout << s1 << endl;
s1.assign("abcdef");
cout << s1 << endl;
s1.assign(5, 'x');
cout << s1 << endl;
}
int main()
{
test2();
return 0;
}
测试结果:
void push_back(char c);——————尾插一个字符
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("hello world");
cout << s1 << endl;
s1.push_back('!');
cout << s1 << endl;
}
int main()
{
test2();
return 0;
}
测试结果:
insert——————在特定位置插入内容
string& insert (size_t pos, const string& str);——————在pos下标位置插入str变量的内容
string& insert (size_t pos, const char* s);——————在pos下标位置插入字符串
string& insert (size_t pos, size_t n, char c);——————在pos下标位置插入n个c字符
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("to ");
cout << s1 << endl;
string s2("Welcome ");
s1.insert(0, s2);
cout << s1 << endl;
s1.insert(11, "Tianjin,");
cout << s1 << endl;
s1.insert(19, 5, 'x');
cout << s1 << endl;
}
int main()
{
test2();
return 0;
}
测试结果:
使用insert也能够实现头插,将指定位置的写为0即可,但是顺序表的头插需要移动数据,是一个O(N^2)的程序,效率低,所以能不用则不用。
erase——————清除内容
string& erase(size_t pos = 0; size_t len = npos);——————清除对应下标开始向后数n个字符,这两个参数都是缺省的,我们如果不输入pos,会默认从头开始删;如果我们不输入len,len就会被赋值为npos,也就是无符号整形的最大值。
npos是一个静态成员变量,是无符号整形的最大值
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("Welcome to Shanghai!");
cout << s1 << endl;
s1.erase(11, 9);
cout << s1 << endl;
s1.erase();
cout << s1 << endl;
}
int main()
{
test2();
return 0;
}
测试结果:
replace——————替换内容
string& replace (size_t pos, size_t len, const string& str);——————将长度为n的原字符串内容替换为str变量的内容
string& replace (size_t pos, size_t len, const char* s);——————将长度为n的原字符串内容替换为另一个字符串的内容
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("Welcome to Shanghai!");
cout << s1 << endl;
string s2("Tientsin");
s1.replace(11, 8, s2);
cout << s1 << endl;
s1.replace(11, 8, "Tianjin");
cout << s1 << endl;
}
int main()
{
test2();
return 0;
}
测试结果:
(7)其他成员函数
const char* c_str() const;——————将string变量转换为C字符串返回
find——————在string变量中从头部开始找子串,pos是开始找字串的位置,返回第一次出现子串的头位置
size_t find (const string& str, size_t pos = 0) const;——————以string变量找子串
size_t find (const char* s, size_t pos = 0) const;——————以字符串找子串
rfind——————在string变量中从尾部开始找子串,pos是开始找字串的位置,返回第一次出现子串的头位置
size_t rfind (const string& str, size_t pos = npos) const;——————以string变量找子串
size_t rfind (const char* s, size_t pos = npos) const——————以字符串找子串
string substr (size_t pos = 0, size_t len = npos) const;——————取字符串pos下标开始的len个字符形成string对象,返回该对象。
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1("hello world1");
char arr[20] = "hello world2";
string s2("o");
printf("%s\n", s1.c_str());
printf("%d %d\n", s1.find("o"), s1.find(s2));
printf("%d %d\n", s1.rfind("o"), s1.rfind(s2));
cout << s1.substr(6, 5) << endl;
}
int main()
{
test2();
return 0;
}
测试结果:
三、string类的非成员函数
除了流插入和流提取的重载,我还想讲以下getline和流插入重载的区别
流插入和流提取一个是输入一个是输出,流插入类似于C语言中的scanf,scanf读取到空字符就会直接结束;C语言中也提供了一个gets函数,这样就可以继续读取空字符了,同样string中的getline也是一个道理
测试代码:
#include<iostream>
#include<string>
using namespace std;
void test2()
{
string s1;
cin >> s1;//我输入hello world
cout << s1 << endl;//s1只读取到空格之前的部分,输出:hello
string s2;
getline(cin, s2);//getline可以读取空字符,将剩余的空格+world读取
cout << s2 << endl;//输出: world
}
int main()
{
test2();
return 0;
}
测试结果:
string类历史悠久,在CTL出现之前string就已经存在了,所以它会有很多在我们看来重复冗余的内容,所以我们也可以在代码的使用过程中感受到C++的发展。
cplusplus网站上的string类:string - C++ Reference (cplusplus.com)