信息学竞赛基础之字符串

此博客原文地址:https://www.cnblogs.com/BobHuang/p/12740540.html
学习了三种基本类型int(整型),double(浮点型)及char(字符型),整型可以帮我们存储整数,浮点型可以帮我们存储实数(小数),char类型可以帮助我们存储26个英文字母和阿拉伯数字等等。
但是如果我遇到了一个比较大的数字呢,比如 TZOJ1420: 手机短号 我们可以尝试用字符串这一类型解答。

一、字符串引入

字符串是以"开头以"结束中间为任意字符的串,如 "Hello world!" 、 "AAAAB" 、 " 0"、"12QWERT"、"\n",我们输出的时候已经用到多次。
字符串是字符数组,字符数组不同于其他的数组只是类型不一样。同样的字符数组也是从下标为0开始的,而不是1。我们可以这样定义数组:
[存储类型] 数组名[常量表达式1]...
字符数组也是这样:

    //字符数组ch1,有ch[0]、ch[1]、ch[2]三个元素
    char ch1[3];    
    //字符数组ch2,有16个元素。ch[0]和ch[1]均有8个元素
    char ch2[2][8];

我们要如何进行赋值呢?我们有3种方法。

(1)用字符初始化数组,例如:

    //定义一个ch1数组,并依次赋值
    char ch1[5]={'a','b','c','d','e'};

初始值表中的每个数据项是一个字符,用字符给数组ch1的各个元素初始化。当初始
值个数少于元素个数时,从首元素开始赋值,剩余元素默认为空字符。当然如果你只是定义了这个字符数组,它和其他数组也是一样,元素的值是不定的,是垃圾。
字符数组中也可以存放若干个字符,也可以来存放字符串。两者的区别是字符串有
结束符('\0'),所以上面的ch不是字符串。
字符串是一维数组,但是一维字符数组不一定是字符串
比如我们可以定义这样的字符串,例如:

    //定义ch2数组,存放了字符串"abcd"
    char ch2[5]={'a','b','c','d','\0'};
    //定义ch3数组,同样存放了字符串"abcd"
    char ch3[5]={'a','b','c','d'};

(2)用字符串初始化数组,例如:

    //使用字符串初始化ch1数组
    char ch1[5]="abcd";

使用这个格式进行字符串初始化,需要注意字符串的长度应小于字符数组的大小,因为最后一个字符存储的是'\0',我们定义字符数组需要+1。同理二维数组可存放若干个字符串,可使用若干个字符串给二维数组初始化。

    //在数组ch2中存放3个字符串,每个字符串的长度不得大于3
    char ch2[3][4]={"abc","012","+_="};

(3)数组元素赋值,字符数组的赋值是给该数组的各个元素赋字符值。例如:

    //定义字符数组ch1,分别进行赋值
    char ch1[3];
    ch1[0]='a';ch1[1]='b';ch1[2]='c';

对多维数组也是如此,当需要一个数组的全部元素值赋给另一数组时,不能直接用数组名=数组名这种直接赋值的方式,要使用字符串拷贝函数来完成。

    char ch1[10]="12a11";
    char ch2[10];
    //将ch1拷贝给ch2,不能用ch2=ch1
    strcpy(ch2,ch1);

字符和字符串的区别

1⃣️两者的定界符不同,字符是‘(单引号)括起来,字符串由"(双引号)括起来。
2⃣️字符只能是单个字符,但是字符串可以是多个字符。
3⃣️可以把一个字符赋值给另一个字符,但是字符串不能直接赋值。
4⃣️字符占1B,字符串占用(l+1)B,l为字符串长度,增加的1B存放字符串结束标志'\0'。字符'0'占内存空间1B,字符串"0"占2B。

三、字符串的输入与输出

字符串可以作为一维数组来处理,一个一个输入,一个一个进行输出。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    char s[100];
    //可以输入一个小于100的数字
    int n;
    while (cin >> n)
    {
        //循环进行输入
        for (int i = 0; i < n; i++)
            cin >> s[i];
        s[n]='\0';
        //循环进行输出
        for (int i = 0; i < n; i++)
            cout << s[i];
        cout << '\n';
    }
    return 0;
}

我们也可以对字符串作为一个整体进行输入和输出。

输入

从键盘读入一个字符数组可以直接用cin语句,和其他类型一样。

    //定义字符数组s,可以存储100个元素
    char s[100];
    //输入字符串s
    cin >> s;

