编码
编码 - 值 –
符号映射关系
– 编码表
ascii
编码表 – 表示英文编码表
unicode
– 表示全世界文字编码表
gbk
--中文自己量身定做的编码表
string 类构造函数
string s1;//无参的构造函数
string s2("hello world");//带参的构造
string s3(s2);//拷贝构造
string s4(s2, 2, 6);//从第二个位置开始,往后六个字符初始化
string s5(s2, 2);//从第二个位置开始,取后面所有字符
string s6("hello world", 3);//初始化前三个
string s7(10,'!');//十个!初始化
capacity
int main()
{
string s1("hello world");
//不包含作为结尾标识符的'\0',计算的是有效字符长度
//调用成员函数
cout << s1.size()<< endl;
cout << s1.length() << endl;
cout << s1.capacity() << endl;//容量
s1.clear();//把有效数据清理(容量并不会清理)
cout << s1 << endl;
}
operator[]
string s1("hello world");
//读
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1.operator[](i) << " ";
cout << s1[i] << " ";
//s1[i]相当于函数调用,相当于s1.operator[](i)
//之前arr[i],相当于解引用
}
//写
for (size_t i = 0; i < s1.size(); i++)
{
s1[i] += 1;
}
at
跟上面用法类似
s1.at(i)
,也是可读可写
检查越界的方式不一样:
operator[]
:断言
at
:抛异常
例题
反转字符串链接
class Solution {
public:
bool Isletter(char ch)//判断是否是字母
{
if(ch>='A'&&ch<='Z'||ch>='a'&&ch<='z')
{
return true;
}
else
{
return false;
}
}
string reverseOnlyLetters(string s) {
size_t begin=0;
size_t end = s.size()-1;//s.size()得到字符个数
while(begin < end)//类似于快排:begin指向开头,end指向末尾,如果都是字母就交换,不是字母就往后找字母
{
while(begin < end && !Isletter(s[begin]))//begin指向的不是字母
{
begin++;
}
while(begin < end && !Isletter(s[end]))//end指向的不是字母
{
end--;
}
swap(s[begin],s[end]);//可以直接使用交换函数,因为在c++中,swap()是库函数
begin++;
end--;
}
return s;
}
};
字符串中的第一个唯一字符链接
class Solution {
public:
int firstUniqChar(string s) {
int CountArry[26]={0};//只需判断26个英文小写字母
for(size_t i = 0;i < s.size();i++)
{
CountArry[s[i]-'a']++;//s[i]-'a'相当于字母的ascii码值相减
}
for(int j=0;j<s.size();j++)
{
if(CountArry[s[j]-'a']==1)//返回第一个只出现了一次的位置
{
return j;
}
}
return -1;
}
};
迭代器
string s1("hello world");
//方式1
//下标+[]
for (size_t i = 0; i < s1.size(); i++)
{
s1[i] += 1;
cout << s1[i] << " ";
}
cout << endl;
//方式2:迭代器
string::iterator it = s1.begin();
while (it != s1.end())
{
*it -= 1;//可以修改
cout << *it << " ";
++it;
}
cout << endl;
//方式3:范围for,自动往后迭代,自动判断结束
//auto自动推导类型
for (auto& e : s1)//会依次把s1里的每个字符取出来赋值给e
{
//加了引用才可修改
e -= 1;//修改
cout << e << " ";//读取
}
cout << endl;
string::iterator it = s1.begin();
迭代器想象成:指针一样的类型
反向迭代器
能够倒着遍历
string s1("hello world");
//反向迭代器
string::reverse_iterator rit = s1.rbegin();
auto rit = s1.rbegin();//auto能够自动推导
while (rit != s1.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
迭代器遍历的意义是什么? 迭代器是通用的方式
对于string,无论是正着遍历或者倒着遍历,下标+[]的方式都很好用为什么还要迭代器?
对于string,下标和[]就足够好用,确实可以不用迭代器,但其他容器(数据结构)呢?
所以意义就是让所有的容器都可以使用迭代器这些方法去访问修改
比如list
,map/set
,不支持下标+[]遍历
const 迭代器
void func(const string& s)//const正向迭代器
{
//string::const_iterator it = s.begin();
//const_iterator 跟普通迭代器的区别:解引用之后不可被修改
auto it = s.begin();//auto自动推导类型
//c++11,用cbein代替begin,cend代替end
while (it != s.end())
{
//*it -= 1;*it是常量,不可以修改
cout << *it << " ";
++it;
}
cout << endl;
}
void func1(const string& s)//const反向迭代器
{
string::const_reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
}
所以迭代器分为正向迭代器,反向迭代器,const正向迭代器,const反向迭代器
增删查改
先用一段程序观察string
的增容
void TestPushBack()
{
string s;
size_t sz = s.capacity();
cout << "capacity is:" << sz << endl;
cout << "capacity growing:\n";
for (int i = 0; i < 1000; i++)
{
s += 'c';
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed:" << sz << endl;
}
}
}
我们观察没增容前的容量是15,但它其实是16,因为包含
'\0'
,'\0'
不属于有效数据
vs的string
增容约为1.5倍,每个编译器string
的增容倍数都可能不一样,因为源码不一样下面是
linux
中的变化规律—严格的二倍增长
也可以用s.reserve(1000);//申请至少能存储1000个字符空间
,避免频繁的增容
💟 reserve
开空间,影响容量,原本的数据不变
💟 resize
开空间,对这些空间给一个初始值,进行初始化原本的数据不变
void TestPushBack2()
{
string s1;
s1.reserve(100);
string s2;
s1.resize(100,'x');//初始化了
}
那么能不能减少数据?也是可以的
void TestPushBack3()
{
string s1("hello world");
s1.resize(5);//resize也会减少数据
}
c_str :跟c语言字符串配合
void test_string3()
{
string s("hello world");
cout << s << endl;//调用的string重载的流插入运算符
cout << s.c_str() << endl;//调用的是字符串
string file("test.txt");
FILE* fout = fopen(file.c_str(), "w");//跟c形式的字符串配合
}
find和substr
void test_string3()
{
string file("test.txt");
FILE* fout = fopen(file.c_str(), "w");
//如何取出文件的后缀名?
size_t pos = file.find('.');
if (pos != string::npos)
{
//意味着找到了
string suffix = file.substr(pos, file.size()-pos);
//substr:从pos位置往后取n个字符
cout << suffix << endl;
string suffix2 = file.substr(pos);//也可以利用缺省值npos
//这里的意思是从pos位置开始,默认取到结尾
}
//没有找到返回npos,size_t npos = -1;
//因为是无符号,所以-1是全1,很大的一个数
}
思考file.substr(pos, file.size()-pos)
,size()的位置在最后一个元素的下一个位置
它和pos相减才是元素个数
如果文件名是test.txt.zip
,预期取出的是zip
,如果用上面的代码,取出的是txt.zip
这就用到了rfind
,这样就解决了
void test_string4()
{
string file("test.txt.zip");
size_t pos = file.rfind('.');
if (pos != string::npos)
{
string suffix = file.substr(pos, file.size() - pos);
cout << suffix << endl;
}
}
用find
和substr
处理网址的一些问题
void test_string4()
{
string url("https://gitee.com/XIAOCHENcl/my-code/tree/master");
size_t pos1 = url.find(":");
string protocol = url.substr(0, pos1 - 0);//取出协议名
cout << protocol << endl;
size_t pos2 = url.find('/', pos1 + 3);
//pos1的位置是':',+3就会跳过'//'到达g,从g位置往后找'/'
string domian = url.substr(pos1 + 3,pos2-(pos1+3));
//pos2的位置是com后面的'/',pos1+3在g的位置,想得到'gitee.com'有几个元素
//左闭右开,相减即得到之间的长度
cout << domian << endl;//取出域名
string uri = url.substr(pos2+1);
//pos位置是com后面的'/',pos+1就是X的位置
cout << uri<< endl;//取出路径
}
插入
void test_string5()//插入
{
string s("hello world");
s += "!!!!";
s += '1';
//头插,效率是O(n),尽量少用
s.insert(s.begin(), 'y');//在头插入y
s.insert(0, "test");//在头插入字符串test
//中间位置插入
s.insert(4, "&&&");//在第四个位置插入&&&
cout << s << endl;
}
删除
void test_string6()
{
string s("hello world");
//尽量少用头部和中间的删除,因为要挪动数据,效率低
s.erase(0,1);//从0位置开始删除1个字符
s.erase(s.size() - 1, 1);//尾删
s.erase(3);//第二个参数默认是npos,从第三个位置往后全删了
cout << s << endl;
}
getline
像scanf
或者cin
默认遇到空格就结束了,拿不到带空格的字符串的完整的一行
getline
的原理相当于下面一段代码
string s1;
//不能用cin和scanf因为拿不到'\n',主要还是因为空格
char ch = getchar();//获取一个字符
while (ch != '\n')//手动获取一行
{
s1 += ch;
ch = getchar();//持续获取字符,找到换行
}
例题:求最后一个单词的长度
//char ch1 = cin.get();
string s1;
//不能用cin和scanf因为拿不到'\n'
getline(cin, s1);//获取一行
size_t pos = s1.rfind(' ');
if (pos == string::npos)
{
cout << s1.size() << endl;
}
else
{
cout << s1.size() - pos - 1;
}
return 0;
比较大小
void test_string7()//比较大小
{
string s1("hello world");
string s2("string");
cout << (s1 > s2) << endl;
//很少用
cout << (s1 > "zzz") << endl;//也可以跟字符串比较
cout << (s1 < string("sss")) << endl;
}
stoi (字符串转整型)to_string(转成字符串)
void test_string8()
{
int val = stoi("1234");//字符串转整型
cout << val << endl;
string s = to_string(3.14);//把浮点数转成字符串
}
以后遇到字符串转其他类型,其他类型转字符串都可以用(只支持c++11)
典型例题
字符串相加链接
class Solution {
public:
string addStrings(string num1, string num2) {
int end1=num1.size()-1;
int end2=num2.size()-1;
int flag=0;//表示进位
string num3;
while(end1>=0||end2>=0)//只有长的字符串遍历完了才停下来
{
int x1=0;
int x2=0;
if(end1>=0)//只要没有遍历完
{
x1=num1[end1]-'0';//-'0'表示数字字符转化成int类型数字
end1--;
}
if(end2>=0)
{
x2=num2[end2]-'0';
end2--;
}
int ret=x1+x2+flag;//加上进位的值
if(ret>9)
{
ret-=10;//减10,进1
flag=1;//进位不会大于2,只有1/0两个值
}
else
{
flag=0;//这一步很重要,如果不进位,要置成0,否则下一次x1+x2+flag就会多加1
}
num3+=ret+'0';//int类型数字加'0',表示数字转化为字符数字
}
if(flag==1)
//当两个字符串个数相等时,并且最后一次相加ret>9,出了while作用域要尾插进位1
{
num3+='1';
}
reverse(num3.begin(),num3.end());//逆置
return num3;
}
};
判断是否回文链接
class Solution {
public:
bool IsNumber(char ch)//主要判断是不是空格
{
if(ch>='a'&&ch<='z')
return true;
if(ch>='A'&&ch<='Z')
return true;
if(ch>='0'&&ch<='9')
return true;
return false;
}
bool isPalindrome(string s) {//所谓判断回文,即是判断是否对策
int begin=0;
int end=s.size()-1;
for(int i=0;i<s.size();i++)//把所有的大写字母转成小写,方便之后的判断
{
if(s[i]>='A'&&s[i]<='Z')
{
s[i]+=32;
}
}
while(begin<end)
{
while(begin<end&&!IsNumber(s[begin]))//如果不是有效字符就进来
{
begin++;
}
while(begin<end&&!IsNumber(s[end]))
{
end--;
}
// if(tolower(s[begin])!=tolower(s[end]))//tolower()--转成小写,一对字符相等说明不了什么,就取它的反面
//{
// return false;
// }
if(s[begin]!=s[end])//一对相等并不能判断回文,但是只要一对不相等一定不回文
{
return false;
}
begin++;
end--;
}
return true;
}
};