目录
1. C/C++中的字符串
1.1. C语言中的字符串
1.2. C++中的字符串
2. string的接口
2.1. string的迭代器
2.1.1begin()与end()函数
2.2.2 rbegin()与rend()函数
2.2. string的初始化与销毁
2.3. string的容量操作
2.3.1 size(),length(),capacity()
2.3.2 empty()函数
2.3.3. clear()函数
2.3.4 resize()
2.3.5 reverse()
2.4. string的访问操作
2.4.1 operator[]与at
2.4.2 back()
2.4.3 front ()
2.5. string的修改操作
2.5.1. 字符串的增加
2.5.1.1 push_back(),operator+=,insert(),append()
2.5.2. 字符串的替换
2.5.2.1 assign()
2.5.2.2 replace ()
2.5.3. 字符串的删除
2.5.3.1 pop_back()
2.5.3.2 erase ()
2.5.4. 字符串的交换
2.6 string类对象的操作(operations)
2.6.1、c_str(重点)
2.6.2 find()
2.6.3 refind()
2.6.4 substr()
2.6.5 fid_first_of
2.6.6 find_last_of ()
2.6.7 find_last_not_of
2.7string类对象的非成员函数
2.7.1 swap
2.7.2 operator>编辑
2.7.3 getline
???? 博客主页:C-SDN花园GGbond
⏩ 文章专栏:玩转c++
1. C/C++中的字符串
1.1. C语言中的字符串
在 C 语言中,字符串是由字符组成的字符数组,以空字符 '\0'
作为结束标志。由于数组特点,字符串的大小在定义数组时就已经确定,无法更改。
//数组大小为20
char str[20] = "hello world!\n";
当然我们可以通过动态内存分配来来解决这个问题,但无疑非常繁琐。
void Test1()
{
char* str = NULL;
int len = 0;
// 初始分配一些内存
str = (char*)malloc(10 * sizeof(char));
if (str == NULL) {
perror("malloc fail");
return 1;
}
strcpy(str, "Hello");
len = strlen(str);
// 根据需要扩展字符串
str = (char*)realloc(str, (len + 6) * sizeof(char));
if (str == NULL) {
perror("realloc fail");
return 1;
}
strcat(str, " World");
printf("%s\n", str);
//最后释放内存
free(str);
}
1.2. C++中的字符串
然C++兼容C语言,在C++中仍然可以使用C语言的字符串,但是C++自己实现了一个关于处理字符串的类–string
,它提供了许多方便的操作和功能,使得字符串的处理更加安全和高效。下面是一个简单的string
的使用:
#include<iostream>
#include<string>
using namespace std;
void Test2()
{
string str = "hello world!";
cout << str << endl;
//改变第一个字符
str[0]++;
cout << str << endl;
//在末尾添加一个字符
str += 'e';
cout << str << endl;
//在末尾添加一个字符串
str += " hello";
cout << str << endl;
}
int main()
{
Test2();
return 0;
}
2. string的接口
C++为我们提供了丰富的string接口,我们可以通过对象来调用 我们可以通过查询相关文档学习
c++Reference
2.1. string的迭代器
迭代器(Iterator)是一种用于遍历容器中元素的工具。它提供了一种统一的方式来访问容器中的元素,而无需关心容器的具体实现细节。对于string迭代器,我们在使用时将其当做指针使用即可。
在string
类中,我们就可以通过迭代器来访问其具体元素,并且也为我们提供了相应的调用函数。
2.1.1begin()与end()函数
begin()
与end()
函数的使用方法具体如下:
begin():
作用:返回指向字符串第一个字符的迭代器。
返回值:普通对象返回
iterator
迭代器,const 对象返回const_iterator
迭代器。
end():
- 作用:返回指向字符串最后一个字符下一个位置的迭代器。
- 返回值:普通对象返回
iterator
迭代器,const 对象返回const_iterator
迭代器。
2.2.2 rbegin()与rend()函数
作用:返回指向字符串最后一个字符位置(即其反向开头)的反向迭代器。
返回值:普通对象返回
iterator
迭代器,const 对象返回const_iterator
迭代器。
- 作用:返回指向字符串第一个字符前面一个位置的反向迭代器。
- 返回值:普通对象返回
iterator
迭代器,const 对象返回const_iterator
迭代器。
#include <iostream>
#include <string>
using namespace std;
void Test3()
{
string s1 = "hello world!";
// 普通迭代器
string::iterator it = s1.begin(); // 指向第一个字符
while (it != s1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
const string s2 = "hello wrold!"; // const 对象
// 使用 const_iterator 遍历 const 字符串
string::const_iterator itt = s2.begin(); // 指向第一个字符
while (itt != s2.end())
{
cout << *itt << " ";
++itt;
}
cout << endl;
// 使用反向迭代器遍历 const 字符串
string::const_reverse_iterator rit = s2.rbegin(); // 指向最后一个字符
while (rit != s2.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
int main() {
Test3();
return 0;
}
2.2. string的初始化与销毁
因为string
是一个类,所以我们在初始化时肯定调用其构造函数初始化。以下就是我们常见初始化的接口:
第一个使我们的默认构造函数,不需要传参。
第二个使用的是拷贝构造来初始化。
第三个是使用一个string的某段区间初始化,其中pos是字符串下标,npos是指无符号整数的最大值。
第四个使用的是某个字符数组初始化。
第五个使用的是某个字符数组前n个字符来初始化
第六个使用的是n个c字符初始化。
第七个使用的是某段迭代器区间初始化。
最后也能通过赋值运算符重载初始化。
void Test5()
{
//1. 使用我们的默认构造函数,不需要传参。
string s1;
s1 = "hello world!";
//2. 使用的是拷贝构造来初始化。
string s2(s1);
//3. 使用一个string的某段区间初始化,其中pos是字符串下标,npos是指无符号整数的最大值。
string s3(s2, 1, 7);
//4. 使用的是某个字符数组初始化。
string s4("hello world!");
//5. 使用的是某个字符数组前n个字符来初始化
string s5("hello world!", 5);
//6. 使用的是n个c字符初始化。
string s6(7, 'a');
//7. 使用的是某段迭代器区间初始化。
string s7(s1.begin(), s1.end());
//赋值运算符重载初始化
string s8 = "hello world!";
cout <<"s1:" << s1 << endl;
cout << "s2:" << s2 << endl;
cout << "s3:" << s3 << endl;
cout << "s4:" << s4 << endl;
cout << "s5:" << s5 << endl;
cout << "s6:" << s6 << endl;
cout << "s7:" << s7 << endl;
cout << "s8:" << s8 << endl;
}
int main()
{
//Test2();
Test5();
return 0;
}
2.3. string的容量操作
string
类常见的容量操作:
函数名称 功能
size 返回字符串的有效长度
length 返回字符串的有效长度
capacity 返回字符串的容量大小
max_size 返回字符串的最大长度
clear 清空字符串
empty 检查是否为空串,是则返回ture,否则返回false
reserve 请求改变字符串的容量
resize 重新设置有效字符的数量,超过原来有效长度则用c字符填充
shrink_to_fit 收缩资字符串容量
2.3.1 size()
,length(),capacity()
在string
类中,可以通过size()
,length()
返回字符串的有效长度;capacity()
返回字符串的容量,其具体效果如下图
#include <iostream>
#include <string>
using namespace std;
void Test6()
{
string s("hello world!");
cout << s.size() << endl;//有效长度
cout << s.length() << endl;//有效长度
cout << s.capacity() << endl;//容量大小
cout << s.max_size() << endl;//最大大小
}
int main() {
Test6();
return 0;
}
其中有效长度size
以及容量大小capacity
不包括\0
。而max_size
返回字符串最大容量,不同平台下大小可能不一样。
探究一下string
的扩容机制
//探究一下string的扩容机制
void TestCapacity()
{
string s;
size_t sz = s.capacity();
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
int main() {
//Test6();
TestCapacity();
return 0;
}
在VS2022
编译器中,string
大概是以1.5
倍扩容,但是在g++
编译器中却是2
倍扩容。
2.3.2 empty()
函数
用来判断字符串是否为空:
void TestEmpty()
{
string s1("");//空串
string s2("hello ");
if (s1.empty())
{
cout << "s1为空串" << endl;
}
else
{
cout << "s1不为空串" << endl;
}
if (s2.empty())
{
cout << "s2为空串" << endl;
}
else
{
cout << "s2不为空串" << endl;
}
}
2.3.3. clear()
函数
void TestClear()
{
string s1("hello world!");
cout <<"s1的有效长度为:"<< s1.size() << endl;
cout <<"s1的容量大小为:"<< s1.capacity() << endl;
s1.clear();
cout << "s1的有效长度为:" << s1.size() << endl;
cout << "s1的容量大小为:" << s1.capacity() << endl;
if (s1.empty())
{
cout << "s1是空串" << endl;
}
}
总结:lear()
函数 改变有效长度size
,但不会改变容量capacity
。
2.3.4 resize()
int main() {
//Test6();
//TestCapacity();
string s("hello world!");
cout << s << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
s.resize(5);
cout << s << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
return 0;
}
int main() {
//Test6();
//TestCapacity();
string s1("hello world!");
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.resize(100);
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
string s2("hello world!");
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
s2.resize(100,'x');
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
return 0;
}
总结:会去改变size,减少的话就是变少(不会改变容量),如果增多的话就可能会扩容顺便帮助我们初始化,第一个版本的话初始化补\0,第二个版本的话就是补自己想要初始化的内容
2.3.5 reverse()
void Test8()
{
string s1("hello world!");
cout << "reserve测试:" << endl;
cout << s1 << endl;
cout << "s1的有效长度为:" << s1.size() << endl;
cout << "s1的容量大小为:" << s1.capacity() << endl;
s1.reserve(5);
cout << s1 << endl;
cout << "s1的有效长度为:" << s1.size() << endl;
cout << "s1的容量大小为:" << s1.capacity() << endl;
s1.reserve(13);
cout << s1 << endl;
cout << "s1的有效长度为:" << s1.size() << endl;
cout << "s1的容量大小为:" << s1.capacity() << endl;
s1.reserve(25);
cout << s1 << endl;
cout << "s1的有效长度为:" << s1.size() << endl;
cout << "s1的容量大小为:" << s1.capacity() << endl;
cout << endl;
}
通过上述实验,我们可以总结出以下规律:
当n<sz时,reserve并不会发生任何改变,resize会删除有效字符到指定大小。
当sz<n<capcity时,reserve并不会发生任何改变,resize会补充有效字符(默认为’\0)到指定大小。
当n>capacity时,reserve会发生扩容,resize会补充有效字符(默认为’\0)到指定大小。
2.4. string的访问操作
string
常见的访问函数:
2.4.1 operator[]与at
首先是operator[]
这个运算符重载与at
函数,它们的功能类似都是返回指定下标字符,并且char*
类型返回char*
类型,const char*
类型返回const char*
类型。
void test1()
{
string s1("hello GGbond");
cout << s1 << endl;
s1[0] = 'x';
cout << s1 << endl;
for (int i = 0; i < s1.size(); i++)
{
cout << s1.at(i);
}
}
int main()
{
test1();
}
C++11引入的front
与back
函数:
2.4.2 back()
void test1()
{
string s1("hello GGbond");
//string s2("");
cout << s1 << endl;
s1.back()='2';
//s2.back() = '2';
cout << s1.back()<< endl;
for (int i = 0; i < s1.size(); i++)
{
cout << s1.at(i)<<' ';
}
cout << endl;
}
int main()
{
test1();
}
不能在空字符串中使用
2.4.3 front ()
void test2()
{
string s1("hello GGbond");
//string s2("");
cout << s1 << endl;
s1.front() = '2';
//s2.front() = '2';
cout << s1.front() << endl;
for (int i = 0; i < s1.size(); i++)
{
cout << s1.at(i) << ' ';
}
cout << endl;
}
int main()
{
test2();
}
front()同样不能在空字符串中使用
2.5. string的修改操作
函数名称 功能
push_back 在字符串后追加字符
operator+= 在字符串后追加字符或字符串
append 在字符串后追加字符串
insert 在指定位置追加字符或者字符串
assign 使用指定字符串替换原字符串
replace 用新字符串替换原字符串指定区间
pop_back 删除字符串最后一个字符
erase 删除字符串指定部分
swap 交换两个字符串
string
关于修改的函数的接口都比较多,一一列举比较麻烦,这里我们只重点介绍常用的接口,剩下的大家具体使用时查官方文档即可。下面是常见的关于string
修改的函数接口:
2.5.1. 字符串的增加
首先我们来介绍字符串的增加操作,在末尾添加字符我们可以使用push_back
,在末尾添加字符串我们可以使用append
,而operator+=
既可以在末尾添加字符,也可以添加字符串,insert
可以在任意位置追加字符或者字符串。
2.5.1.1 push_back(),operator+=,insert(),append()
void test2()
{
string s1("hello GGbond");
cout << "push_pack测试:" << endl;
s1.push_back('!');
//s1.push_back("字符串");error
cout << s1 << endl;
cout << "+=测试:" << endl;
s1 += '!';
cout << s1 << endl;
s1 += "??????";
cout << s1 << endl;
cout << "append测试:" << endl;
s1.append("append字符串");
cout << s1 << endl;
//s1.append('字');error
cout << "insert测试:" << endl;
s1.insert(0,5, '1');
cout << s1 << endl;
s1.insert(2, "字符串");
cout << s1 << endl;
}
int main()
{
test2();
}
append
与insert
的接口不止这些,下面是具体的的接口,需要时直接插文档即可
2.5.2. 字符串的替换
介绍两个字符串替换的函数assign
以及replace
,其中assign
是直接替换掉原字符串,而replace
是替换原字符串的某段区间。
2.5.2.1 assign()
string::assign 赋值(用法与append几乎一样 但赋值前 原字符串会被clear())
string& assign ( const string& str );
string& assign ( const string& str, size_t pos, size_t n );
string& assign ( const char* s, size_t n );
string& assign ( const char* s );
string& assign ( size_t n, char c );
template <class InputIterator>
string& assign ( InputIterator first, InputIterator last );
/**
* created by Liu Xianmeng on 2022/12/5
*/
#include <bits/stdc++.h>
using namespace std;
int main(){
string str;
string base="The quick brown fox jumps over a lazy dog.";
str.assign(base);
cout << str << endl;
str.assign(base,10,9);
cout << str << endl; // "brown fox"
str.assign("pangrams are cool",7);
cout << str << endl; // "pangram"
str.assign("c-string");
cout << str << endl; // "c-string"
str.assign(10,'*');
cout << str << endl; // "**********"
str.assign(10,0x2D);
cout << str << endl; // "----------"
str.assign(base.begin()+16,base.end()-12);
cout << str << endl; // "fox jumps over"
/* 【打印结果】
The quick brown fox jumps over a lazy dog.
brown fox
pangram
c-string
**********
----------
fox jumps over
*/
return 0;
}
void Test13()
{
string s1("hello world!");
string s2;
//直接用s1替换s2
s2.assign(s1);
cout << s2 << endl;
//用s1的某个位置开始替换几个字符
s2.assign(s1,4 ,6);
cout << s2 << endl;
//用常量字符串替换
string s3;
s3.assign("wwwwwwwwwwwwwww");
cout << s3 << endl;
//用常量字符串前n个字符
string s4;
s4.assign("wwwwwwwwwwwwwww", 5);
cout << s4 << endl;
//用n个字符c
s2.assign(5, '6');
cout << s2 << endl;
//用迭代器区间
s2.assign(s1.begin() + 1, s1.end() - 1);
cout << s2 << endl;
}
int main()
{
//test2();
Test13();
}
2.5.2.2 replace ()
void Test13()
{
string s1("hello world!");
string s3("i am GGbond!");
//用s1替换掉2下标长度为2的区间
s3.replace(2, 2, s1);
cout << s3 << endl;
//用字符数组前n个字符替换原字符下标为pos开始的len个zif
s3.replace(0, 2, "hhhh", 2);//string& replace (size_t pos, size_t len, const char* s, size_t n);
cout << s3 << endl;
}
assign
是直接替换掉原字符串原字符串会被clear(),而replace
是替换原字符串的某段区间。
2.5.3. 字符串的删除
2.5.3.1 pop_back()
pop_back
支持删除最后一个字符
void test3()
{
string s1("hello world!");
cout << s1 << endl;
s1.pop_back();
cout << s1 << endl;
}
int main()
{
test3();
}
2.5.3.2 erase ()
void Test1()
{
string s("hello world!");
cout << s << endl;
//删除迭代器所指字符
s.erase(s.begin());
cout << s << endl;
//删除0下标长度为3的一段区间
s.erase(0, 3);
cout << s << endl;
//删除一段迭代器区间
s.erase(s.begin(), s.end() - 2);
cout << s << endl;
//用默认值删除完
s.erase();
cout << s << endl;
}
int main()
{
Test1();
}
2.5.4. 字符串的交换
swap()
交换两个字符串
void Test2()
{
//使用成员函数swap
string s1("hello world!");
string s2("1111111111");
s1.swap(s2);
cout << s1 << endl;
cout << s2 << endl;
//使用全局std的swap函数
swap(s1, s2);
cout << s1 << endl;
cout << s2 << endl;
}
int main()
{
Test2();
}
明明全局swap也可以达到交换的效果,那string里面也实现一个swap的成员函数有必要吗??
成员swap:
综上,要尽量使用成员函数的swap
2.6 string类对象的操作(operations)
2.6.1、c_str(重点)
返回一个指向C类型的字符串指针,下面介绍他的用处:
void Test3()
{
string s1("hello world!");
cout << s1 << endl;
cout << s1.c_str() << endl;
cout << (int*)s1.c_str() << endl;
cout << (void*)s1.c_str() << endl;
}
int main()
{
Test3();
}
我们可以观察到,s1.c_str()返回的其实是一个char*指针,但是为什么打印出来的不是地址呢??因为cout可以自动识别类型,对于char*类型的指针他会把它当成是字符串去处理,只要指针不是char*类型的,都会当成打印地址。
void Test3()
{
string s1("hello world!");
cout << s1 << endl;
cout << s1.c_str() << endl;
cout << (int*)s1.c_str() << endl;
cout << (void*)s1.c_str() << endl;
s1 += '\0';
s1 += "111111111";
cout << s1 << endl;
cout << s1.c_str() << endl;
}
int main()
{
Test3();
}
我们会发现,当我们尾插‘\0’后再插入一些字符,打印出来的结果就不一样了,因为对于c语言来说,字符串默认是读取到\0停止,但是对于string来说,读取多少是取决于他的成员变size!!
2.6.2 find()
找一个字符里的子串是否存在,如果存在,返回对应的第一个字符的下标,如果不存在,就会返回string::npos。
1)(2)(4)版本差不多,区别是一个是找string类,一个是找常量字符串,一个是找字符。pos的缺省值0,不传的话就是从头开始遍历往后找,我们也可以通过pos来缩小查找的范围。
void Test3()
{
string s1("hello world!");
cout << s1 << endl;
cout << s1.find("ell") << endl;
cout << s1.find("abc") << endl;
cout << s1.find("w") << endl;
}
int main()
{
Test3();
}
(3)版本就是 找常量字符串从pos位置开始的n个字符
2.6.3 refind()
和find的区别就是默认是pos开始从后往前找
void Test4()
{
string s1("a.b.c.d.e");
cout << s1 << endl;
cout << s1.rfind(".") << endl;
cout << s1.rfind("d") << endl;
cout << s1.rfind(".",5) << endl;
}
int main()
{
Test4();
}
2.6.4 substr()
void Test5()
{
string s1("hello.com");
size_t pos = s1.find('.');
cout << s1.substr(pos, 4) << endl;
cout << s1.substr(pos) << endl;
cout << s1.substr(pos,s1.size()-pos )<< endl;
}
int main()
{
Test5();
}
从pos位置开始截取len个字符返回,不传len就是默认全部返回(经常和find以及rfind配合使用)
如果我们不知道几个字符,s1.size()-pos刚好是剩下所有的字符,或者就干脆不传,这时候相当于就是把后面的全部打印完
2.6.5 fid_first_of
找到第一个匹配的子串中任何一个字符的下标
当指定pos时,搜索只包括位置pos处或之后的字符,忽略pos之前可能出现的任何字符。
void Test6()
{
string s1("hello wrrld");
cout<<s1.find_first_of("abcde")<<endl;
cout << s1.find_first_of("abcd") << endl;
}
int main()
{
Test6();
}
2.6.6 find_last_of ()
找到最后一个与子字符串任意一个字符匹配的下标。其实可以理解从后往前找第一个
当指定pos时,搜索仅包括位置pos处或之前的字符,忽略pos之后可能出现的任何字符。
void Test6()
{
string s1("hello world");
cout << s1.find_last_of("o") << endl;//不是4是7
}
int main()
{
Test6();
}
2.6.7 find_last_not_of
跟前两个类似,区别就是找第一个不匹配的和找最后一个不匹配的
2.7string类对象的非成员函数
2.7.1 非成员 swap
为了减少拷贝构造防止调用std标准模板库中swap,使用全局swap,底层实际调用成员函数swap函数
2.7.2 operator<< operator>>
从c的字符串数组到c++的string类,原先打印读取字符串是默认读取到\0,但是封装乘string类后他有了自己的size,所以会根据size去打印,因此是可以打印出\0的,但是>>还是跟之前的scanf一样,默认以换行或者是空格作为标识,如果我们想打印出有空格的字符串,是行不通的!!
因此我们想要流插入有空格的字符串,就得用getline
2.7.3 getline