ACM/ICPC 语法学习笔记—字符串

定义

字符串常量是由一对双括号引起的字符序列。例如"C language"、"student"、"123"等都是合法的字符串常量。

字符串常量和字符常量的区别:

  1. 字符常量由单引号括起来,字符串常量由双括号括起来。 'C' "China"

  2. 字符常量只能是单个字符,字符串常量则可以含零个或多个字符。

  3. 可以把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋予一个字符变量。在C语言中没有相应的字符串变量,但是可以用一个字符数组来存放一个字符串常量。Str[6] = "China";

  4. 字符常量占一个字节的内存空间。字符串不像其他数据类型具有固定的长度,不用字符串是不等长的,因此,字符串的存储不光需要存储其起始位置,还应该记载其结束位置。字符串常量占的内存字节数等于字符串中字符数加1,增加的一个字节中存放字符'\0'(ASCII码为0),这是字符串结束的标志。如字符串常量"China"中实际上有六个字符,分别是'C''h''i''n''a''\0'

    注:字符常量'A'和字符串常量"A"虽然都只有一个字符,但是在内存中的情况不同。

    如以下代码程序:

    #include <bits/stdc++.h>
    using namespace std;
    int main() {
        char a = 'C';
        string b = "C";
        printf("字符'c'的size:%d\n", sizeof(a));
        printf("字符串%\"C\"的size:%d\n", sizeof(b));
    }
    

    代码运行的结果是:

    字符'c'的size:1
    字符串%"C"的size:32
    

字符数组与字符串

在C语言中没有专门的字符串变量,通常用一个字符数组来存放一个字符串。(注:在C++中对于字符串有类string可以通过引用#include <string>来直接使用string新建字符串变量。具体内容在本文最后进行讲解)字符串是以\0作为串的结束符。因此把一个字符串存入一个数组时结束符'\0'也需要存入数组,并以此作为该字符串结束的标志。有了\0标志之后,就不必再用字符数组的长度来判断字符串的长度了。

C语言允许用字符串的方式对字符数组做初始化赋值,但是与用字符初始化是有区别的。

例如

char ch1[] = {'C','h','i','n','a'};
//数组ch1的大小为5,包含1个大写字母,4个小写字母
char ch2[] = "China";
char ch3[] = {"China"};
//以上两句话是等效的,数组ch2、ch3的大小均为6,包含1个大写字母,4个小写字母,和一个\0

对于字符数组可以有两种使用方式:

  • 当做单个字符看待。(ch[0]ch[1]等)

  • 当做一个字符串看待,使用数组名访问整个数组,直接操作字符串中的个所有字符。可以使用printfscanf函数一次性输入输出一个字符数组中的字符串,而不必使用循环语句逐个地输入输出每个字符。

    #include <bits/stdc++.h>
    using namespace std;
    int main() {
        char ch[6] = "China";
        //按照字符逐个输出
        for (int i = 0; i < 6; i ++ ) {
            printf("%c", ch[i]);
        }
        printf("\n");
        //按照字符串整体输出
        printf("%s", ch);
        return 0;
    }
    

    特别的,在定义字符串的时候,可以省略掉字符数组的大小,如以下两种情况在编译过程中是完全等价的:

    ch[] = "China";
    ch[6] = "China";
    

两种方式输入字符串:

同字符串的输出一样,输入一个字符串有两种方式。

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch1[6], ch2[6];
    //按照字符数组逐个读入
    for (int i = 0; i <= 5; i ++ ) {
        scanf("%c", &ch1[i]);
    }
    //按照字符串整体读入
    scanf("%s", ch2);

    printf("Ans1:%s\n", ch1);
    printf("Ans2:%s\n", ch2);
}

当输入为China China时,输出为:

Ans1:China 
Ans2:China

