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的用处就是从字符串中提取出来需要的各种信息.

ssincin基本是完全等价的.

#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;
}

注意: intdouble进行除法的话,首先要将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;
}

从最大的长度开始枚举,最大的后缀长度就是全部字符串中最短的字符串.
那么找最大长度的时候可以在输入的时候就找出最大值.
这种找出最长公共前缀,最长公共后缀的题目都是一样的,都是枚举比较,都是从第二个开始比较,因为都是和第一个进行比较

posted @ 2020-11-03 22:08  RowryCho  阅读(203)  评论(0编辑  收藏  举报