s是首地址,也就是&s[0],代表会从s[0]开始存储。读入完会在最后添加'\0'标志,因此我们输入字符串即可,不需要自己加'\0'。
但是空格和换行并不能存储在里面,比如我们输入了"Alice is smart",但是只能输入"Alice",因为cin读入是以空格和换行作为分割的,我们需要使用getline。getline是这样使用的,里面需要添加参数。

    char ch[100];
    //使用cin里面的getline,需要给首地址和长度参数,给20最多接受19
    cin.getline(ch,20);
    //默认已换行结束,我可以让他以'/'作为结束
    //这个里面可以存储有换行
    cin.getline(ch,100,'/');

如果使用C语言就是如下所示:

    //读取字符串到ch里,以空格或者换行结束
    scanf("%s",ch);
    //读取一行字符串到ch里,以换行结束
    gets(ch);
    //如果你的C++版本较高,gets是不被支持的
    fgets(ch,100,stdin);

输出

输出比较简单,直接cout简单明了

    //直接cout就可以了
    cout<<ch<<"\n";

但是如果一个字符串"Hello\0Bob"会输什么呢,他其实只会输出Hello,因为他认为后面的是垃圾,即"Bob"是垃圾。

    char ch[100]="Hello\0Bob";
    cout<<ch<<"\n";

当然你也可以使用C语言的printf

    printf("%s",ch);
    //还有一个较短小的puts
    puts(ch);

四、题目实践

那么学会这个我们就可以做题目了吗,当然是可以的,我们先来尝试输入输出题。
TZOJ5600: 打招呼,题目很简单,输入一个人的名字,对他打个招呼,但是他的名字可能含有空格。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    //不超过20个字符,但是最后一位要存储'\0',多开一位
    char ch[25];
    //包含空格,使用getline输入
    cin.getline(ch,21);
    //直接按格式输出即可
    cout<<"Hello, "<<ch<<"!\n";
    return 0;
}

TZOJ1420: 手机短号 也是基本的输入输出比较简单。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n;
    //读入n组
    cin >> n;
    while (n--)
    {
        //不超过11个字符
        char ch[12];
        cin>>ch;
        //最后5个字母就是从&ch[11-5]开始输出
        cout << "6" << ch+(11-5) << "\n";
        /*
        //和上面等价
        cout << "6" << &ch[11-5] << "\n";
        //继续等价
        cout<<"6";
        for(int i=11-5;i<11;i++)
        {
            cout<<ch[i];
        }
        cout<<"\n";
        */
    }
    return 0;
}

但是你如果使用了getline会遇到困难,输出的东西很奇怪。这是因为输入组数N之后还有一个换行符没有被正常吃掉,我可以可以cin.get();也可以使用getchar();
** 使用一行读取前一定要看看之前是否会存在换行或空格 **

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n;
    //读入n组
    cin >> n;
    cin.get();
    while (n--)
    {
        //不超过11个字符
        char ch[12];
        cin.getline(ch,12);
        //最后5个字母就是从&ch[11-5]开始输出
        cout << "6" << ch+(11-5) << "\n";
    }
    return 0;
}

我们怎么知道输入的有多长呢,可以用strlen函数。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    char s[100];
    cin>>s;
    //在函数里放入字符数组s作为参数
    int l=strlen(s);
    /*
    //和strlen等价,也可以求出长度
    l=0;
    for(int i=0;s[i]!='\0';i++)
    {
        l++;
    }
    */
    cout<<l<<"\n";
    return 0;
}

字符串遍历

我们需要用字符串的方式进行读取,读取了之后我们可以一个一个进行访问。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    char s[100];
    cin>>s;
    //1.直接按照字符串的末尾是'\0'进行遍历
    //当然'\0'是0,!=0也可以直接舍去
    for(int i=0;s[i]!='\0';i++)
    {
        /*
        你要执行的代码
        */
    }
    //2.按照长度进行遍历
    //在函数里放入字符数组s作为参数,读取s的长度
    int l=strlen(s);
    for(int i=0;i<l;i++)
    {
        /*
        你要执行的代码
        */
    }
    return 0;
}

二维数组读入

TZOJ5104: 说反话 这题可以用二维数组解决,一维数组不带框([]),二维数组需要一个

#include <bits/stdc++.h>
using namespace std;

