C,C++语法基础 | 字符串 | 05
字符串 | 05
字符串与整数的联系 -- ASCII码
每个常用的字符都对应一个0~127
的数字,二者之间可以相互转化.
#include<iostream>
using namespace std;
int main(){
char c = 'a';
cout << (int)c << endl; // 97
int a = 66;
cout << (char)a << endl; // B
return 0;
}
常用的ASCII
码值:
'0'
: 48'0'
`'9'`是4857'A'
: 65'a'
: 97
字符串可以参与运算,运算时会将其当做整数.
#include<iostream>
using namespace std;
int main(){
int a = 'B' - 'A'; // 66 - 65
int b = 'A' * 'B'; // 65 * 66
char c = 'A' + 2; // 65 + 2
cout << a << endl;
cout << b << endl;
cout << c << endl;
return 0;
}
字符数组的定义
字符串就是字符数组加上结尾符'\0'
可以使用字符串来初始化字符数组,但此时要注意,每个字符串结尾会暗含一个\0
字符,因此字符数组的长度至少要比字符串的长度多1.
// 前两种是正常的数组初始化
char a1[] = {'C','+','+'}; // 列表初始化,没有空字符
char a2[] = {'C','+','+','\0'}; // 列表初始化,含有显示的空字符
// 后两种使用双引号进行初始化
char a3[] = "C++"; // 自动添加表示字符串结尾的空字符
char a4[6] = "Daniel"; // 错误: 没有空间可存放空字符!
字符串的输入输出
char a[100]; // 定义一个字符数组来存储字符串
scanf("%s",a); // 这里字符串的格式为 %s, 还有就是数组是不用&取地址符号的,数组名本身就是一个地址
scanf("%s",a+1); // 从a[1]开始存储
cin >> s + 2; // 从a[2]开始存储
// 输入多个字符串,使用空格隔开就可以读入
char s1[100],s2[100];
scanf("%s %s",s1,s2); // 读入 abc def
但是需要注意的是,普通读入字符串的方法,遇到空格或者回车就会停止读入.
下面有几种方式可以读入一整行字符串,可以读入包含空格的字符
// 字符数组整行读入
gets(s); // 这个函数已经被淘汰了,现在在使用的话就会出现CE
fgets(s,最多读入多少字符,stdin); // 一般中间数字保证这一行字符足够读入即可
cin.getline(s,最多读入多少字符);
// 字符串整行读入
getline(cin,s);
输出字符串
// 下面3种都是等价的字符串输出
puts(s); // 直接输出字符串并换行
printf("%s\n",s);
cout << s << endl;
字符数组的常用操作
首先需要引入头文件#include<string.h>
或者#include<cstring>
strlen(s); //求字符串的长度,注意长度就是明面上的长度,不包含最后一个`\0`
strcpy(a,b); // 把字符串b复制给字符串a,是后面复制给前面的
strcmp(a,b); // 按照字典序比较字符串a和字符串b,依次输出 -1,0,1
还有就是字符串的循环,一般是不求长度的,利用结尾的\0
// 求字符串长度
len = strlen(s);
for(int i=0;i<len;i++);
// 利用结尾的'\0'
for(int i=0;s[i];i++);
标准库类型 string的定义
比赛的时候我们很少自己定义字符数组来存储字符串,80%的情况都是定义string
来处理.
string
是一个可变长的字符串,比字符数组更加好用. 可以很方便的执行字符串的拼接等操作.
首先需要引入#include<string>
string s1; // 默认初始化,s1是一个空字符串
string s2=s1; // s2是s1的副本
string s3="hiya"; // s3是该字符串字面值的副本
string s3(10,'c'); // s4的内容和是 cccccccccc
需要注意的是,string
使用scanf/printf
都比较麻烦,所以建议使用string
就直接使用cin/cout
string的empty()和size()
这是string
两个常用的方法.
需要注意, string.size()
的时间复杂度是O(1)
的, 因为类中专门有一个变量来进行长度的存储,而strlen()
的时间复杂度是O(n)
string s = "abc";
cout << s.empty() << endl; // 0
cout << s.size() << endl; // 3
string的比较操作
string
支持>,<,=,>=,<=,==,!=
等所有比较操作,按照字典序进行比较.
string的加法
c++
的string
可以和Java
一样,非常方便的进行字符串的拼接操作,一样都是先把其他字符转换为string
类型
string s1 = "hello, ",s2="world\n";
string s3 = s1 + s2; // s3 的内容是 hello, world\n
// 当然还可以加上字符类型
s3 += '!'; // s3 的内容是 hello, world\n!
需要主要+=
的使用场景,就是必须左右两边至少需要保证有一个string
类型,否则就会出错
string s = "abc" + '!'; // 这是错误的! 因为字符串是不能相加的,可以进行相加的是string
处理string对象中的字符
可以将string
对象当成字符数组来处理
string s = "hello, world";
for(int i=0;i<s.size();i++)
或者使用基于范围的for
语句
string s = "hello world";
for(char c:s)cout << c << endl; // 这个很类似增强型的for
for(char &c:s)c='a'; // 直接进行取地址
// 当然不可以不具体指明,使用auto
for(auto &c:s)c='a';
stringstream
引入头文件#inlcude<sstream>
stringstream
的用处就是从字符串中提取出来需要的各种信息.
ssin
和cin
基本是完全等价的.
#include<bits/stdc++.h>
using namespace std;
int main(){
string s;
getline(cin,s);
stringstream ssin(s); // 把字符串初始化为stringstream
int a,b;
string str;
double c;
// 可以使用 ssin 实现 cin 的功能
ssin >> a >> b >> str >> c;
cout << a << endl << b << endl << str << endl << c << endl;
return 0;
}
string常用函数
string.back()
返回最后一个字符
string.pop_back()
删除最后一个字符
string.substr(起始下标,长度)
截取字符串子串
string.find(子串)
查找判断字符或者子串是否在字符串中,如果存在,则返回索引,负责返回一个不确定数s.find(xx)>=0 && s.find(xx)<s.size()
注意: s.size()
最好是赋值给一个len
,因为比s.size()
小的数减去s.size()
不能得到一个正确答案.
时间统计函数
引入#include<ctime>
int start_time = clock();
for(int i=0;i<10000;i++); // 制造一个循环
cout << (clock() - start_time) / 1000 << endl;
习题五
字符串长度
string.size()
, strlen(s)
#include<iostream>
using namespace std;
int main(){
string s;
getline(cin,s);
cout << s.size() << endl;
return 0;
}
字符串中的数字个数
#include<cstdio>
#include<iostream>
#include<string>
using namespace std;
int main(){
string s;
getline(cin,s);
int cnt = 0;
for(auto c:s){
if(c >= '0' && c <= '9'){
cnt++;
}
}
cout << cnt << endl;
return 0;
}
注意: 这是读入一行有空格的字符串. 字符可以直接和字符进行比较,就是做int
进行处理.
循环相克令
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
int get_num(string s){
if(s=="Hunter")return 0;
if(s=="Gun")return 1;
if(s=="Bear")return 2;
}
int main(){
int n;
cin >> n;
while(n--){ // 循环的写法
string s1,s2;
cin >> s1 >> s2;
int x=get_num(s1),y=get_num(s2);
if((x+1)%3==y){
cout << "Player1" << endl;
}else if(x == y){
cout << "Tie" << endl;
}else{
cout << "Player2" << endl;
}
}
return 0;
}
循环的写法:
int n;
cin >> n;
while(n--){
...
}
如果直接写的话,两个人都要有3种可能,那么最坏的情况就要写9个判断.
可以这么处理,用3进制处理.
猎人: 0, 枪: 1 : 熊: 2
(x+1)%3 == y 赢
(x+1)%3 < y 输
x == y 平
字符串加空格
#include<iostream>
#include<string>
#include<cstdio>
using namespace std;
int main(){
string s;
getline(cin,s);
for(auto c:s)cout << c << " ";
return 0;
}
替换字符
#include<bits/stdc++.h>
using namespace std;
int main(){
string s,a,b;
getline(cin,s);
cin >> a >> b;
stringstream ssin(s); // 把字符串初始化为stringstream
string s2;
while(ssin >> s2){
if(s2 == a){
cout << b << " ";
}else{
cout << s2 << " ";
}
}
return 0;
}
字符串插入
#include<iostream>
#include<string>
#include<cstdio>
using namespace std;
int main(){
string s1,s2;
while(cin >> s1 >> s2){
int max = -1;
for(auto c:s1)max = max > c ? max : c;
for(int i=0;i<s1.size();i++){
if(s1[i] == (char)max){
cout << (s1.substr(0,i+1) + s2 + s1.substr(i+1)) << endl;
break;
}
}
}
return 0;
}
有若干行数据,读入多组数据
string a,b;
while(cin >> a >> b){
...
}
还有就是使用了字符串截取函数string.substr(起始坐标,截取长度)
只出现一次的字符
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
// 10W最好就开成全局,方法内栈内存会限制为1M,会爆
// 全局变量为堆内存,没有设限
char s[100000+10]; // 数组都是多开10个,保证安全
int main(){
cin >> s;
int cnt[26+10] = {0};
int len=strlen(s);
for(int i=0;i<len;i++){
cnt[s[i]-'a']++;
}
for(int i=0;i<len;i++){
if(cnt[s[i]-'a']==1){
cout << s[i] << endl;
return 0;
}
}
cout << "no" << endl;
return 0;
}
使用一个数组来记录每个字符出现的次数,然后再遍历一次确定第一次只出现一次的字符.
字符串匹配
#include<iostream>
#include<string>
using namespace std;
int main(){
double k;
string s1,s2;
cin >> k >> s1 >> s2;
int cnt = 0;
for(int i=0;i<s1.size();i++){
if(s1[i] == s2[i])cnt++;
}
cout << ((double)cnt / s1.size() >= k ? "yes" : "no") << endl;
return 0;
}
注意: int
和double
进行除法的话,首先要将int
转为double
忽略大小写比较字符串大小
#include<iostream>
#include<cstring>
using namespace std;
int main(){
string s1,s2;
getline(cin,s1);
getline(cin,s2);
// 都转化为小写字母处理
for(int i=0;s1[i];i++){
if(s1[i]>='A' && s1[i]<='Z')s1[i] += 32;
}
for(int i=0;s2[i];i++){
if(s2[i]>='A' && s2[i]<='Z')s2[i] += 32;
}
int res = s1.compare(s2);
if(res == 0)cout << "=";
else if(res < 0)cout << "<";
else cout << ">";
return 0;
}
去掉多余空格
#include<bits/stdc++.h>
using namespace std;
int main(){
string s;
while(cin >> s){
cout << s << " ";
}
return 0;
}
这种空格使用cin
读入,会自动过滤的,所以只要读入然后输出即可.
如果是自己手动探路的,就会使用到另外一类的双指针
#include<bits/stdc++.h>
using namespace std;
int main(){
string s1,s2;
getline(cin,s1);
for(int i=0;i<s1.size();i++){
if(s1[i]!=' '){
s2 += s1[i];
}else{
s2 += ' '; // 加上第一个空格
int j = i;
while(j < s1.size() && s1[j] == ' ')j++;
i = j - 1; // 抵消后面的i++
}
}
cout << s2 << endl;
return 0;
}
探路算法(经常用)
int j = i;
while(j<s1.size() && j==' ')j++;
i = j-1;
信息加密
#include<iostream>
#include<cstdio>
#include<string>
#include<string.h>
using namespace std;
int main(){
string s;
getline(cin,s); // 注意,这是输入一行有空格的字符串
for(auto &c:s){
if((c>='a'&&c<'z') || (c>='A')&&(c<'Z'))c = (char)(c+1);
else if(c=='z')c='a';
else if(c=='Z')c='A';
}
cout << s << endl;
return 0;
}
这里需要注意的是输入的string
是有空格的.
输出字符串
#include<bits/stdc++.h>
using namespace std;
int main(){
string s1,s2;
getline(cin,s1);
for(int i=1;i<s1.size();i++){
s2 += s1[i] + s1[i-1]; // 字符直接相加,如果是char类型会自动转为char
}
s2 += (s1[0] + s1[s1.size()-1]);
cout << s2 << endl;
return 0;
}
其实有问题的还是循环的问题,这种循环其实只要取模就可以了
像下面这种循环,可以不需要特判,直接取模即可
for(int i=1;i<s1.size();i++){
s2 += s1[i] + s1[i-1]; // 字符直接相加,如果是char类型会自动转为char
}
s2 += (s1[0] + s1[s1.size()-1]);
for(int i=0;i<s1.size();i++){
s2 += s1[i] + s1[(i+1)%s1.size()];
}
单词替换
#include<bits/stdc++.h>
using namespace std;
int main(){
string s,a,b;
getline(cin,s);
cin >> a >> b;
stringstream ssin(s); // 把字符串初始化为stringstream
string s2;
while(ssin >> s2){
if(s2 == a){
cout << b << " ";
}else{
cout << s2 << " ";
}
}
return 0;
}
字符串中最长的连续出现的字符
这个我原来想的还是使用数组来存次数,然后找到最大的. 但是这种连续子串天然可以使用双指针算法.
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
while(n--){
string s;
cin >> s;
// 存放最大次数和最大次数的字符
int cnt = 0;
char c;
for(int i=0;i<s.size();i++){
int j=i;
while(j < s.size() && s[i] == s[j])j++;
if(cnt < j - i)cnt = j - i,c = s[i];
i = j -1;
}
cout << c << " " << cnt << endl;
}
return 0;
}
双指针算法的练习 -- "扣单词"
比如"I'm a teacher, I from China!" 把单词一个一个扣出来
#include<iostream>
#include<string>
using namespace std;
int main(){
string s;
getline(cin,s);
for(int i=0;i<s.size();i++){
int j=i;
while(j < s.size() && s[j]!=' ')j++;
cout << s.substr(i,j-i) << endl;
i = j;
}
return 0;
}
最长单词
#include<bits/stdc++.h>
using namespace std;
int main(){
string res,s;
while(cin >> s){
if(s.back() == '.')s.pop_back();
if(res.size() < s.size())res = s; // 注意这里等于是不会替换的,也就保证了第一个
}
cout << res << endl;
return 0;
}
倒排单词
#include<iostream>
#include<string>
using namespace std;
int main(){
string ss[100];
int n=0;
while(cin >> ss[n])n++;
for(int i=n-1;i>=0;i--){
cout << ss[i] << " ";
}
return 0;
}
如何是按照单词为单位的话,那就需要定义字符串数组.
// 循环读入字符串数组
string ss[100];
int n=0;
while(cin >> ss[n])n++;
字符串移位包含问题
#include<iostream>
using namespace std;
int main(){
string s1,s2;
cin >> s1 >> s2;
// 这里保证了第一个字符串是最长的
// swap() 可以交换所有类型的变量
if(s1.size() < s2.size())swap(s1,s2);
for(int i=0;i<s1.size();i++){
s1 = s1.substr(1) + s1[0];
int f = s1.find(s2);
if(f>=0 && f<s1.size()){ // 这里使用了查找函数
cout << "true";
return 0;
}
}
cout << "false";
return 0;
}
首先一个是循环串的移位,y总也是使用的字符串拼接的方式.
for(int i=0;i<s1.size();i++){ // 循环移位
s1 = s1.substr(1) + s1[0];
还有就是保证第一个字符串是最大的,可以使用swap()
,swap()
任何类型都能够进行交换
string s1,s2;
if(s1.size() < s2.size())swap(s1,s2);
字符串乘方
#include<iostream>
using namespace std;
int main(){
string s1;
while(cin>>s1,s1!="."){
int len = s1.size();
for(int n=len;n;n--){ // 这里的n是重复的次数
if(len % n ==0){ // // 循环节的长度一定是字符串长度的约数
int m = len / n;
string s2 = s1.substr(0,m); // 这个就是循环节,都是从头开始的
string s3;
for(int i=0;i<n;i++)s3 += s2;
if(s1 == s3){
cout << n << endl;
break;
}
}
}
}
}
首先一个是读入问题,最后一个以.
结束,逗号表达式从左到右求值,最后的值作为返回值
string s;
while(cin >> s,s!='.'){
...
}
字符串的最大跨距
#include<iostream>
using namespace std;
int main(){
string s,s1,s2;
char c;
while(scanf("%c",&c),c!=',')s+=c;
while(scanf("%c",&c),c!=',')s1+=c;
cin >> s2;
// 找到最左边和最右边的位置
int l = s.find(s1),r = s.rfind(s2);
int s1_len = s1.size();
if(l>=0 && l<s.size() && r>=0 && r<s.size() && r - l - s1_len>=0){
cout << r - l - s1_len ;
}else{
cout << "-1";
}
return 0;
}
首先是读入的问题,如果是以逗号分隔的读入,那么就可以使用单个字符一个一个读入.
string s,s1,s2;
char c;
while(scanf("%c",&c),c!=',')s+=c;
while(scanf("%c",&c),c!=',')s1+=c;
cin >> s2;
这个题目需要使用到string.find()
和string.rfind()
,需要注意的是,其实都是从左开始找的.
还有就是string.size()
的问题,如果是需要进行减法运算的话,建议赋值给一个len
进行存储.
最长公共字符串后缀
#include<iostream>
using namespace std;
const int N = 210;
string ss[N];
int main(){
int n;
while(cin >> n,n){
int len = 0x7ffffff;
for(int i=0;i<n;i++){
cin >> ss[i];
if(len > ss[i].size())len = ss[i].size();
}
while(len){// 从len开始枚举,到0直接输出即可
bool success = true;
for(int i=1;i<n;i++){ // 从第二个字符串开始枚举,因为都是跟第一个字符串进行比较
bool is_same = true; // 首先假设是相同的
for(int j=1;j<=len;j++){
if(ss[0][ss[0].size()-j]!=ss[i][ss[i].size()-j]){
is_same = false;
break;
}
}
if(!is_same){
success = false;
break;
}
}
if(success)break;
len--;
}
cout << ss[0].substr(ss[0].size() - len) << endl;
}
return 0;
}
从最大的长度开始枚举,最大的后缀长度就是全部字符串中最短的字符串.
那么找最大长度的时候可以在输入的时候就找出最大值.
这种找出最长公共前缀,最长公共后缀的题目都是一样的,都是枚举比较,都是从第二个开始比较,因为都是和第一个进行比较