为什么要学习string类
c语言中的字符串
c语言中,字符串是以\0结尾的一些字符的集合,为了操作方便,c标准库提供了一些str系列的函数,但是这些库函数与字符串是分离开的,不符合OOP的思想,而且底层空间需要自己管理,稍不留神就越界访问
标准库的string类
1,字符串是表示字符序列的类
2.标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于用于操作单字节字符串的设计特性
3.string类是使用char作为它的字符类型,使用它的默认char_traits和分配类型,更多信息,参考basic_string
4.string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数
5.注意,这个类独立于所使用的编码来处理字节,如果用来处理多字节或边长字符(UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将它按照字节(而不是实际编码的字节)来操作
总结:
1.string是表示字符串的字符串类
2.该类的接口与常规容器的接口基本相同,再添加了一些专门操作string的常规操作
3.string再底层实际是: basic_string模板类的别名,typedef basic_string<char,char_traits,akkocator> string;
4.不能操作多字节或者变长字符的序列
string有四个版本,分别基于不同的编码,默认utf-8,根据字符第一个二进制是1还是0区分用ascii还是2字节一读,还有utf-16和utf-32,wstring
string类的常用接口
1.string类对象的构造
constructor函数名称 | 功能说明 |
---|---|
string() 重点 | 构造空的类对象,空字符串 |
string(const char*s) 重点 | c-string构造string类对象 |
string(size_t, char c) | n个字符c |
string(const string& s) 重点 | 拷贝构造函数 |
void Teststring()
{string s1; // 构造空的string类对象s1string s2("hello bit"); // 用C格式字符串构造string类对象s2string s3(s2); // 拷贝构造s3
}
2. 容量操作
函数名称 | 功能说明 |
---|---|
size 重点 | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty 重点 | 检测字符串是否为空串 |
clear 重点 | 清空有效字符 |
reserve 重点 | 为字符串预留空间 |
resize 重点 | 将有效字符的个数改成n个,多出的空间用字符c填充 |
void test2()
{string s1("hello");cout << s1.size() << "\n";cout << s1.length() << "\n";cout << s1.capacity() << "\n";/*s1.clear();cout << s1.size() << "\n";cout << s1.length() << "\n";cout << s1.capacity() << "\n";*//*s1.resize(10);cout << s1 << endl;*//*s1.resize(10, 'd');cout << s1 << endl;*//*s1.resize(3);cout << s1.size() << "\n";cout << s1.length() << "\n";cout << s1.capacity() << "\n";*/s1.reserve(20);cout << s1.size() << "\n";cout << s1.length() << "\n";cout << s1.capacity() << "\n";cout << s1 << 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, char c)用字符c填充空间.注意: resize增多,可能会改变容量大小,减少时,空间大小不变
4.reserve(size_t res_art=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,不会改变容量大小
3. 访问及遍历操作
函数名称 | 功能说明 |
---|---|
operator[] 重点 | 返回pos位置的字符,const string类对象调用 |
begin+end | begin去第一个字符的迭代器,end取最后一个字符下一个位置的迭代器 |
rbegin+rend | rbegin取最后一个字符的迭代器,rend取第一个字符 |
范围for | c++11支持更简洁的for遍历方式 |
void test3()
{string s1("hello");//遍历1 下标访问for (size_t i = 0; i < s1.size(); i++){cout << s1[i];}cout << endl;//遍历2 迭代器string::iterator it = s1.begin();//string::const_iterator cit = s1.begin(); //const类型for (; it != s1.end(); it++){cout << *it;}cout << endl;//string::reverse_iterator rit = s1.rbegin();//string::const_reverse_iterator crit = s1.begin();auto it1 = s1.rbegin(); //自动推导迭代器类型while (it1 != s1.rend()){cout << *it1;it1++;}cout << endl;//遍历3 范围forfor (auto c:s1){cout << c ;}cout << endl;}
下标[]访问越界会报错,at访问则会抛异常
4. 修改操作
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后面插入字符 |
append | 在字符串后追加一个字符串 |
operator+= 重点 | 在字符串后追加字符串 |
c str 重点 | 返回c格式字符串,遇\0结束 |
find+npos 重点 | 从字符串pos位置开始往后找字符c,返回该字符的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符位置 |
substr | 在str中从pos位置开始,截取n个字符返回 |
void test4()
{string s1("hello");s1.push_back('c');s1.append("world");s1 += "ni";printf("%s\n",s1.c_str());int pos = s1.find('o');if (pos != string::npos){cout << "找到"<<pos << endl;}int pos2 = s1.rfind('o');if (pos2 != string::npos){cout << "找到" << pos << endl;}string s2 = s1.substr(pos, 2);cout << s2 << endl;
}
注意:
1.在string尾部追加字符时,pushback/append/+=三种实现方式差不多,一般+=用的比较多,不仅可以连接单个字符,还可以是字符串
2.string操作,如果能够预估到村多少字符,可以先通过reserve把空间预留好
3.insert往中间插入,但不推荐,效率不高
4.删除earse同中间插入,给一个位置,不给数量会将后面的全部删除
5.replace替换字符
6.npos的值是-1,size_t类型会变为int最大值
替换字符串指定字符
// string s1("hello world i love you");
// string newStr;
// size_t num = 0;
// for (auto ch : s1)
// {
// if (ch == ' ')
// ++num;
// }
// // 提前开空间,避免repalce时扩容
// newStr.reserve(s1.size() + 2 * num);
//
// for (auto ch : s1)
// {
// if (ch != ' ')
// newStr += ch;
// else
// newStr += "%20";
// }
两个swap功能一样,而针对类的交换函数更高效,只需要改变指针
转换c串用于调用c函数的接口,需要传入c字符串,例如打开文件fopen
substr取字符串指定内容,也可以查找任一字符
void test5()
{//string s1("hello");//size_t pos = s1.find('e');//if (pos != string::npos)//{// string suffix = s1.substr(pos, s1.size() - pos);//第二个参数可以缺省// cout << suffix;//}//取网址中间内容域名,xxxx.comstring url = "https://legacy.cplusplus.com/reference/string/string/";size_t pos = url.find("://");if (pos != string::npos){size_t finish = url.find('/',pos + 3);string suffix = url.substr(pos + 3, finish - (pos + 3));//第二个参数可以缺省cout << suffix;}url.find_first_of("abcd"); //找字符串中包含任一字符url.find_last_of("abcd"); //倒着找
}
非成员函数
函数名称 | 功能说明 |
---|---|
operator+ | 尽量少用,传值返回导致深拷贝效率低 |
operator>> 重点 | 输入运算符重载 |
operator<< 重点 | 输出运算符重载 |
getline 重点 | 获取一行字符串 |
relational operators | 大小比较 |
6. msvc和g++string结构
下述结构式32位平台验证,32位平台指针占4字节
vs的string
共占28字节,内部先有一个联合体,定义string字符串的存储空间
- 当字符串长度小于16时,使用内部固定的字符数组存放
- 当字符串长度大于等于16时,从堆上开辟空间
- 扩容时除第一次外都是1.5被扩容
union _Bxty
{ // storage for small buffer or pointer to larger onevalue_type _Buf[_BUF_SIZE];pointer _Ptr;char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
大多数情况下字符串的长度小于16,string对象创建时需要的空间是固定空间,不用从堆申请,效率高
还有一个size_t保存字符串长度,一个size_t字段保存从对数开辟空间的总量
还有一个指针做其他事情
所以总共28个字节
*g++结构
string通过写时拷贝实现,string对象共4个字节,内部包含一个指针,指向一块堆空间,包含如下字段:
空间不够2倍扩容
- 空间总大小
- 字符串有效长度
- 引用计数
struct _Rep_base
{size_type _M_length;size_type _M_capacity;_Atomic_word _M_refcount;
};
- 指向堆空间的指针,保存指针大小
练习
仅仅反转字母
https://leetcode.cn/problems/reverse-only-letters/submissions/
思路:
用两个下标,一个从头,一个从尾,找到是字母的就交换。交换完记得移动下标,不然还是换这两个
class Solution {
public:string reverseOnlyLetters(string s) {int begin = 0;int end = s.size() - 1;while (begin < end){while (begin < end && !isalpha(s[begin])){begin++;}while (begin < end && !isalpha(s[end])){end--;}swap(s[begin], s[end]);//交换后继续移动一步,防止再次交换begin++;end--;}return s;}
};
找出第一个只出现一次的字母
https://www.nowcoder.com/practice/e896d0f82f1246a3aa7b232ce38029d4?tpId=37&&tqId=21282&rp=1&ru=/activity/oj&qru=/ta/huawei/question-ranking
思路:
可以利用计数排序的思路,记录字符串内26个字母的数量,然后根据字符串每个字母查看计数,如果是1打印这个字符,如果不是就打印-1
#include <iostream>
using namespace std;int main() {string s;cin >> s;int ary[26] = {0};//计数,对应26个字母for (auto ch : s ) {ary[ch - 'a']++;}for (int i = 0; i < s.size(); i++) {if (ary[s[i] - 'a'] == 1) {cout << s[i];return 0;}}cout << -1;
}
字符串最后一个单词长度
https://www.nowcoder.com/practice/8c949ea5f36f422594b306a2300315da?tpId=37&&tqId=21224&rp=5&ru=/activity/oj&qru=/ta/huawei/question-ranking
思路:
记录长度,下标从最后一个字母开始,如果不是空格就循环,统计完长度
也可以利用字符串倒着查找函数,找到第一个空格,用字符串长度减去找到的位置再减1就是最后一个单词的长度
#include <iostream>
using namespace std;int main() {string s;getline(cin, s);//记录下标和长度int length = 0;int sign = s.size() - 1;while (sign >=0 && s[sign] != ' '){length++;sign--;}cout<<length;
}
#include <iostream>
using namespace std;int main() {string s;getline(cin, s);//查找int pos = s.rfind(' ');if(pos != string::npos){cout<<s.size() - pos -1<<endl;}else {cout<<s.size()<<endl;}}
字符串相加
https://leetcode.cn/problems/add-strings/description/
思路:
两个字符串从最后一位开始,逐个相加,用一个新字符串保存结果。相加得到的数%10后得到存储的数,尾插到新字符串里。/10后得到进位,如果有进位,加到下一个位计算。哪个字符串空了,返回0来相加
注意相加时需要转换为整数,不是字符串ascii相加。最后有进位处理一次。将结果逆置
class Solution {
public:string addStrings(string num1, string num2) {int length1 = num1.size() - 1;int length2 = num2.size() - 1;string s;//预先开辟空间,节省消耗s.reserve( num1.size() > num2.size()? num1.size() + 1:num2.size() + 1);int carry = 0;while (length1 >= 0 || length2 >= 0) {//取需要相加的数int val1 = length1 >=0 ? num1[length1] - '0' : 0;int val2 = length2 >=0 ? num2[length2] - '0' : 0;//相加,包括进位int num = val1 +val2 +carry;char ret = num % 10 + '0';s += ret;carry = num / 10;length1--;length2--;}if (carry){s += '1';}reverse(s.begin(), s.end());cout << s;return s;}
};