了解string

本文详细介绍了C++ string类的构造函数,包括无参、带参、拷贝和部分初始化。探讨了容量计算与清理、operator[]与at的用法,以及迭代器(正向、反向、const)的应用实例,涉及典型问题如字符串反转、唯一字符查找和数据增删改查。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

编码

编码 - 值 –符号映射关系 – 编码表
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;
	}
}

findsubstr处理网址的一些问题

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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值