C++用string进行字符串处理(个人笔记)

前言

作为一直使用C的新手, 对字符串的处理方式一直局限于字符数组和#include<string.h>, 虽然知道C++中的string用来处理字符串更为方便, 但一直没机会系统学习. 最近仍是希望通过 洛谷题单 用string的方式来编写程序, 加强熟练性和对"类"的概念的理解.


基本知识若干

  1. 若要使用C++中的string类, 要包含#include<string>头文件, 要注意的是, 该同文件的作用是可以使用string类, 而#include<cstring>的作用是使用如memset(), strcmp(), strcat(), 之类的函数, 并不代表同一个东西
  2. (应该)需要包含命名空间using namespace std;
  3. string类的基本操作在例题中逐个分析, 这里先记录string的定义方法: string s; 即将s定义为string类, 之后使用cin 或 getline 对其赋值即可
  4. … …

引例一

洛谷P5733 【深基6.例1】自动修正
在这里插入图片描述

代码实现

#include<iostream>
#include<string>
using namespace std;

string a;
int main(void)
{
	cin >> a;
	for(int i=0; i <a.length(); i++)
	{
		a[i] = toupper(a[i]);
	}
	cout << a << endl;
	return 0;
}

分析

  1. 很简单的一道题目, 用来成为使用string解题的第一题刚刚好, 注意所包含的头文件, 命名空间和定义方式即可
  2. cin >> a; 这条语句替换为getline(cin,a); 也可以, getline函数返回的是流参数, 因此可以作为while()循环的条件用来判断输入是否结束, 同时getline可以得到 一行 , 而cin遇到 空格换行符 都会结束
  3. 代码中for循环的部分可以发现, 如果要取string类中单个元素(字符), 可以将其视为字符数组, 用下标的方式即可对其内单个元素(字符)进行访问
  4. 学习到的一个成员函数, a.length(); 返回string中元素个数
  5. 另外学习到两个库函数(?) totower(x)和tolower(x), 包含在头文件#include<iostream>中, 作用分别是将x转变为大写字母或小写字母(不是字母则不变), 但其只能对单个字符产生效果, 而不能传递给其一整个字符串.

引例二

洛谷 P1957 口算练习题

在这里插入图片描述
在这里插入图片描述

代码实现

#include<cstdio>
#include<iostream>
#include<string>
#include<cctype>
using namespace std;

int len(int x)
{
	int lenth = 0;
	if(x==0)	return 1;
	else if(x<0)	lenth ++;
	
	while(x!=0)
	{
		x /= 10;
		lenth ++;
	}
	return lenth;
}

int change(string s)
{
	int ans=0;
	for(int i=0; i<s.size(); i++)	
		ans = ans*10+s[i]-48;
	return ans;
}

int main(void)
{
	int t;
	scanf("%d", &t);
	getchar();
    getchar();
	string s;
	char c;
	
	while(t--)
	{
		getline(cin, s);
		if(s[0]>='a' && s[0]<='c')
		{
			c = s[0];
		}
		int flag = 0;
		string a, b;
		
		for(int i=0; i<s.length(); i++)
		{
			if(s[i]>='0'&&s[i]<='9'&&!flag)
			{
				flag = 1;
				while(s[i]>='0'&&s[i]<='9')
				{
					a.append(1,s[i++]);
				}
			}
			if(s[i]>='0'&&s[i]<='9'&&flag)
			{
				while(s[i]>='0'&&s[i]<='9')
				{
					b.append(1,s[i++]);
				}
				break;
			}
		}
		
		int x, y;
		x = change(a);
		y = change(b);
		if(c=='a')
		{
			int sum = x + y;
			printf("%d+%d=%d\n", x, y, sum);
			printf("%d\n", len(x)+len(y)+len(sum)+2);
		}
		else if(c=='b')
		{
			int sum = x - y;
			printf("%d-%d=%d\n", x, y, sum);
			printf("%d\n", len(x)+len(y)+len(sum)+2);
		}
		else
		{
			int sum = x * y;
			printf("%d*%d=%d\n", x, y, sum);
			printf("%d\n", len(x)+len(y)+len(sum)+2);
		}
		
		
	}
	return 0;
}

