C++string类
文章目录
![在这里插入图片描述](https://img-blog.csdnimg.cn/abfbb4d419ca4a59849c891af867eb69.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiU6KGM5LiU5oCdNjY=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
1.为什么要学习string类?
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,
但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可
能还会越界访问。
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本
都使用string类,很少有人去使用C库中的字符串操作函数。
2.标准库中的string类
介绍:
- string是表示字符串的字符串类
2.该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3.string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
4.不能操作多字节或者变长字符的序列。
5.使用string类时,必须包含#include头文件以及using namespace std;
1.string类对象的常见构造
函数名称 | 功能说明 |
---|---|
string() | 构造空的string类对象,即空字符串 |
string(const char* s) | 用C格式字符串构造string类对象 |
string(size_t n,char c) | string类对象中包含n个字符c |
string(const string&s) | 拷贝构造函数 |
void TestString(){
string s1;
string s2("hello world");
string s3(s2);
}
2.string类对象的容量操作
函数名称 | 功能说明 |
---|---|
size() | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty | 检测字符串释放为空串,是返回true,否则返回false |
clear | 清空有效字符 |
reserve | 为字符串预留空间 |
resize | 将有效字符的个数改成n个,多出的空间用字符c填充 |
void TestString(){
string s("hello");
cout<<s.size()<<endl;
cout<<s.length()<<endl;
cout<<s.capacity()<<endl;
cout<<s<<endl;
//清空,不改变底层大小
s.clear();
cout<<s,size()<<endl;
cout<<s.capacity()<<endl;
//将s中有效字符个数增加到10个,多出位置用'a'进行填充
s.resize(10,'a');
//s.resize(15);//将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
//s.resize(5); //将s中有效字符个数缩小到5个
cout<<s.size()<<endl;
cout<<s.capacity()<<endl;
}
注意:
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来填充多出的元素空间。
4.resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
3. string类对象的访问及遍历操作
operator | 返回pos位置的字符,const string类对象调用 |
---|---|
begin+end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | rend获取一个字符的迭代器 + rbegin获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
void TestString(){
string s("hello Bit");
// 3种遍历方式:
// 需要注意的以下三种方式除了遍历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后插入空格
str.push_back(' ');
//追加hello
str.append("hello");
//追加一个字符'a'
str+='a';
//追加字符串“world”
str+="world";
cout<<str<<endl;
cout<<str.c_str<<endl;//以C语言方式打印字符串
//获取file的前缀
string file("string.cpp");
size_t pos=file.rfind('.');
string suffix(file.substr(pos,file.size()-pos));
cout<<suffix<<endl;
//取出url的域名
string url("http://www.cplusplus.com/reference/string/string/find/");
cout<<url<<endl;
size_t start=url.find("://");
if (start == string::npos)
{
cout << "invalid url" << endl;
return;
}
start += 3;
size_t finish = url.find('/', start);
string address = url.substr(start, finish - start);
cout << address << endl;
// 删除url的协议前缀
pos = url.find("://");
url.erase(0, pos+3);
cout<<url<<endl;
}
注意:
- 在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
- 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
5.string类成员函数
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
---|---|
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline | 获取一行字符串 |
relational operators | 大小比较 |
反转字母:
class Solution {
public:
bool isletter(char ch){
if(ch>='a'&&ch<='z'){
return true;
}
if(ch>='A'&&ch<='Z'){
return true;
}
return false;
}
string reverseOnlyLetters(string s) {
if(s.empty()){
return s;
}
size_t begin=0;
size_t end=s.size()-1;
while(begin<end){
while(begin<end&&!isletter(s[begin])){
begin++;
}
while(begin<end&&!isletter(s[end])){
end--;
}
swap(s[begin],s[end]);
begin++;
end--;
}
return s;
}
};
找到字符串中只出现一次的字符:
class Solution {
public:
int firstUniqChar(string s) {
int count[256]={0};
for(int i=0;i<s.size();i++){
count[s[i]]+=1;
}
for(int i=0;i<s.size();i++){
if(count[s[i]]==1){
return i;
}
}
return -1;
}
};
字符串里最后一个字符的长度
#include<iostream>
#include<string>
using namespace std;
int main(){
string char;
while(getchar(cin,char)){
size_t pos=char.rfind(' ');
cout<<char.size()-pos-1<<endl;
}
return 0;
}
验证一个字符串是否是回文:
class Solution {
public:
bool isvalid(char ch){
return (ch>='a'&&ch<='z')
||(ch>='A'&&ch<='Z')
||(ch>='0'&&ch<='9');
}
bool isPalindrome(string s) {
for(auto& ch : s)
{
if(ch >= 'a' && ch <= 'z')
ch -= 32;
}
int begin=0;
int end=s.size()-1;
while(begin<end){
while(begin<end&&!isvalid(s[begin])){
begin++;
}
while(begin<end&&!isvalid(s[end])){
end--;
}
if(s[begin]!=s[end]){
return false;
}else{
begin++;
end--;
}
}
return true;
}
};
字符串相加:
class Solution {
public:
string addStrings(string num1, string num2) {
string addret;
int cur=0,i=num1.size()-1,j=num2.size()-1;
while(i>=0||j>=0||cur!=0){
if(i>=0){
cur+=num1[i--]-'0';
}
if(j>=0){
cur+=num2[j--]-'0';
}
addret+=to_string(cur%10);
cur/=10;
}
reverse(addret.begin(),addret.end());
return addret;
}
};
3.string类的模拟实现(常用)
1.实现string类的构造、拷贝构造、赋值运算符重载以及析构函数
class string{
public:
string(const char* str="")
{
if(nullptr==str){
assert(false);
return;
}
_str=new char[strlen(str)+1];
strcpy(_str,str);
}
~string(){
if(_str){
delete[] _str;
_str=nullptr;
}
}
private:
char* _str;
};
void Teststring(){
string s1("hello");
string s2(s1);
}
上述string类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构 造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝
2.深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情
况都是按照深拷贝方式提供。
1.传统写法的string类
class string{
public:
string(const char* str=""){
if(nullptr==str){
assert(false);
return;
}
_str=new char[strlen(str)+1];
strcpy(_str,str);
}
string(const string& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str,s._str);
}
string& operator=(const string& s)
{
if(this!=&s){
char* temp=new char[strlen(s.s_str)+1];
strcpy(temp,s._str);
delete[] _str;
_str=temp;
}
return *this;
}
~string(){
if(_str){
delete[] str;
_str=nullptr;
}
}
private:
char* _str;
};
2.现代版写法的string类
class string{
public:
string(const char* str=""){
if(nullptr==str){
str="";
}
_str=new char[strlen(str)+1];
strcpy(_str,str);
}
string(const string& s)
:_str(nullptr)
{
string temp(s._str);
swap(_str,temp._str);
}
string& operator=(const string& s){
if(this!=&s){
string temp=(s._str);
swap(_str,temp._str);
}
return *this;
}
~string(){
if(_str){
delete[] _str;
_str=nullptr;
}
}
private:
char* _str;
};
4.string类的扩容机制
VS下:
#include<iostream>
#include<windows.h>
#include<string>
using namespace std;
void TestPushBack(){
string s;
size_t sz = s.capacity();
for (int i = 0; i < 100;i++){
s.push_back('c');
if (sz!=s.capacity()){
sz = s.capacity();
cout << sz << endl;
}
}
}
int main(){
TestPushBack();
system("pause");
return 0;
}
Linux下:
结论:
- string在vs下扩容方式是以2倍的方式扩容的
- 在Linux下扩容方式是以1.5倍的方式扩容的