例题1:[洛谷 P5733 【深基6.例1】自动修正](P5733 【深基6.例1】自动修正 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

例题2:洛谷 P1914 小书童——凯撒密码

例题3:洛谷 P1125 笨小猴

C语言中字符串函数

C语言提供了丰富的字符串处理函数,大致可分为字符串的输入输出、合并、修改、比较、转换、复制及搜索几类。使用这些函数可大大减轻变成的负担。用于输入输出的字符串函数,在食用前应包含头文件stdio.h,使用其他字符串函数则应包含头文件string.h

字符串输出函数puts

  • 规格说明:int put(const char * pc);

  • 功能描述:把字符指针指定的字符串输出到标准输出流中,空字符不输出,但是输出换行符及空格等分隔符。

  • 函数参数:pc可以是指针变量,也可以是字符数组名。

  • 函数返回值:若出现些错误,则返回EOF(-1);否则返回非负数。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch[] = {"China"};
    puts(ch);
    return 0;
}

运行结果为:China

注:puts()函数完全可以由printf()函数取代。当需要按一定格式输出时,通常使用printf()函数。

字符串读入函数gets

  • 规格说明:char * gets(char * pc);

  • 功能描述:从标准输入流读字符到字符指针指向的字符数组中,直至遇到换行符或输入文档结束符,并且丢弃换行符或结束符,然后在字符数组中添加一个空字符。

  • 函数参数:pc必须是字符数组名或指向字符数组的指针变量。不可以使用指针变量指向字符串常量,因为常量空间不允许改变。

  • 函数返回值:若成功,返回pc1;若直接遇见换行符或结束符,pc所指数组不变,返回空指针,若读入长度超出数组长度,数组改变,会出现内存写问题(超出数组长度)。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch[11];
    printf("input string:\n");
    gets(ch);
    printf("output string:\n");
    puts(ch);
    return 0;
}

运行结果为:

input string:
Love China
output string:
Love China

将字符串读入字符数组还可以使用gets()函数,但是由于存在字符数组越界的风险,已经不再建议使用,新的C++11标准更是删除了这个函数,而输出一个字符串还可以使用puts(),同时会自动输出换行,这倒是还能使用。

测字符串长度函数strlen

  • 规格说明:int strlen(const char * pc);

  • 功能描述:计算pc所指向的字符串的长度。

  • 函数参数:pc可以是指针变量,也可以是字符数组名。

  • 函数返回值:返回pc指向数组中储存字符串的长度,即第一个\0之前的有效字符个数。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch1[] = {'I',' ','l','o','v','e','\0','y','o','u','\0'};
    char ch2[] = {'I',' ','l','o','v','e',' ','y','o','u','\0'};
    int len1 = strlen(ch1);
    int len2 = strlen(ch2);
    printf("%d\n", len1);
    printf("%d", len2);
    return 0;
}

运行结果为:

6
10

字符串连接函数strcat

  • 规格说明:char * strcat(char * pc1, const char * pc2);

  • 功能描述:把字符指针pc1所指向的字符数组的末尾添加pc2所指向的字符串(包括结尾的空字符在内)的副本,即用pc2所指向的字符串的第一个字符覆盖pc1所指向的字符串末尾的空字符

  • 函数参数:pc1必须是字符数组名或只想字符数组的指针变量,不可以使用指针变量指向字符串常量。pc2可以是指针变量,也可以是字符数组名。

  • 函数返回值:返回pc1,若连接后的pc1长度超出pc1指向数组长度,会出现内存写问题(超出数组长度)。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch[20] = {"I love"};
    char ch2[7] = " China";
    strcat(ch, ch2);
    printf("%s", ch);
    return 0;
}

运行结果为:I love China

字符串复制函数strcpy

  • 规格说明:char * strcpy(char * pc1, const char *pc2);

  • 功能描述:把字符指针pc2所指向的字符串(包括结尾的空字符在内)复制到pc1所指向的字符数组中。

  • 函数参数:pc1必须是字符数组名或只想字符数组的指针变量,不可以使用指针变量指向字符串常量。pc2可以是指针变量,也可以是字符数组名。

  • 函数返回值:返回pc1,若复制后的pc1长度超出pc1指向数组的长度,会出现内存写问题(超出数组长度)。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch1[20] = "I love ";
    char ch2[20] = "China";
    strcpy(ch1, ch2);
    printf("%s", ch1);
    return 0;
}

