C++ std::string类介绍
在C++中,std::string类是标准模板库(Standard Template Library,STL)d 一部分,它提供了对字符串的封装,使得字符串的处理变动更加方安全和高效。与传统都C语言分隔字符串(字符串是以'\0'结尾的一些字符的合集)相比,std::string类提供了丰富的成员函数来执行各种字符串操作,如连接、查找、比较、替换等等,同时自动管理内存,减少了内存泄漏的风险。
1.标准库中的string类
1.1string类的了解
可以参考文献cplusplus.com - The C++ Resources Network
1.2 auto和范围for
auto关键字
- 在早期的C/C++中auto的含义是:使用auto修饰的变量是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会规定:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须有编译器在编译时期推导而得。
- 用auto声明指针类型时,用auto和auto*没有任何区别,但是auto声明引用类型时必须加&
- 当在同一行声明多个变量时,这些变量必须是相同类型,否者编译器将会报错,因为编译器实器只对第一个类型进行推导,然后用推导出俩的类型定义其他变量
- auto不能作为函数的参数,可以做返回值但是建议谨慎使用
- auto不能用来直接声明数组
int func1()
{
return 10;
}
int main()
{
int a = 0;
auto b = a;
auto c = 'a';
auto d = func1();
cout << typeid(b).name() << endl;//int
cout << typeid(c).name() << endl;//char
cout << typeid(d).name() << endl;//int
return 0;
}
int main()
{
auto e;
cout << e << endl;
return 0;
}
解析:使用auto来推导变量的类型时必须初始化变量,否者根本无从推导变量的类型,编译器会报错,这一点和引用类似。
int main()
{
int x = 10;
auto y = &x;
auto* z = &x;
cout << typeid(x).name() << endl;//int
cout << typeid(y).name() << endl;//int * __ptr64
cout << typeid(z).name() << endl;//int * __ptr64
return 0;
}
解析:变量y和z都是指向变量x的指针,用auto声明指针类型时,用auto和auto*没有任何区别,所以两者的类型相同
int main()
{
auto aa = 1, bb = 2;
auto cc = 3, dd = 4.0;
return 0;
}
解析:当在同一行声明多个变量时,这些变量必须是相同类型,否者编译器将会报错,如在上面例子中aa和bb变量在同一行定义并且是相同类型的变量,所以不会报错;而在同一行定义的cc和dd变量一个是整形,一个是浮点型变量所以编译器报错。
int main()
{
auto arr[] = { 1,2,3,4 };
return 0;
}
解析:auto不能用来直接声明数组
int func1()
{
return 10;
}
void func2()
{
func1();
}
auto func3()
{
return func2();
}
解析:使用auto做函数的返回值时,有时候需要调用多个函数才能知道返回类型。如在上面代码中想知道func3的返回值类型需要回去看func2的返回值类型,想知道func2的返回值类型需要回去看func1的返回值类型,这样就有一些麻烦了,特别是当需要调用函数多时,会降低程序的运行效率。
范围for
- 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基本范围的for循环。for循环后的括号由冒号 “:”分为两部分:第一部分是范围内的用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
- 范围for可以作用到数组和容器对象上的遍历
- 范围for的底层很简单,容器遍历实际就是替换为迭代器
int main()
{
int arr[] = { 1,2,3,4,5 };
for (auto e : arr)
{
e *= 2;
}
for (auto e : arr)
{
cout << e;
}
cout << endl;
return 0;
}
int main()
{
int arr[] = { 1,2,3,4,5 };
//只能访问而不能修改数组的值
/*for (auto e : arr)
{
e *= 2;
}*/
for (auto& e : arr)
{
e *= 2;
}
for (auto e : arr)
{
cout << e;
}
cout << endl;
return 0;
}
解析:上面两个代码的功能是一样的,都是访问数组中各元素的值并且修改元素的值,只不过需要注意的是使用auto来修改数组中的值时需要使用auto&。
int main()
{
string s1("hello world");
for (auto ch : s1)
{
cout << ch;
}
cout << endl;//hello world
return 0;
}
1.3string类的常见接口
1.3.1string类对象的常见构造
函数名称(constructor) | 功能说明 |
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char*s) (重点) | 用C-string来构造string类对象 |
string(size_t n,char c) | string类对象中包含n个字符c |
string(const string& s) (重点) | 拷贝构造函数 |
int main()
{
string s1();//构造空的string类对象s1
string s2("hello world");//用C格式字符串构造string类对象s2
string s3(s2);//拷贝构造s3
cout << s2 << endl;//hello world
cout << s3 << endl;//hello world
return 0;
}
1.3.2string类对象的容量操作
函数名称 | 功能说明 |
size (重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串是否为空 |
clear (重点) | 清空有效字符 |
reserve (重点) | 为字符串预留空间 |
resize (重点) | 将有效字符的个数改成n个,多出的空间用字符c填充 |
微提醒:
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来填充多出的空间。特别需要注意的是:resize在改变元素个数时,如果是将元素个数增加,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变
4.reserve(size_t res_arg = 0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间大小时,reserve不会改变容量大小
int main()
{
string s1("hello world");
//大小
cout << s1.size() << endl;//11
cout << s1.length() << endl;//11
//空间
cout << s1.capacity() << endl;//15
return 0;
}
微提醒:如在上面例子中:size和length计算出的结果一样
int main()
{
string s1("good lucky");
cout << s1.size() << endl;
cout << s1.capacity() << endl<<endl;
//比size和capacity都小
s1.reserve(5);
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
//在size和capacity之间
s1.reserve(13);
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
//比size和capacity都小
s1.reserve(20);
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
return 0;
}
微提醒:
在上面例子中为s1保留5个字节大小时(比size和capacity都小),s1的size和capacity都没有改变;为s1保留20个字节大小时(比size和capacity都大),s1的size没有改变,但是capacity扩容了;在这个过程中s1的size一直都没有改变,这表明在vs编译器上,reserve只会改变capacity的大小而不会改变size的大小
int main()
{
string s1("good day");
cout << s1.size() << endl;
cout << s1.capacity() << endl<<endl;
//比size和capacity都小
s1.resize(4);
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
//在size和capacity之间
s1.resize(10);
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
//比size和capacity都大
s1.resize(20);
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
return 0;
}
微提醒:由上面例子可以知道当为s1的大小改变为5个字节大小时(比size和capacity都小),s1的size改变而capacity没有改变;使s1大小改变为20个字节大小时(比size和capacity都大),s1的size和capacity都扩容了;在这个过程中s1的size一直在改变,这表明在vs编译器上,reserve会改变capacity的大小和size的大小
1.3.3string类对象的访问及遍历操作
函数名称 | 功能说明 |
operator[](重点) | 返回pos位置的字符,const string类对象调用 |
begin + end | begin获取一个字符的迭代器 +end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | begin获取一个字符的迭代器 +end获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持更简明的范围for的新遍历方式 |
int main()
{
string s1("happy day");
//operartor[]
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i];
}
cout << endl;//happy day
//begin+end
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it;
it++;
}
cout << endl;//happy day
//rbegin + rend
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit;
rit++;
}
cout << endl; //yad yppah
//范围for
for (auto e : s1)
{
cout << e;
}
cout << endl;//happy day
return 0;
}
微提醒:operator[]的访问方式相当于数组用下标访问一样,没有特别大的难度;begin和end的使用需要使用到迭代器iterator,在这个代码中的it变量我们可以看做是指针,他的使用方式和指针相似但却不是指针;而rbegin和rend的使用需要使用的reserve_iterator迭代器,和iterator迭代器相似,只不过是开始结束位置相反
1.3.4string类对象的修改操作
函数名称 | 功能说明 |
push_back | 在字符串后面尾插字符c |
append | 在字符串后追加字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str (重点) | 返回C格式字符串 |
find + npos (重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind (重点) | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
int main()
{
string s1("lucky day");
s1.push_back('f');
cout << s1 << endl;//lucky dayf
s1 += 'o';
cout << s1 << endl;//lucky dayfo
s1 += ' ';
s1.append("for me");
cout << s1 << endl;//lucky dayfo for me
return 0;
}
int main()
{
string s1("happy day");
size_t pos = s1.find('d');
cout << pos << endl;//6
size_t p = s1.rfind('h');
cout << p << endl;//8
return 0;
}
微提醒:find是从左向右开始查找字符,rfind是从右向左查找字符,两者都是返回该字符在字符串中的位置下标
int main()
{
string s("hello world lucky day good");
string s1 = s.substr(6, 5);
cout << s1 << endl;//world
string s2 = s.substr(6);
cout << s2 << endl;//world lucky day good
string s3("hello world");
string s4 = s3.substr(6, 10);
cout << s4 << endl; //world
return 0;
}
微提醒:substr的第一个参数是从这个下标开始截取字符串,第二个参数表示截取的字符个数,如果第二个参数没有给,那么编译器会默认截取到该字符串的最后一个字符为止;如果第二个参数的大小大于剩余字符串的大小,也是默认取到最后一个字符为止。如在上面例子中,s1是从第六个位置开始截取5个字符,则为world;s2是从第六个字符开始截取,由于第二个参数没写则是默认取到最后一个字符为止,则为world lucky day good;s3是从第六个字符开始截取,由于第二个参数10大于剩余字符串(world)的大小,所以也是取到最后一个字符为止,则为world
微提醒:
1.在string尾部追加字符时,push_back,append,operator+=三种的实现方式差不多。一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串
2.对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好