int main()
{
    //一个字母一个空格,最多80/2=40个单词
    //最少一个单词
    char s[40][81];
    //记录单词个数,初始为0
    int n=0;
    //循环读入所有单词
    while(cin>>s[n])n++;
    //倒着输出所有单词
    for(int i=n-1;i>=0;i--)
    {
        //如果不是第一个,输出空格
        if(i!=n-1)cout<<" ";
        cout<<s[i];
    }
    cout<<"\n";
    return 0;
}

如何比较大小呢,可以用strcmp(这里的比较大小为字典序),如果相等返回为0,小于返回负数,大于返回正数。
字典序顾名思义就是在字典里的先后,如果我从前往后翻一个字典,字典序小的一定会被我先翻到,即"abandon"一定在"abort"前面。
你可以猜猜下列程序的larger的结果,当然你可可以在本地电脑运行一下。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int larger;
    char s1[20]="abandon",s2[20]="abort";
    larger=strcmp(s1,s2);
    cout<<"abandon和abort的大小\t"<<larger<<"\n";
    char s3[20]="abandon",s4[20]="aban";
    larger=strcmp(s3,s4);
    cout<<"abandon和aban的大小\t"<<larger<<"\n";
    char s5[20]="abandon",s6[20]="Abandon";
    larger=strcmp(s5,s6);
    cout<<"abandon和Abandon的大小\t"<<larger<<"\n";
    char s7[20]="hello, Bob",s8[20]="hello, Alice";
    larger=strcmp(s7,s8);
    cout<<"hello, Bob和hello, Alice的大小\t"<<larger<<"\n";
    char s9[20]="hello, Bob",s10[20]="hello, Bob";
    larger=strcmp(s9,s10);
    cout<<"hello, Bob和hello, Bob的大小\t"<<larger<<"\n";
    return 0;
}

你有没有猜对呢?
"abandon"<"abort"是因为前两个字母相等,比较第三位'a'<'o'。
"abandon">"aban"是因为"abandon"比较长,比较第五位'd'>'\0'
"abandon">"Abandon"是因为ASCII值小写比大写的值大,比较第一位'a'>'A'
"hello, Bob">"hello, Alice"有空格依旧可以比较,比较第八位'B'>'A'
"hello, Bob"和"hello, Bob"两者完全相等。

五、字符串string类型

string是c++内部已经帮我们实现好的字符串类模版,他是不定长的,可以随时赋值。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    //直接按照字符串初始化
    string s="12212";
    //可以再次被赋值
    s="1213";
    //可以直接用其他变量赋值
    string str1=s;
    return 0;
}

他也可以进行+运算和比较大小运算,也可以直接获得长度。他的输入和输出和数组一样,只有读取一行的操作略作改变,调用c语言输出需要转换一下。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    string s1="aba";
    string s2="12";
    //把s1和s2拼接起来
    s2=s1+s2;
    //cout<<s2<<"\n";
    //直接用比较运算符进行比较
    if(s1>s2)cout<<"s1>s2\n";
    if(s1<s2)cout<<"s1<s2\n";
    if(s1==s2)cout<<"s1==s2\n";
    //获取长度
    int l=s1.length();
    //读取一行
    getline(cin,s1);
    //调用c_str使用c语言输出
    printf("%s\n",s1.c_str());
    return 0;
}

六、练习

可以练习的题目
1.TZOJ3714: 最简单的题
2.TZOJ5476: 学习之签到
3.TZOJ1166: 大小写转换
4.TZOJ4574: 转换大写
5.TZOJ1095: 保留字母
6.TZOJ5355: 星期几
7.TZOJ1171: 字符逆序
8.TZOJ5014: 说反话
9.TZOJ2878: 美食节之摆摊位(二)
10.TZOJ2961: 代码抄袭
11.TZOJ5611: 计算差异度
12.TZOJ5614: 请输入正确的名字
13.TZOJ5357: 字符串替换
14.TZOJ1055: 简单密码破解
15.TZOJ1379: C语言合法标识符
16.TZOJ1495: C语言实验题――统计子串
17.TZOJ4644: 展开字符串
18.TZOJ3457: 最小新数
19.TZOJ2625: 电话号码
20.TZOJ3212: Pig在哪里
21.TZOJ5133: U的爱慕者
22.TZOJ1484: C语言实验题――字符串排序
23.TZOJ5136: 隧道有多长
24.TZOJ1351: 单词数
25.TZOJ5019: 福尔摩斯的约会
26.TZOJ1426: 剪花布条
27.TZOJ1356: A == B ?

posted @ 2020-04-20 21:10  暴力都不会的蒟蒻  阅读(802)  评论(0编辑  收藏  举报