✨思维导图附上
看不清楚戳这里【String思维导图】
图片因为上传上限是5MB,所以不能直接传。图片无法更新但网页持续更新。
这一节我们将详细学习string字符串这个类,string类是早于STL标准库的,后面把string归属到STL,支持标准库中很多操作。触类旁通,通过学习string,也便于我们更好的学习STL。活不多说开始~
string 字符串
我们学习关于STL的学习通过【cplusplus旧网站】来学习。
1.string类简介
我就充当翻译官吧,不对的地方欢迎指正:
- String 类是一个字符序列的对象。
- 标准字符串类提供了类似于标准字节容器的接口,但增加了专门用于操作单字节字符字符串的特性。
- 字符串类是基本字符串类模板(basic_string class template)的一个实例化,它使用char(即字节)作为其字符类型,并使用其默认的char_traits和分配器类型。typedef basic_string string (如果大家对基本的string模板感兴趣,可以通过网站查看)
我自己对String的看法是:把c的char数组封装起来的感觉,然后提供很多方法,char形的顺序表哈哈(以空字符 \0 结尾的字符数组)。 当然这个看法肯定是浅显的。
2.string 的属性
string的属性,在库中可能是比较复杂的,它的字符存储位置不相同,比如小于某个数字的字节,就储存在buffer这个字符数组,大于有存在其他地方,这个比较杂。我这里就简单的把它理解为三个属性(这只是我的简单理解,并非库中真实)。
//基本属性: char* _str; //存储字符串的动态数组吧 size_t _size; //记录字符长度(不包括\0) size_t _capacity; //记录数组的容量(最大空间)
3.成员常量
npos这个的值代表无符号整形的最大值:4294967295 。一般在成员函数之间使用,直接使用即可。
4.string 的默认成员函数
构造函数:
代码示例:
方式1:default
1 | //default 默认无参构造: 2 | string s1; 3 | //错误: string s1(); 4 | //原因:编译器无法识别这是在声明函数还是对象实例化,会报错!
方式2:copy
1 | //拷贝构造:把s1拷贝给s2(深拷贝) 2 | string s2(s1);
方式3:substring
1 | //拷贝s1,从下标为0的字符开始,拷贝10个长度的字符串 2 | string s3(s1, 0, 10);
方式4:c-string
1 | //拷贝str1字符数组,直到遇到\0: 2 | char str[10] = { 'h','e','l','l','o' }; 3 | string s4(str1);
方式5:sequence
1 | //拷贝str1字符数组,前5个字符 2 | string s5(str1, 5);
方式6:fill
1 | //拷贝5个单字符'*': 2 | string s6(5, '*');
方式7:range
1 | //迭代器拷贝 其实相当于指针拷贝 //begin()指向字符数组的第一个元素,end()指向字符数组最后一个元素的下一个位置\0 2 | string s7(s6.begin(), s6.end());
方式8:pointer range
1 | //字符数组 地址拷贝 str1 = "Hello World"; 2 | string s8(str1, str1 + sizeof(str1));
析构函数
析构函数释放char* _str;指针申请的堆上空间。
赋值运算符重载
代码示例:
方式1:string
//把s1赋值给s2 string s1; string s2; s2 = s1; // 赋值 string类的权限缩小到const string&
方式2:c-string
//赋值一个字符串“hello” string s1; s1 = "hello" // 赋值 char* 权限缩小到 const char*
方式3:character
//赋值一个字符* string s1; s1 = '*'; // 赋值 char 权限缩小到 const char
构造赋值小疑问?
提问:string s = "zhang san" ; 这个是什么?
答案:
构造函数+拷贝构造: 这里"zhang san" 是个c-string字符数组,要把它转换到string。就是"zhang san"会调用string的构造sting("zhang san"),然后会再拷贝构造。为什么不是赋值?因为这里是在构造一个对象并初始化,不是赋值。
string s = "zhang san" ; 我自己感觉这样写或者string s("zhang san");这两种写法对我来说最舒服。
5.容量操作
size、length
这两个成员函数是冗余的,两个的效果是一样的,我猜测设计size是为了让string更好的融入STL标准库。
代码示例:
函数: size()、length(); 作用:返回string对象的成员数量(_size大小); 返回值:size_t string s("zhang san"); cout << s.size() << endl; //9 cout << s.length() << endl; //9
max_size
代码示例:
函数: max_size(); 作用:string对象在当前编译器,操作系统系能开辟的最大空间; 返回值:size_t string s; cout << s.max_size() << endl; //2147483647
不同编译器,操作系统结果可能不同,所以这个函数作用不大。
capacity
代码示例:
函数: capacity(); 作用:当前对象的容量,返回_capacity的大小; 返回值:size_t string s("zhang san"); cout << s << endl; //zhang san cout << s.size() << endl; //9 cout << s.capacity() << endl; //31
resize
在VS,Windows系统下, 开辟空间大于n的空间,具体要看平台,把size扩到或缩到n;
若n大于当前capacity,扩容,追加n-size个指定的字符(没指定也会把size扩到n,用\0沾满)
若n小于capacity,改变size到n,并size位置赋值一个\0;缩小过程,第二个参数没用。
代码示例:
string s("zhang san"); //扩大 s.resize(20,'*'); cout << s << endl; //zhang san*********** cout << s.size() << endl; //20 cout << s.capacity() << endl; //31 //缩小 s.resize(5); cout << s << endl; //zhang cout << s.size() << endl; //5 cout << s.capacity() << endl; //31
reserve
扩容: 传递一个无符号整形n ,改变对象的capacity到n不等(平台不同,扩容不同),开辟一个大小为n的空间,实际开辟n+1不等个。若当前容量大于n,可能会缩小,看平台。
总结一下 : 若你的容量小于n,它会扩容到至少n+1;若n小于当前容量,可能处理,可能不处理。
代码示例:
string s("zhang san"); //缩小 s.reserve(5); cout << s << endl; //zhang san cout << s.size() << endl; //9 cout << s.capacity() << endl; //15 //扩大 s.reserve(100); cout << s << endl; //zhang san cout << s.size() << endl; //9 cout << s.capacity() << endl; //111
clear
清除对象中的数据
代码示例:
string s("zhang san"); s.clear(); cout << s << endl; //无数据
empty
代码示例:
函数: empty(); 作用:判断当前对象是否为空,为空返回True,反之; 返回值:bool string s("zhang san"); cout << s.empty() << endl; //0
shrink_to_fit (C++11)
代码示例:
函数: shink_to_fit(); 作用:把容量缩小到和size一样大小; 返回值:void string s("zhang san"); s.reserve(1000); cout << s.size() << endl; //9 cout << s.capacity() << endl; //1007 s.shrink_to_fit(); cout << s.size() << endl; //9 cout << s.capacity() << endl; //15
5.元素访问
orerator[]
真爽好吧!下标访问好。[]运算符重载,两个构成函数重载。
代码示例:
函数: orerator[](); 作用:给它一个初始下标位置,它会返回对应值的引用; 返回值:char& string s("zhang san"); cout << s[3] << endl ; //n
at
代码示例:
函数: at(); 作用:给它一个初始下标位置,它会返回对应值的引用; 返回值:char& string s("zhang san"); cout << s.at(3) << endl ; //n
back (C++11)
代码示例:
函数: back(); 作用:返回最后一个元素的引用,注意确保对象不为空(会报错); 返回值:char& string s("zhang san"); cout << s.back() << endl ; //n
front (C++11)
代码示例:
函数: front(); 作用:返回第一个元素的引用,注意确保对象不为空(会报错); 返回值:char& string s("zhang san"); cout << s.front() << endl ; //z
7.修改
append
追加操作,在对象最后追加内容
代码示例:
string s1("zhang san"); string s2("li si"); const char* str = "wang wu" ;
方式1:string
//在s1后插入s2 s1.append(s2); //zhang sanli si
方式2:substring
//在s1后插入s2,从s2下标为2的位置开始的3个字符 s1.append(s2,2,3); //zhang san si
方式3:c-string
//在s1后插入"li si" s1.append("li si"); //zhang sanli si
方式4:buffer
//在s1后插入str数组中前三个字符 s1.append(str,3); //zhang sanwan
方式5:fill
//在s1后插入5个# s1.append(5,'#'); //zhang san#####
方式6:range
//在s1后插入s2,从开始到结尾的字符.[) s1.append(s2.begin(),s2.end()); //zhang sanli si
方式7:pointer range
//在s1后插入数组范围开始到第三个位置.[) s1.append(str,str+3); //zhang sanwan
push_back
代码示例:
函数: push_back(); 作用:在最后一个位置后插入一个字符; 返回值:void string s("zhang san"); s.push_back('c') cout << s << endl ; //zhang sanc
operator +=
前面说了那么多,最爽的登场!
代码示例:
string s1("zhang san"); string s2("li si"); s1 += s2; //zhang sanli si s1 += "wang wu"; //zhang sanwang wu s1 += '#'; //zhang san#
assign
覆盖,assign不是追加,而是覆盖。这里我们已经熟悉了这些参数套路,我就不再次一一举例了。
代码示例:
string s1("zhang san"); string s2("li si"); s1.assign(s2); //li si
insert
具体就是在给定pos位置前,插入一个string、substring、c-string、buffer、fill、character、range
代码示例:
string s("zhang san"); //在下标为3的元素,前面插入"#####"(c-string类型) s.insert(3,"#####"); //zha#####ng san
erase
代码示例:
方法1:sequence
//删除s下标为6开始的2个的字符 这里给了缺省值哈 string s("zhang san"); s.erase(6,2) //zhang n
方法2:character
//删除第一个元素。删除iterator处的元素,可以用指针. string s("zhang san"); s.erase(s.begin()) //hang san
方法3:range
//删除所有元素。范围 string s("zhang san"); s.erase(s.begin(),s.end()) //
replace
swap
交换两个对象,注意是地址也是要交换的,不是只交换值
代码示例:
//交换s1和s2 string s1("zhang san"); string s2("li si"); s1.swap(s2); cout<<s1<<endl; //li si cout<<s2<<endl; //zhang san
pop_back (C++11)
删除最后一个位置的元素
代码示例:
//尾删 string s1("zhang san"); s1.pop_back(); //zhang sa
8.字符串操作
c_str
代码示例:
//c_str 返回字符串首地址 string s1("zhang san"); cout<<s1.c_str<<endl; //zhang san cout<<*s1.c_str()<<endl; //z
data
这个也是多余的,和c_str一样的作用。
get_allocator
copy
str.copy(char* a, int len , int begin_pos ) 拷贝str对象中从begin_pos开始,长度为len的字符串,到a数组,copy不会自动改\0,所以需要自己修改。返回值为len。若a数组中原本有数据,会从头开始覆盖。
代码示例:
//拷贝s1中从0位置开始的5个字符到a数组,并返回拷贝长度 string s1("zhang san"); char a[100]={'n','h'}; int len = s1.copy(a,5,0); a[len]='\0'; cout<<a<<endl;
find
在对象中查找是否有与它(str指针内容)相同的,第一个参数可以string,c-string,buffer,character.默认从首位置开始.找到返回找到字符串的的第一个字符的位置,没找到就返回npos。
代码示例:
//s1从首位置开始,找"zhang"字符串 string s1("zhang san"); s1.find("zhang",0);
rfind
与find相反,反着找,默认位置是最后,返回值相同.
find_first_of
给定第一个参数string,c-string,buffer,character,相当于把前面的字符串全部拆分成字符,str如果出现了第一个参数的任意字符,则返回首次出现的位置。默认从第一个数据开始找。往后找。
//s1从下标为0上位置开始,正向找's','q','w'三个字符,返回最后一次位置的下标 string s1("zhang san"); s1.find_first_of("sqw",0); //6
find_last_of
注意:寻找方向是从pos位置,向前找
代码示例:
//s1从下标为0上位置开始,反向找's','q','w'三个字符,返回最后一次位置的下标 string s1("zhang san"); s1.find_last_of("sqw",0); //npos; s1.find_last_of("sqw",6); //6
find_first_not_of
从第一个位置开始,向后查找,出现不存在在string中组成的字符时,就返回元素位置;若不存在则npos
代码示例:
//在s1中查找不是‘z’,'h','n','g','s'的元素 string s1("zhang san"); s1.find_first_not_of("zhngs",0); //2
rfind_last_not_of
从第一个位置开始,向后查找,出现不存在在string中组成的字符时,就返回元素位置;若不存在则npos
代码示例:
//在s1中查找不是‘z’,'h','n','g','s'的元素 string s1("zhang san"); s1.find_last_not_of("zhngs"); //7
substr
获得子字符串,string substr(begin_pos=0,len=npos) 获取从pos位置长度为len的字符串,并返回。
代码示例:
//从s1的首位置后的5个字符作为字符串返回 string s1("zhang san"); string s2 = s1.substr(0,5); //zhang
compare
9. Iterator
关于迭代器,是需要单独一篇博客来稍微进行分析的,这里只能暂且介绍相关函数。在string类中,我们把typedef char* iterator;,所以这里我们简单的把iterator理解为char*指针。
begin
begin()返回字符串的首字符的地址。
end
end()返回字符串的最后一个元素的下一个位置的地址。也就是\0那个位置。
rbegin
rbegin指向的位置和end()是对称的,也就是最后一个元素的下一个地址。\0的位置。但我们实践发现,解引用后打印的是最后一个元素。那是因为解引用操作的运算符重载,访问的是前一个元素。这是反向迭代器的一个小重点。
代码示例:
string s1("zhang san"); cout << *s1.rbegin();
rend
rend()它指向的位置也和begin对称,指向字符串首地址。
crbegin (C++11)
crend (C++11)
10.string的遍历方法
代码示例:
方法1:常规遍历
for (int i = 0; i < s1.size(); i++) { cout << s1[i] << ' '; } cout << endl;
方法2:迭代器遍历
string::iterator it = s1.begin(); while(it!=s1.end()) { cout << *it << ' '; it++; } cout << endl;
方法3:语法糖(迭代器遍历)
for (auto e : s1) { cout << e << ' '; } cout << endl;
语法糖底层也是迭代器遍历,迭代器它是容器之间通用的,也就是说在库里面,迭代器用法都是一样的,但是其底层实现还是存在很多差异的。
总结
这篇博客介绍了string类的所有成员函数,希望对大家有所帮助。博文中部分为进行介绍的函数,后续会补齐。