分析

收获很多的一道题, 一个一个说

  1. 首先, 对题目本身而言难度并不大, 学习到的第一个点是: 如果要向string类字符串后添加字符(串), 最好不要用下标法. 本题一开始即使用这种方法, 虽然在调试中发现a, b中的某些值确实发生了改变, 但当我想打印a, b时却只有空行.
  2. 推荐的做法是使用+=拼接字符串的方法, 或使用成员函数.append()向队尾添加字符(串).
    其中.append()函数共有三种常见使用方法:
    ①. .append(str)
    ②. .append(str, 起始位置, 截止位置)
    ③. .append(次数, c) — c为单个字符
    本题中使用的就是第三种方法在队尾添加所需字符
  3. C11标准新添加了stoi, atoi这类将字符串转化为数字的函数, 但考虑到之后的比赛可能大部分不会支持C11标准, 因此 自己写 照搬了洛谷一个题解中的字符串转变为int型的方法
    原题解地址
    也就是如下这段代码(可以当板子记下来 )
int change(string s)
{
	int ans=0;
	for(int i=0; i<s.size(); i++)	
		ans = ans*10+s[i]-48;
	return ans;
}
  1. 该函数中使用了成员函数.size(), 目前学习, 查阅到的资料说.size()和.length()没有任何区别
  2. 接下来要说的就与题目本身无关, 而是与评测相关的了.
    首先, 本题最早提交时, 在执行了scanf()后直接执行getline(), 结果显而易见就是将换行符错误的接收了, 因此在scanf()后添加了getchar().
  3. 其次, 即使添加了getchar(), 洛谷的评测机也无法正确接收到输入的内容, 经过查找后了解到洛谷的评测是建立在Linux系统上的, 该系统上会将\r\n视为两个字符, 而Windows系统上会将\r自动忽视. 因此Linux上需要两个getchar(), 才能令所需的数值正确输入.
  4. 最后是代码健壮性(最近数据结构老师总在说这个词)的问题了. 最初主函数中接收a, b两个数字字符串时默认二中中间是以空格分隔的, 但显然洛谷评测(或者其他可能的OJ评测)的时候, 中间的字符不一定是空格.(具体是啥呢我也不知道) 所以不应该刻意判断二者中间的字符是什么来作为分隔的标志, 而是要以二者(两个数字字符串)本身作为判断的依据, 这样便不会出错了

引例三

洛谷P1603 斯诺登的密码
在这里插入图片描述
在这里插入图片描述


代码实现

#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;

string nums[30] ={"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", 
"nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", 
"eighteen", "nineteen", "twenty", "a", "another", "first", "both", "second", "third"};


int main(void)
{
	int t=6;
	int a[6] = {0};
	int k = 0;
	
	while(t--)
	{
		string s;
		cin >> s;
		if(s[s.length()-1]=='.')
		{
			s = s.substr(0, s.length()-1);
		}	
		
		for(int i=0; i<=26; i++)
		{
			if(s==nums[i])
			{
				if(i<=20)
				{
					a[k++] = i;
				}
				else if(i<=23)
				{
					a[k++] = 1;
				}
				else if(i<=25)
				{
					a[k++] = 2;
				}
				else
				{
					a[k++] = 3;
				}
			}
		}
	}
	
	if(k==0)
	{
		printf("0");
	}
	else
	{
		for(int i=0; i<k; i++)
		{
			a[i] = a[i]*a[i]%100;
		}
		
		sort(a,a+k);
		int flag = 0;
		for(int i=0; i<k; i++)
		{
			if(a[i]>0 && !flag)
			{
				flag = 1;
				printf("%d", a[i]);
			}
			else if(a[i]>0 && a[i]<10 && flag)
			{
				printf("0%d", a[i]);
			}
			else if(a[i]>=10 && flag)
			{
				printf("%d", a[i]);
			}
			
		}
	}
	
	
	
	return 0;
}

分析

重要的有两点

  1. 我们可以创建string类型的数组, 其初始化方式如代码所示
  2. 如果我们要删除string中最末尾的字符, 不应该像C一样, 将其改为’\0’ (C++中的字符串并不以\0结尾, 或者说并不全是以\0结尾). 而是应该使用如下三种方法:

①. s.substr() 该成员函数是截取s的子字符串, 若括号中有一个数字, 则是截取从该角标到结尾作为原字符串的新值; 若有两个数字a,b, 则是从a开始截取长度为b的子串.

例:

string s = "12345";
string sub1 = s.substr(3); //sub1 == "45"
string sub2 = s.substr(1,3); //sub2 == "234"

②. s.erase() 该成员函数的一种使用方法是括号中一个变量, 该变量不能只是一个数字, 而是要用s.begin加一个数字, 表示删除这个位置上的字符. 另一种使用方法是括号中两个变量a,b, 表示删除从a开始的b个长度的字符串

例:

string s = "12345";
s.erase(s.begin()+2); //s == "1245"
s.erase(1,2); //s == "15"
  1. 是s.pop_back(), 直接删除字符串最后一个字符, 简单粗暴, 但是突然发现是C11标准下的东西, 所以不做太多解释

引例四

详见 洛谷 P1601 A+B Problem(高精)大数加法_2



引例五

洛谷 P1303 A*B Problem (高精)
在这里插入图片描述

代码实现

#include<cstdio>
#include<string>
#include<iostream>
using namespace std;

string add(string str1, string str2)
{
	string str;
	int len1 = str1.length();
	int len2 = str2.length();
	
	if(len1<len2)
	{
		for(int i=1; i<=len2-len1; i++)
			str1 = "0" + str1;
	}
	else
	{
		for(int i=1; i<=len1-len2; i++)
			str2 = "0" + str2;
	}
	len1 = str1.length();
	int cf=0;
	int temp;
	for(int i=len1-1; i>=0; i--)
	{
		temp = str1[i]-'0'+str2[i]-'0'+cf;
		cf = temp/10;
		temp %= 10;
		str = char(temp+'0') + str;
	}
	if(cf!=0)	str = char(cf+'0') + str;
	return str;
}

string mul(string str1, string str2)
{
	string str;
	if(str1=="0"||str2=="0")
		return "0";
	int len1 = str1.length();
	int len2 = str2.length();
	
	string tempstr;
	for(int i=len2-1; i>=0; i--)
	{
		tempstr = "";
		int temp = str2[i]-'0';
		int t = 0;
		int cf = 0;
		if(temp!=0)
		{
			for(int j=1; j<=len2-1-i; j++)
			{
				tempstr += "0";
			}
			for(int j=len1-1; j>=0; j--)
			{
				t = (temp*(str1[j]-'0')+cf)%10;
				cf = (temp*(str1[j]-'0')+cf)/10;
				tempstr = char(t+'0') + tempstr;
			}
			if(cf!=0)	tempstr = char(cf+'0')+tempstr;
		}
		str = add(str, tempstr);
	}
	str.erase(0,str.find_first_not_of('0'));
	return str;
}

int main(void)
{
	string str1, str2;
	cin >> str1 >> str2;
	cout << mul(str1,str2) << endl;
	return 0;
}

分析

与上面的A+B问题相似, 仍是使用string类代替以往的字符数组实现高精运算, 只是乘法运算中使用到了A+B, 故在此统一列出. 本题自然也是一道"模板题", 这类高精度的算法确实不需要背下来, 但是本着提升编程思维和写代码能力的目的, 还是准备打印出来仔细学习其中的思路.
另外本题中学习到了string类中的find()成员函数, 用法如下:

#include<iostream>
#include<string>
using namespace std;

int main(void)
{
	string s = "abcabcabc";
	
	cout << s.find('a') << endl;	//0
	cout << s.find('a', 1) << endl;	//3
	cout << s.rfind('a') << endl;	//6
	cout << s.find('d') << endl;	//18446744073709551615
	cout << (s.find('d') == -1) << endl;	//1
	cout << endl;
	cout << s.find("abc") << endl;	//0
	cout << s.find("abc", 1) << endl;	//3
	cout << endl;
	cout << s.find_first_of('c') << endl;	//2
	cout << s.find_first_of('c', 3) << endl;	//5
	cout << s.find_last_of('c', 3) << endl;	//2
	cout << s.find_first_of("jiefuc", 3) << endl;	//5
	cout << endl;
	cout << s.find_first_not_of('c') << endl;	//0
	cout << s.find_first_not_of('c', 2) << endl;	//3
	cout << s.find_last_not_of('c', 2) << endl;	//1
	cout << s.find_first_not_of("isfiab") << endl;	//2
	
	return 0;
}