运行结果为:China

字符串比较函数strcmp

  • 规格说明:int strcmp(cons char *pc1, const char *pc2);
  • 功能描述:按照ASCII码顺序比较字符指针pc1pc2所指向的串的大小。
  • 函数参数:pc1pc2可以是指针变量,也可以是字符数组名
  • 函数返回值:
    • pc1所指向的字符串 == pc2指向的字符串,返回值0;
    • pc1所指向的字符串 > pc2`指向的字符串,返回值大于0;
    • pc1所指向的字符串 < pc2指向的字符串,返回值小于0;

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char * pc1 = "abc";
    char * pc2 = "ABC";
    char * pc3 = "abc";
    char * pc4 = "abd";
    char * pc5 = "ab";
    printf("cmp pc1 ans pc2:%d\n", strcmp(pc1, pc2));
    printf("cmp pc1 ans pc3:%d\n", strcmp(pc1, pc3));
    printf("cmp pc1 ans pc4:%d\n", strcmp(pc1, pc4));
    printf("cmp pc1 ans pc5:%d\n", strcmp(pc1, pc5));
    return 0;
}

运行结果为:

cmp pc1 ans pc2:1
cmp pc1 ans pc3:0
cmp pc1 ans pc4:-1
cmp pc1 ans pc5:1

字符串查找函数strstr

  • 规格说明:char * strstr(const char * pc1, const char * pc2);
  • 功能描述:查找字符指针pc1所指向的字符串中第一次出现pc2所指向字符串(不包括空字符)的地址。
  • 函数参数:pc1pc2可以是指针变量,也可以是字符数组名。
  • 函数返回值:返回字符指针pc1所指向字符串中第一次出现pc2所指向字符串(包括空字符)的地址。若不出现返回空指针。

例如:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch[20] = "I love China.";
    char *pc = "China";
    char *tmp = strstr(ch, pc);
    int pos = strlen(ch) + 1 - strlen(tmp);
    printf("%d", pos);
    return 0;
}

运行结果为:8

C++ string类型字符串

使用C语言风格的字符数组有诸多不便,比如不能弹性变化长度,不能直接赋值或者复制,也有数组越界的风险。在C++中提供了一些更好的工具——标准模板库(Standard Template Library,STL),将很多有用的功能进行了封装,开箱即用,而不需要另外重新开发这些功能。STL包括各类容器(如队列、栈等)、算法(如排序)和其他的一些功能。现在,将要使用string数据类型来处理字符串问题。

像前面所说的,C语言中没有字符串这种数据类型,而在C++中我们可以直接使用string来定义一个字符串。

例题4:洛谷 P5015 标题统计

像上面这道例题,可以不使用字符数组,而是使用一种新的“数据类型”——string。一个string类型的变量可以用来储存一个字符串,并且可以激昂这个字符串当做一个整体进行处理——可以对string进行赋值、拼接、裁切等,而字符数组毕竟是个数组,做到这些相对麻烦。

输入时使用cin语句,不断读入字符串。当发现读入文件读完后,会直接中断while循环。

这里的s可以理解为一个“加强版”的字符数组,可以使用s.length()来直接查询字符串s的长度,也可以和字符数组一样使用s[0]来查询这个字符串最开头的字符是什么。

而且string类型的字符串可以直接拿来赋值、拼接操作,比如s = s + s就是将两个字符串s拼接在一起,其结果赋值回s的意思。

例题5:洛谷 P5734 【深基6.例6】文字处理软件

这道题用到了string类型的常用处理方法:

  1. string s定义一个名字为s的字符串变量。
  2. s+= str或者s.append(str)在字符串s后面拼接字符串str。
  3. s<str比较字符串s的字典序是否在str的字典序之前。
  4. s.size()s.length()得到字符串s的长度。
  5. s.substr(pos, len)截取字符串s,从第pos个位置开始len个字符,并返回这个字符串。
  6. s.insert(pos, len)在字符串s的第pos个字符之前,插入字符串str,并返回这个字符串。
  7. s.find(str, [pos])在字符串s中从第pos个字符开始寻找str,并返回位置,如果找不到返回-1。pos可以省略,默认为0。

例题6:洛谷 P1308 统计单词数

代码中使用了getline()函数,可以讲完整的一行的输入数据读入到字符串中,无论这一行当中是否有空格。使用方法是getline(cin, 字符串名称)

其他类别函数

fgets()函数

fgets()函数用来读入一行字符串,并存入字符数组中,空格也一起存下了。由于gets()函数因为存在可能溢出的风险而不是用。fgets(s, sizeof(s), stdin)这条语句中制定了字符数组的最大读入数量,因此是安全的。

sscanf()函数和sprintf()函数

sscanf()函数,可以从已经存储下来的字符串中读入信息。sprintf()可以将信息输出到字符串当中。比如sscanf(s, "%d", a);就可以从s字符串中读入一个整数a。同理sprintf(s, %d, a);可以将一个int类型的数a输出到字符串s中。

例题7:洛谷 P1957 口算练习题

习题

注:例题和加粗习题为必做题

洛谷 P1765 手机

洛谷 P3741 honoka的键盘

洛谷 P1321 单词覆盖还原

洛谷 P1553 数字反转(升级版)

洛谷 P1603 斯诺登的密码

洛谷 P1200 你的飞碟在这儿Your Ride Is Here

洛谷 P1597 语句解析

洛谷 P1598 垂直柱状图 【尽量做】

例题答案

例题1:[洛谷 P5733 【深基6.例1】自动修正](P5733 【深基6.例1】自动修正 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

读入字符串,对每一个字符进行分析,如果当前字符ch[i]>='a'&&ch[i]<='z'证明这是一个小写字母,把他变成大写字母即可。

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch[110];
    scanf("%s", ch);
    for (int i = 1; ch[i] != '\0'; i ++ ) {
        if (ch[i] >= 'a' && ch[i] <= 'z') {
            ch[i] += 'A' - 'a';
        }
    }
    printf("%s", ch);
    return 0;
}

例题2:洛谷 P1914 小书童——凯撒密码

读入字符串,把其中每一个字符加上\(n\),特别注意的,在当前字符加上\(n\)之后,可能会超过'a'~'z'的范围,需要特殊处理。

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n;
    char ch[60];
    scanf("%d", &n);
    scanf("%s", ch);
    for (int i = 0 ; ch[i] != '\0'; i ++ ) {
        ch[i] = (ch[i] - 'a' + n) % 26 + 'a';
    }
    for (int i = 0; ch[i] != '\0'; i ++ ) {
        printf("%c", ch[i]);
    }
    return 0;
}

例题3:洛谷 P1125 笨小猴

读入字符串,统计该字符串中出现最多的字母的次数和出现最少的字母的次数,进行相减,判断结果是否为质数,如果是质数则输出Lucky Word与这个质数,不是则输出No Answer0

#include <bits/stdc++.h>
using namespace std;
#define inf 2147483647
int main() {
    char ch[110];
    int maxx = 0, minn = inf, cnt[26] = {0};
    scanf("%s", ch);
    for (int i = 0; ch[i] != '\0'; i ++ ) {
        int now = ch[i] - 'a';//获取当前字母是26个中的第几个(0~25)
        cnt[now] ++ ;
    }
    for (int i = 0; i < 26; i ++ ) {
        if (cnt[i] == 0) {
            continue;
            //判断26个字母中第i(0~25)个是否出现,不出现则不统计
        }
        if (cnt[i] >= maxx) {
            maxx = cnt[i];
        }
        if (cnt[i] <= minn) {
            minn = cnt[i];
        }
        //找到这个单词中出现最多字母出现的次数和出现最少字母出现的次数
    }
    //下面为常规判断质数的方法
    if (maxx - minn == 0 || maxx - minn == 1) {
        printf("No Answer\n0");
        return 0;
    }
    for (int i = 2; i * i <= maxx - minn; i ++ ) {
        if ((maxx - minn) % i == 0) {
            printf("No Answer\n0");
            return 0;
        }
    }
    printf("Lucky Word\n%d", maxx - minn);
    return 0;
}

例题4:洛谷 P5015 标题统计

这里使用了string类型的变量,因为读入字符串会忽略前面的空格,并且读到分隔符(空格或者换行)时停止,所以可以一直读入字符串,每次读入一个字符串,把其长度加入答案中即可。

#include <bits/stdc++.h>
using namespace std;
int main() {
    string s;
    int cnt = 0;
    while (cin >> s) {
        cnt += s.length();
    }
    printf("%d", cnt);
    return 0;
}

例题5:洛谷 P5734 【深基6.例6】文字处理软件

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n;
    scanf("%d", &n);
    string s;
    cin >> s;
    for (int i = 1; i <= n; i ++ ) {
        int opt, a, b;
        string tmp;
        scanf("%d", &opt);
        if (opt == 1) {
            cin >> tmp;
            s += tmp;
            cout << s << endl;
        }
        if (opt == 2) {
            scanf("%d%d", &a, &b);
            s = s.substr(a, b);
            cout << s << endl;
        }
        if (opt == 3) {
            scanf("%d", &a);
            cin >> tmp;
            s.insert(a, tmp);
            cout << s << endl;
        }
        if (opt == 4) {
            cin >> tmp;
            int ans = s.find(tmp);
            cout << ans << endl;
        }
    }
    return 0;
}

例题6:洛谷 P1308 统计单词数

首先将读入的单词和句子中的每个字母都换成小写字母(大写字母也可)。然后我们发现,如果要查找的单词是to时,若句子为I like to walk to Toshiba Company.的时候,将单词Toshiba中的to也参与了统计。那么我们这里利用一个小处理,把要查找的单词前后各加一个空格,输入的句子最前边和最后边也加一个空格,这样 ‘ ’+"to"+' '去查找,即可统计单个的单词数量。

#include <bits/stdc++.h>
using namespace std;
int main() {
    string s, SS;
    getline(cin, s);
    getline(cin,SS);
    for (int i = 0; i < SS.length(); i ++ ) {
        if (SS[i] >= 'A' && SS[i] <= 'Z') {
            SS[i] += 32;
        }
    }
    for (int i = 0; i < s.length(); i ++ ) {
        if (s[i] >= 'A' && s[i] <= 'Z') {
            s[i] += 32;
        }
    }
    s = ' ' + s + ' ';
    SS = ' ' + SS + ' ';
    if (SS.find(s) == -1) {
        cout << -1 << endl;
        return 0;
    }
    int nxt = SS.find(s);
    int pos = nxt;
    int cnt = 0;
    while (nxt != -1) {
        cnt ++ ;
        nxt = SS.find(s, nxt + 1);
    }
    cout << cnt << ' ' << pos << endl;
    return 0;
}

例题7:洛谷 P1957 口算练习题

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n, a, b, c;
    char opt, s[20], ans[20];
    scanf("%d\n", &n);
    while(n -- ) {
        fgets(s, sizeof(s), stdin);
        if (s[0] == 'a' || s[0] == 'b' || s[0] == 'c') {
            opt = s[0];
            s[0] = ' ';
        }
        sscanf(s, "%d %d", &a, &b);
        switch(opt) {
            case 'a' : 
                c = a + b;
                sprintf(ans, "%d+%d=%d", a, b, c);
                break;
            case 'b' :
                c = a - b;
                sprintf(ans, "%d-%d=%d", a, b, c);
                break;
            case 'c' :
                c = a * b;
                sprintf(ans, "%d*%d=%d", a, b, c);
        }
        printf("%s\n", ans);
        printf("%d\n", strlen(ans));
    }
    return 0;
}
上一篇:实验3 转移指令跳转原理及其简单应用编程


下一篇:Java @ControllerAdvice 全局异常处理