string的介绍与使用
- 引言
- string类常用接口
- 构造函数
- 容量操作
- size与length
- capacity
- resize
- reserve
- clear
- empty
- 元素访问
- 迭代器访问
- begin
- end
- rbegin
- rend
- 范围for
- 下标访问
- 字符串修改
- insert
- append
- push_back
- operator+=
- pop_back
- erase
- 字符串查找
- find
- rfind
- 非成员函数
- operator>>
- operator<<
- 总结
引言
在C语言部分,我们对于字符串的操作经常借助C标准库提供的一系列操作字符串的库函数(strlen等
)来实现的。
但是,这样去操作字符串,函数与其操作的字符串是分离的,不符合面向对象的思想,使用时较为麻烦;而且需要自己管理底层的空间,容易导致越界访问的问题。
在C++标准库中提供了管理字符串的string类,封装了方法与元素使操作字符串时更加方便:
- strings是表示字符序列的对象
string
类是basic_string
模板类的一个实例,它使用char
来实例化basic_string
模板类,并用char_traits
和allocator
作为basic_string
的默认参数- ……(后面会详细介绍)
使用string类时,必须包含#include<string>
头文件以及using namespace std;
string文档详解
string类常用接口
string类的元素其实与之前实现的顺序表类似,包括一个指针指向存储数据的空间(_date
)、另两个数字表示元素个数(_size
)与容量(_capacity
)。
构造函数
string的构造方式有许多,包括无参构造、拷贝构造、C字符串构造、字符串中部分构造等:
例如:
//测试各种string的构造重载
int main()
{
//无参构造s1
string s1;
cout << "1: " << s1 << endl;
//使用C字符串(字符串常量)构造s2
string s2("hello qqq");
cout << "2: " << s2 << endl;
//使用string对象s2拷贝构造s3
string s3(s2);
cout << "3: " << s3 << endl;
//使用s2从下标为6的位置后的字符构造s4
string s4(s2, 6);
cout << "4: " << s4 << endl;
//使用常量字符串的前5个字符构造s5
string s5("abcdef", 5);
cout << "5: " << s5 << endl;
//使用6个字符'q'构造s6
string s6(6, 'q');
cout << "6: " << s6 << endl;
//使用s6首尾位置的迭代器构造s7
string s7(s6.begin(), s6.end());
cout << "7: " << s7 << endl;
return 0;
}
接下来就逐一说明:
-
无参构造了一个string
对象s1
,所以打印s1
时没有显示内容;
在前面的内容中已经对类和对象有了一定的了解,使用cout << s1 << endl;
打印类对象实际是<<
运算符重载 -
使用常量字符串"hello qqq"
构造对象s2
; -
拷贝构造,使用类对象s2
拷贝构造s3
; -
使用类对象s2
从下标为6的位置以后的元素构造s4
,第三个参数使用缺省参数;
这个构造函数的重载形式是有三个参数:const string& str
、size_t pos
、size_t len = npos
,表示用str
类中从pos
位置开始的len
个字符构造一个新类对象。len是一个缺省参数,缺省值为npos,string中定义npos的值为-1,放在一个无符号数len中就将是一个很大的数,即表示用从pos位置以后的所有元素构造 -
使用常量字符串"abcdef"
的前5个字符构造s5
, 即"abcde"
; -
使用6个字符'q'
构造string对象s6
; -
使用迭代器区间,从s6.begin()
到s6.end()
来构造s7
,这个区间是左闭右开的;
迭代器iterator
,提供了一种统一的方式来访问集合中的元素,而不需要了解底层集合的具体实现细节,使用时类似于指针(string中就是指针)。 这里的构造重载是一种模板,这意味着不仅可以使用string的迭代器区间来构造string对象。
容量操作
对于容量的操作,就是对成员变量_size
与_capacity
的操作,这里只介绍常用的几种
size与length
size()
与length()
的作用都是求string对象中的数据长度,他们的返回值相同,但是由于历史版本原因存在两个成员函数。
int main()
{
string s1("abcdefg");
cout << s1.size() << endl;
return 0;
}
需要注意的是:string对象与C字符串不同,C字符串以'\0'
为结束标志,所以在使用strlen
计算字符串长度时是以'\0'
为结束标志的;而对于string,没有结束标志,它的长度其中存储的字符的个数,也就是其属性_size
的值。
例如当string对象存储的字符序列中存在'\0'
:
int main()
{
string s1("abcdefg");
s1 += '\0';
s1 += "666666";
cout << s1.size() << endl;
}
可以通过resize()
函数来改变string中元素个数
capacity
capacity()
用于查看string对象的容量
int main()
{
string s1("abcdefg");
cout << s1.capacity() << endl;
return 0;
}
capacity
的值会大于等于size
的值,容量大于等于元素个数,这不难理解。
当我们修改string对象中的字符序列时,可能会出现修改后的size
大于capacity
的情况,这时就会发生扩容。扩容一般为一倍或一点五倍扩(不同环境下不同)。但是容量大小不会大于size的理论最大值,这个值可以通过max_size
函数查到,不同环境下的值会有不同。
vs2019下扩容演示(1.5倍):
int main()
{
string s1;
size_t old = 0;
while (s1.capacity() <= 128)
{
s1 += '6';
if (old != s1.capacity())
{
cout << s1.capacity() << endl;
old = s1.capacity();
}
}
return 0;
}
resize
resize
用于修改string对象字符序列的元素个数,即_size
的值
resize
函数有两个重载版本,可以将string对象的字符个数改为n
个:
当n
小于原string对象的_size
时,将保留原对象的前n
个字符,_size
的值为n
;
当n
大于原string对象的_size
时,将保留原字符,超出原_size
的部分使用传参指定的字符c
补足,若未传参,使用'\0'
补足。
int main()
{
//使用常量字符串初始化string对象s1
string s1("abcde");
cout << s1 << " " << s1.size() << endl;
//将s1元素个数删到3
s1.resize(3);
cout << s1 << " " << s1.size() << endl;
//使用字符!将s1元素个数增加到10
s1.resize(10, '!');
cout << s1 << " " << s1.size() << endl;
return 0;
}
reserve
reserve
用于修改string对象的容量,即_capacity
的值
reserve
接收一个size_t型的参数n
,表示将string对象的容量扩展至n
个字符(或大于n个字符),函数并不会改变string字符序列的长度(元素个数)。
(当n的值小于原对象的容量时,函数行为取决于容器实现,但最后的容量一定大于n
的值)
int main()
{
//使用常量字符串初始化string对象s1
string s1("abcde");
cout << s1 << " " << s1.capacity() << endl;
//将s1扩容至50
s1.reserve(50);
cout << s1 << " " << s1.capacity() << endl;
//将s1缩容至20
s1.reserve(20);
cout << s1 << " " << s1.capacity() << endl;
//将s1缩容至10
s1.reserve(10);
cout << s1 << " " << s1.capacity() << endl;
return 0;
}
在上面的测试中,当使用reserve
扩容s1为50时,容量增大到了63;缩容s1为20时,容量没有缩小;缩容s1为10时,容量缩小到了15。均符合上面的叙述。
clear
clear
可以清理string对象的字符序列,使其元素个数为0
int main()
{
string s1("abcdef");
cout << s1 << " " << s1.size() << endl;
s1.clear();
cout << s1 << " " << s1.size() << endl;
return 0;
}
empty
empty
用于判断string对象的字符序列是否为空
int main()
{
string s1("abcdef");
cout << s1 << " " << s1.empty() << endl;
//清理string后,判断其是否为空
s1.clear();
cout << s1 << " " << s1.empty() << endl;
return 0;
}
元素访问
在类和对象部分我们知道,在类外是不能访问类对象的属性的,但是对于string类,可以通过一些方式来访问string中的字符序列的元素:
迭代器访问
迭代器(iterator
)提供一种通用的方法,使其能够依序访问某个容器所含的各个元素,而又无需暴露该容器的内部表达方式。
迭代器的使用与指针类似,可以定义一个对于某个容器的迭代器变量,它指向该容器类型的对象中的某一元素。这意味着迭代器支持类似于指针的解引用*
、访问成员->
、加减+
、-
等操作。
对于string来说,它迭代器的类型为string::iterator
,表示这是对于string的迭代器类型,它就可以指向string对象字符序列中的某个元素(对于string来说迭代器底层就是原生指针):
int main()
{
string s1("abcdef");
//使用begin函数返回其第一个字符的迭代器
string::iterator it = s1.begin();
cout << *it << endl;
return 0;
}
有一些接口可以帮助我们使用迭代器:
begin
begin
可以返回string对象中指向第一个元素的迭代器
通过begin
函数返回的迭代器,可以访问string对象中的首元素,当然这个迭代器+1
就可以访问下一个元素:
int main()
{
string s1("abcdef");
//使用begin函数返回其第一个字符的迭代器
string::iterator it = s1.begin();
cout << *it << endl;
//it迭代器自增1,指向下一个元素
++it;
cout << *it << endl;
return 0;
}
end
end
可以返回string对象尾元素的下一个位置的迭代器
前面提到过,迭代器构成的区间是左闭右开的,所以作为整个字符序列的尾,应该指向的是尾元素的下一个位置。所以对这个返回值解引用就是非法访问,就会导致程序崩溃。
这个方法常用于与begin
结合,遍历整个string对象的字符序列:
int main()
{
string s1("abcdef");
string::iterator it = s1.begin();
while (it < s1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
rbegin
rbegin
返回一个sting对象尾元素的反向迭代器
反向迭代器 reverse_iterator
,与正向迭代器相反,+1
是向前移动,-1
是向后移动,不难理解,使用反向迭代器就可以实现倒着遍历string对象。
对rbegin
的返回值解引用就是sting对象的尾元素:
int main()
{
string s1("abcdef");
//使用rbegin函数返回其最后一个个字符的反向迭代器
string::reverse_iterator rit = s1.rbegin();
cout << *rit << endl;
//反向迭代器自增,向前移动
++rit;
cout << *rit << endl;
return 0;
}
rend
rend
返回一个string对象首元素前的位置的反向迭代器
rend
返回的就是倒着遍历string对象时,迭代器区间的右边,是一个开区间,所以指向的是首元素前的位置。
对其解引用就会崩溃,常用于与rdegin
结合倒序遍历string对象:
int main()
{
string s1("abcdef");
string::reverse_iterator rit = s1.rbegin();
while (rit < s1.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
return 0;
}
范围for
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号 :
分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围
范围for可以让我们更简洁的遍历容器中的元素,它依赖迭代器实现:
int main()
{
string s1("abcdef");
for (auto e : s1)
{
cout << e << " ";
}
cout << endl;
return 0;
}
auto
是一种新的类型指示符,可以简化繁琐的类型名的书写,在编译阶段auto
会替换为变量的实际类型
当然,范围for这里的auto也可由我们已知的类型(元素的类型)替换:
int main()
{
string s1("abcdef");
for (char e : s1)
{
cout << e << " ";
}
cout << endl;
return 0;
}
当需要在范围for中修改元素的值时,在类型后加上&
即可
int main()
{
string s1("abcdef");
for (char& e : s1)
{
++e;
cout << e << " ";
}
cout << endl;
return 0;
}
下标访问
使用[]
用下标访问string中的元素,实际是运算符重载operator[]
的函数调用:
用法上与数组的下标访问类似,不过该方法会做越界检查,而数组的越界会崩溃:
int main()
{
string s1("abcdef");
for (int i = 0; i < 6; ++i)
{
cout << s1[i] << " ";
}
cout << endl;
return 0;
}
字符串修改
string提供了一些方法来管理其中的字符序列:
insert
insert
可以实现在string对象的pos
位置插入一些字符
insert
有许多的重载版本,包括在pos
位置插入一个string对象(的部分)、插入一个C字符串(的前n个元素怒)、插入若干个指定字符、在迭代器位置添加若干字符、在迭代器位置添加一块迭代器区间的数据等等:
int main()
{
string s("ghijkl");
//在s1的第4个元素的位置插入string对象s
string s1("abcdef");
s1.insert(3, s);
cout << "1: " << s1 << endl;
//在s2的第四个位置插s的第3个位置的3个元素
string s2("abcdef");
s2.insert(3, s, 2, 3);
cout << "2: " << s2 << endl;
//在s3的第四个元素位置插入一个常量字符串
string s3("abcdef");
s3.insert(3, "hello 66");
cout << "3: " << s3 << endl;
//在s4的第四个元素的位置"hello 66"的前5个元素
string s4("abcdef");
s4.insert(3, "hello 66", 5);
cout << "4: " << s4 << endl;
//在s5的第四个元素位置插入6个'q'
string s5("abcdef");
s5.insert(3, 6, 'q');
cout << "5: " << s5 << endl;
//在s6的首元素位置插入6个'q'
string s6("abcdef");
s6.insert(s6.begin(), 6, 'q');
cout << "6: " << s6 << endl;
//在s7的首元素位置插入'q'
string s7("abcdef");
s7.insert(s7.begin(), 'q');
cout << "7: " << s7 << endl;
//在s8的首元素位置插入s
string s8("abcdef");
s8.insert(s8.begin(), s.begin(), s.end());
cout << "8: " << s8 << endl;
return 0;
}
(但是在string对象字符序列中间插入元素时,会发生移动,所以效率不高,尽量少用)
不难发现,string的接口是有些冗余的,这里面的一些接口,并不是必须要使用的
append
append
用于在string对象末尾追加一块内容
与上面类似,append
也实现了许多的重载版本,例如在string对象末尾插入一个string对象(的部分)、插入一个C字符串(的前n个元素)、插入若干个指定字符、插入一个迭代器区间中的数据等等
int main()
{
string s("ghijkl");
//在s1后追加s
string s1("abcdef ");//为观察清晰,在sn的末尾加上' '
s1.append(s);
cout << "1: " << s1 << endl;
//在s2后追加s从第一个元素开始的3个元素
string s2("abcdef ");
s2.append(s, 0, 3);
cout << "2: " << s2 << endl;
//在s3后追加"hello 66"
string s3("abcdef ");
s3.append("hello 66");
cout << "3: " << s3 << endl;
//在s4后追加"hello 66"的前5个元素
string s4("abcdef ");
s4.append("hello 66", 5);
cout << "4: " << s4 << endl;
//在s5后追加6个'q'
string s5("abcdef ");
s5.append(6, 'q');
cout << "5: " << s5 << endl;
//在s6后使用反向迭代器追加倒置的s
string s6("abcdef ");
s6.append(s.rbegin(), s.rend());
cout << "6: " << s6 << endl;
return 0;
}
push_back
push_back
即在string对象后尾插一个字符
int main()
{
string s1("abcdef ");//为观察清晰,在s1的末尾加上' '
//给s1尾插'q'
s1.push_back('q');
cout << "1: " << s1 << endl;
return 0;
}
operator+=
operator+=
即+=
的运算符重载,用于在string对象的末尾追加数据
使用+=
,可以在string对象的末尾添加一个string对象、C字符串、一个字符:
int main()
{
string s("ghijkl");
//给s1+=s
string s1("abcdef ");
s1 += s;
cout << "1: " << s1 << endl;
//s2+="qqqqqq"
string s2("abcdef ");
s2 += "qqqqqq";
cout << "2: " << s2 << endl;
//s3+='q'
string s3("abcdef ");
s3 += 'q';
cout << "3: " << s3 << endl;
return 0;
}
pop_back
pop_back
用于在string对象的尾删除一个字符
int main()
{
//对s1尾删
string s1("abcdef");
cout << "0: " << s1 << endl;
s1.pop_back();
cout << "1: " << s1 << endl;
}
erase
erase
用于删除string对象中的部分元素
erase
有3个重载版本,可以实现删除string对象pos
位置的len
个元素(缺省值为npos
)、迭代器位置的一个元素、迭代器区间中的元素:
int main()
{
//删除s1中第四个元素位置的2个元素
string s1("abcdef");
s1.erase(3, 2);
cout << "1: " << s1 << endl;
//删除第二个元素
string s2("abcdef");
s2.erase(s2.begin() + 1);
cout << "2: " << s2 << endl;
//删除s3中第二个元素到倒数第二个元素
string s3("abcdef");
s3.erase(s3.begin() + 1, s3.end() - 1);
cout << "3: " << s3 << endl;
return 0;
}
但是删除string对象字符序列中间的元素时,会发生移动,所以效率不高,尽量少用
字符串查找
find
find
用于在string对象中从指定位置向后查找子串
find
有4个重载版本,其中参数pos
为查找的起始位置。查找到则返回子串在原串中的位置,否则返回npos
(-1u)
需要注意的是,当寻找子串时,必须与原串中完全匹配才会返回值。
借助find
,可以实现将一个string对象依据某字符串分割(一个网址的分割):
int main()
{
string web("https://legacy.cplusplus.com/reference/string/string/find/");
size_t b1 = web.find("://");
if (b1 != string::npos)
{
size_t b2 = web.find("/", b1 + 3);
if (b2 != string::npos)
{
string protocol(web.begin(), web.begin() + b1);
string domain(web.begin() + b1 + 3, web.begin() + b2);
string uri(web.begin()+ b2 + 1, web.end());
cout << protocol << endl;
cout << domain << endl;
cout << uri << endl;
}
}
}
rfind
find
用于在string对象中从指定位置向前查找子串
rfind
与find
的区别只在于从 pos
位置开始向前寻找,在这里就不再赘述了
非成员函数
operator>>
operator>>
是流提取运算符重载,借助该运算符重载可以实现使用cin >>
方便的输入string对象
operator<<
operator<<
是流插入运算符重载,借助该运算符重载可以实现使用cout <<
方便的输入string对象
int main()
{
string s;
cin >> s;
cout << s << endl;
return 0;
}
总结
到此,关于string
类主要接口的介绍及使用就介绍完了
熟练使用string会使我们在处理字符序列数据时更加方面
接下来的文章中将模拟实现string类的主要接口,欢迎大家持续关注
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
希望与大家共同进步哦