引例六

洛谷 P1098 字符串的展开

在这里插入图片描述
在这里插入图片描述

代码实现

#include<cstdio>
#include<string>
#include<iostream>
#include<cmath>
using namespace std;

int main(void)
{
	int p1, p2, p3;
	string s;
	string::iterator it;

	cin >> p1 >> p2 >> p3 >> s;

	for (it = s.begin(); it < s.end(); it++)
	{
		if (*it == '-')
		{
			if (it - s.begin() == 0 || it - s.end() + 1 == 0)
			{
				continue;
			}
			string t;
			string::iterator itt;

			char begin = *(it - 1);
			char end = *(it + 1);
			if (begin == '-' || end == '-')
			{
				continue;
			}
			else
			{
				begin++;
				end--;
			}

			if (begin - end == 1)
			{
				s.erase(it);
				it--;
				continue;
			}
			else if (begin > end || fabs(begin - end) > 26)
			{
				continue;
			}

			while (begin <= end)
			{
				t.append(p2, begin);
				begin++;
			}
			if (p1 == 3)
			{
				for (itt = t.begin(); itt < t.end(); itt++)
				{
					*itt = '*';
				}
			}
			else
			{
				if (p1 == 2 && begin >= 'a')
				{

					for (itt = t.begin(); itt < t.end(); itt++)
					{
						*itt += 'A' - 'a';
					}
				}
			}

			s.erase(it);
			if (p3 == 1)
			{
				string::iterator temp = s.insert(string::const_iterator(it), t.begin(), t.end());
				it = temp;
			}
			else
			{
				string tt(t.rbegin(), t.rend());
				auto temp = s.insert(string::const_iterator(it), tt.begin(), tt.end());
				it = temp;
			}
			it += t.size();
		}
	}

	cout << s << endl;


	return 0;
}

分析

又是一道题内题外收获都很多的题目, 虽说这道题显然有一些更简单的做法, 但毕竟是为了学新知识嘛, 在出现无数让人脱发的bug后, 请教了lxd大佬, 也通过自己的一系列查找资料, 终于是理解了各种bug的原因.

  1. 首先让我们隆重欢迎迭代器小哥哥! 迭代器似乎是一个类似指针的东西, 但又和指针不尽相同, 之后在学习其他容器时可以加深学习理解. 定义方法为string::iterator (变量名); 这个东西可以通过解引用符* 来获取其当前指向位置的字符. 同时可以与s.begin(), s.end(), 之类的函数(迭代器)进行运算
  2. 当迭代器(下面我们用it来代表迭代器)出现在需要使用增删函数的字符串中, (可能)会发生类似于指针错位的bug. 例如当插入多个元素时, 当前所剩内存空间不足以插入这么多个元素, 就会导致系统自动将这部分数据转移到另一个可以容纳得下的位置, 而it并不会随之移动, 这就导致了迭代器失效. 虽说是可能发生, 但我们需要假设这个bug每次运行都会发生. 解决方法的话, 我们需要知道C11之前的insert()函数返回值并非迭代器类型(这就导致我的devcpp遇到这个问题后很难解决), 于是首先我们转战vscode, 在每条insert()语句前, 使用auto类型定义一个迭代器变量, 让他接受insert()的返回值, 再把这个值返还给it. 这就实现了使得迭代器不会失效.
  3. 如第二点中所说的auto类型, 这是C++11标准以上(大概)特有的自动类型变量, 必须进行初始化, 并会自动接受赋值号右侧的内容, 转化成同一类型. 很是方便.
  4. 语句string tt(t.rbegin(), t.rend()); 实现了将字符串t逆序交给字符串tt的功能. 其中的rbegin()和rend()为反向迭代器.
  5. ps. 以后遇到迭代器相关问题就用vscode吧
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值