字符类型

  1. char c;
  2. cin >> c;
  3. scanf("%c", &c);
  4. int a[N];
  5. double a[N];
  6. bool a[N]; // 布尔类型
  1. 'a' 字符 ' '
  2. "a" 字符串 "abc" "" " "
  3. a[0] = '\n';
  4. cout << a[0];
  5. #include <bits/stdc++.h>
  6. using namespace std;
  7. int main()
  8. {
  9. cout << sizeof ('a') << '\n';
  10. cout << sizeof ("a") << '\n'; // 占用存储空间的大小
  11. return 0;
  12. }
  13. /*
  14. 1
  15. 2
  16. */
  17. 字符占一个字节,“a”会增加一个字节,用来存放字符串结束符'\0',所以占2字节

QQ截图20220319103653.png

  1. 单引号,括起来,是字符,不能是空
  2. 双引号,括起来,是字符串,可以是空,表示空串

用【getchar()】读入字符

  1. char c = getchar();
  2. // 可以用来读入空格、回车

ASCII码

  1. // ASCII码表,我们只需记忆
  2. // '0', 48
  3. // 'A', 65
  4. // 'a', 97 delta = 32
  5. 场景:
  6. 1、判断字符串当中的字符,是不是 数字字符、大写字母、小写字母
  7. 2、大小写转换, +/-32 +/-('a' - 'A')
  8. 3、统计字符串当中,26个英文字母出现的次数。经常会用,一个一维数组来维护 int cnt[30];
  9. cnt[s[i] - 'a']++;
  10. 4、字符串当中,有数字字符,比如x1, x123,我们要把1123,弄出来变成int类型
  11. int t = 0, j = i;
  12. while (j < len && s[j] >= '0' && s[j] <= '9'){
  13. t = t * 10 + s[j] - '0';
  14. j++;
  15. }
  1. // 分析下面代码
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. const int N = 110;
  5. char s[N];
  6. int cnt[30]; // 你看,这里面有30这个数字。他会不会在表示26个英文字母,开的数组(猜测)
  7. int main(){
  8. scanf("%s", s); // they are lowercase
  9. for (int i = 0; s[i]; i++) // 遍历整个字符串,结束的判断,用的是s[i]是不是'\0'
  10. cnt[s[i] - 'a']++;
  11. for (int i = 0; i < 26; i++) cout << cnt[i] << ' ';
  12. puts("");
  13. return 0;
  14. }

image.png

一维字符数组,二维字符数组

  1. 一维字符数组
  2. char s[N];
  3. hello, world
  4. 二维字符数组,经常用来表示地图,棋盘
  5. char s[N][N];
  6. .#...#
  7. .@.#..
  8. #.....

开数组要开到全局变量,禁止开到局部,养成好习惯

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. char s[110];
  4. int main(){
  5. return 0;
  6. }
  1. int main(){
  2. int s[110]; // 这种是不好的编程习惯
  3. }
  1. int main(){
  2. int n;
  3. cin >> n;
  4. int s[n]; // 这种就是狗屎
  5. }

用【字符数组】表示字符串

  1. #include <cstdio>
  2. using namespace std;
  3. char s[110];
  4. int main()
  5. {
  6. scanf("%s", s);
  7. printf("%s\n", s);
  8. int len = strlen(s); // 字符串长度
  9. printf("%d\n", len);
  10. return 0;
  11. }

用【string】表示字符串

string是C++里的一个类,字符数组是C语言里的东西。
string的优点,基本上能覆盖字符数组的一切操作,有很多便利的函数可以直接使用。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. string s;
  6. cin >> s;
  7. cout << s << '\n';
  8. int len = s.size(); // 字符串长度
  9. cout << len << '\n';
  10. return 0;
  11. }
  1. 字符串的下标,默认是从0开始的, 0-index
  2. string s = "abc";
  3. s[0] = 'a';
  4. s[1] = 'b';
  5. s[2] = 'c';
  6. 无论用string,还是字符数组读入字符串,下标都是从0开始的
  7. 因为是从0开始的,所以遍历一个字符串时,[0, len - 1]
  8. for (int i = 0, len = s.size(); i < len; i++){
  9. cout << s[i];
  10. }
  11. 说一个特殊情况
  12. 如果,读入的字符串,下标必须是从1开始的,怎么办呢?
  13. 这时候,只能利用字符数组了
  14. char s[110];
  15. scanf("%s", s + 1); // 相当于向右偏移一位
  1. // 下面是一个关于0-index, 1-index的问题,如何读入的问题
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. char s[110], s1[110];
  5. int main(){
  6. scanf("%s", s + 1);
  7. for (int i = 1, len = strlen(s + 1); i <= len; i++) printf("%c\n", s[i]);
  8. scanf("%s", s1);
  9. for (int i = 0, len = strlen(s1); i < len; i++) printf("%c\n", s1[i]);
  10. return 0;
  11. }

用【getline(cin, s)】读入一整行到string

  1. // ab cd ed
  2. // 有空格,但也要读进来
  3. //(cin读入是,遇到空格/回车/文件结束,就会终止读入)
  4. // 注意一点,需要用getline,才用。不要乱用
  5. // (getline读入,是遇到回车/文件结束,才会终止读入)
  6. #include <iostream>
  7. using namespace std;
  8. int main()
  9. {
  10. string s;
  11. getline(cin, s);
  12. cout << s << '\n';
  13. return 0;
  14. }

用【fgets()】读入一整行到字符数组

  1. char s[110];
  2. fget(s, sizeof s, stdin);

禁止使用gets()读入字符数组

string的常用函数

【常用方法】 string s1, s2; s1 += s2; s1.append(s2); 拼接操作 if (s1 == s2) 判断相等的操作 s1 < s2,直接比较大小,字典序 s.size() s1 = s.substr(pos, len); 截取子串 s.insert(pos. s1); 插入字符串 int p = s.find(‘A’); or s.find(“abc”) 查找子串。会返回第一次出现的位置,如果没找到,返回string::npos int p = s.find(‘A’, pos); 从哪个位置开始往后找

  1. // s.size(),长度
  2. // s.substr(2),截取子串
  3. #include <iostream>
  4. #include <cstring>
  5. using namespace std;
  6. int main()
  7. {
  8. string s;
  9. s = "abcdef";
  10. cout << s << '\n';
  11. cout << s.size() << '\n'; //输出字符串长度
  12. cout << s.substr(2) << '\n'; //截取子串,是位置2开始往后所有
  13. cout << s.substr(2, 3) << '\n'; //从位置2开始,截取三位
  14. return 0;
  15. }
  1. // string::operator+=
  2. #include <iostream>
  3. #include <string>
  4. int main ()
  5. {
  6. std::string name ("John");
  7. std::string family ("Smith");
  8. name += " K. "; // c-string
  9. name += family; // string
  10. name += '\n'; // character
  11. std::cout << name;
  12. return 0;
  13. }
  14. // 注意看上面的代码
  15. // 和平时的,并不一样
  16. // 注意观察,哪些不一样,为什么
  17. // string s = "John";
  18. // string s("John");
  19. // using namespace std;
  20. // std::string
  1. // string::substr
  2. #include <iostream>
  3. #include <string>
  4. int main ()
  5. {
  6. std::string str="We think in generalities, but we live in details.";
  7. // (quoting Alfred N. Whitehead)
  8. std::string str2 = str.substr (3,5); // "think"
  9. std::size_t pos = str.find("live"); // position of "live" in str
  10. std::string str3 = str.substr (pos); // get from "live" to the end
  11. std::cout << str2 << ' ' << str3 << '\n';
  12. return 0;
  13. }
  1. #include<iostream>
  2. using namespace std;
  3. int main(){
  4. string s="to be question";
  5. string s2="the ";
  6. s.insert(6, s2); // to be (the )question
  7. cout << s << '\n';
  8. return 0;
  9. }
  1. string s = "abcdef";
  2. 第一种,写循环
  3. for (int i = 0, len = s.size(); i < len; i++) cout << s[i];
  4. 第二种,C++11的写法
  5. for (char c : s) cout << c;
  6. for (char &c: s) c++; // 对字符串的当中的字符进行修改操作,写一个引用符号就可以了
  7. cout << s << '\n';

string与字符数组的相互转换

  1. // string 转 字符数组
  2. char arr[10];
  3. string s = "hello, world";
  4. strcpy(arr, s.c_str());
  1. // 字符数组 转 string
  2. char arr[10];
  3. strcpy(arr, "hello, world");
  4. string s = arr;

字符数组的常用函数

  1. char s[110];
  2. int len = strlen(s);
  3. char s1[] = "aaa";
  4. char s2[] = "bbb";
  5. int p = strcmp(s1, s2); // 相等是0,小于是负数,大于是正数
  6. strcat(s1, s2); // s1后面拼接上s2
  7. strcpy(s1, s2); // s2复制给s1,s1存的东西变成和s2一样

stoi(),Convert string to integer

  1. string s = "1234";
  2. int x = stoi(s);
  3. cout << x * 2<< '\n';

atof(), Convert string to double (function )

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. char s[110];
  4. int main(){
  5. scanf("%s", s);
  6. double x = atof(s);
  7. cout << x * 2<< '\n';
  8. return 0;
  9. }

用【memset】【memcpy】操作字符数组

  1. int s[110];
  2. memset(s, 0, sizeof s); // 全都赋值成0
  3. memset(s, 0x3f, sizeof s); // 全都赋值成0x3f3f3f3f,0x开头的表示16进制数
  4. char s1[] = "abc";
  5. char s2[] = "bcd";
  6. memcpy(s1, s2, sizeof s1); // 把s2的值赋值给s1

strcpy提供了字符串的复制。即strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符。memcpy提供了一般内存的复制。即memcpy对于需要复制的内容没有限制,因此用途更广。

二维字符数组的读入

  1. // 二维字符数组,如何有效读入的问题
  2. #include <iostream>
  3. #include <cstdio>
  4. using namespace std;
  5. const int N = 110;
  6. char s[N][N];
  7. int n, m;
  8. int main()
  9. {
  10. cin >> n >> m;
  11. for (int i = 0; i < n; i++) scanf("%s", s[i]);
  12. for (int i = 0; i < n; i++) printf("%s\n", s[i]);
  13. return 0;
  14. }
  15. /*
  16. .....#
  17. ####.#
  18. ###..#
  19. ##.#..
  20. */
  21. // 如何是下面的写法,是容易被输入数据坑的
  22. for (int i = 0; i < n; i++)
  23. for (int j = 0; j < n; j++)
  24. scanf("%c", &s[i][j]);
  25. // 虽然我们提前知道了每一行每一列有多少个数字,但是这种操作经常被坑
  26. // 原因是,测试数据,可能每一行的后面有一个空格,一个我们看不见的空格
  27. // 你的代码里,会把这个空格当成一个字符读入。然后,自然读入的数据就不准确了
  28. for (int i = 0; i < n; i++)
  29. for (int j = 0; j < n; j++)
  30. cin >> s[i][j];
  31. // 这样倒是可以解决问题,但这并不如上面一层循环更靠谱,尤其当数据量特别大的时候

用【sscanf】从字符串格式化输入

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 30;
  4. char s[] = "Saturday March 25 1989";
  5. char month[N], weekday[N];
  6. int year, day;
  7. int main(){
  8. sscanf(s, "%s %s %d %d", weekday, month, &day, &year);
  9. printf("%d %s %d\n", day, month, year);
  10. return 0;
  11. }
  1. // CSP-J2021 T3网络连接
  2. // 读入数据用sscanf就相当的妙
  3. // 而我赛场上,只会用字符串,一点一点拆。。 差距显然
  4. #include <bits/stdc++.h>
  5. using namespace std;
  6. int a, b, c, d, e;
  7. char s[] = "192.168.1.1:8080";
  8. int main(){
  9. sscanf(s, "%d.%d.%d.%d:%d", &a, &b, &c, &d, &e);
  10. printf("%d.%d.%d.%d:%d\n", a, b, c, d, e);
  11. return 0;
  12. }
  1. sscanf()是有返回值的,和scanf一样,正常读入几个数字,就会返回几

用【sprintf】把一些整型拼成一个字符串(字符数组)

  1. char temp[30];
  2. sprintf(temp, "%d.%d.%d.%d:%d", a, b, c, d, e);

用【stringstream】把一些整型拼成一个字符串(string)

  1. stringstream ss;
  2. ss << a << '.' << b << '.' << c << '.' << d << ':' << e;
  3. string s = ss.str();
  4. cout << s << '\n';

例题, 找第一个只出现一次的字符

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. char s[N];
  5. int vis[30];
  6. int main()
  7. {
  8. scanf("%s", s);
  9. int len = strlen(s);
  10. for (int i = 0; i < len; i++)
  11. vis[s[i] - 'a']++;
  12. bool flag = false;
  13. for (int i = 0; i < len; i++)
  14. {
  15. if (vis[s[i] - 'a'] == 1)
  16. {
  17. flag = true;
  18. printf("%c\n", s[i]);
  19. break;
  20. }
  21. }
  22. if (!flag) printf("no\n");
  23. return 0;
  24. }

例题,验证子串

  1. //string s;
  2. //s.find()
  3. #include <bits/stdc++.h>
  4. using namespace std;
  5. int main()
  6. {
  7. string s1, s2;
  8. cin >> s1 >> s2;
  9. int ans = 0;
  10. if (s1.find(s2) != -1) ans = 1;
  11. else if (s2.find(s1) != -1) ans = -1;
  12. if (ans == 1) cout << s2 << " is substring of " << s1 << endl;
  13. else if (ans == -1) cout << s1 << " is substring of " << s2 << endl;
  14. else cout << "No substring" << endl;
  15. return 0;
  16. }

例题,删除单词后缀

  1. // string s;
  2. // s.erase(len - 2)
  3. // 使用char s[40];
  4. // 匹配后缀成功后,利用s[len-2] = '\0'进行截断

例题,单词的长度

  1. // 第一种方法,直接手搓
  2. // getline(cin, s);
  3. // 然后遍历,碰到空格算一个单词
  4. // 要注意输出的格式
  5. #include <bits/stdc++.h>
  6. using namespace std;
  7. int main()
  8. {
  9. string s;
  10. getline(cin, s);
  11. bool flag = false;
  12. for (int i = 0, len = s.size(); i < len; i++)
  13. {
  14. while (s[i] == ' ') i++;
  15. if (flag) printf(",");
  16. flag = true;
  17. int j = i;
  18. int cnt = 0;
  19. while (j < len && s[j] != ' ') j++;
  20. printf("%d", j - i);
  21. i = j - 1;
  22. }
  23. puts("");
  24. return 0;
  25. }
  26. // 第二种方法
  27. // 使用while (cin >> s),更好编写
  28. // 不过要注意一下格式控制的小技巧
  29. // 本地调试要使用freopen
  30. #include <bits/stdc++.h>
  31. using namespace std;
  32. int main()
  33. {
  34. //freopen("1.in", "r", stdin);
  35. string s;
  36. bool flag = false;
  37. while (cin >> s){
  38. if (flag) cout << ',';
  39. flag = true;
  40. int len = s.size();
  41. cout << len;
  42. }
  43. puts("");
  44. return 0;
  45. }

例题,单词翻转

  1. //getline(cin, s);
  2. //比较麻烦,构造一个一个单词出来,然后翻转
  3. //使用while (cin >> s)
  4. //会两个点,格式错误,调不出来

例题,2048:【例5.18】串排序

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. string s[25];
  4. int n;
  5. int main(){
  6. cin >> n;
  7. for (int i = 0; i < n; i++) cin >> s[i];
  8. sort(s, s + n); // 排序,按【字典序】排序
  9. for (int i = 0; i < n; i++) cout << s[i] << '\n';
  10. return 0;
  11. }

结束符 ‘\0’

image.png

【扩展阅读】C++中字符串的结尾标志 \0

  1. \0C++中字符串的结尾标志,存储在字符串的结尾,它虽然不计入串长,但要占一个字节的内存空间。在百度百科中查看\0词条,会有这样一句话:c/c++中规定字符串的结尾标志为'\0'。有人可能认为,在C语言里(C++会不同),'\0'并不是字符型,而是int型。在这里,我们姑且和百度词条作者保持一致,认为\0'\0'是等价的。由于不同处理器的位数不同,'\0'并不一定是8位的00000000。实际上,由于不同处理器的位数不同,sizeof(int)返回的结果也都不同,而sizeof(char)返回结果一般是1,对8位机来说,一个字节由8位组成,16位机一个字节由16位组成,我们通常用的电脑通常是32位的,即一个字节由32位组成,现在已经是64位机了。CPU一般是以字节为单元进行读取的。但是一般情况下大家还是认同1Byte等于8bit的说法,因为这是构成的最小位数。
  2. '\0'是转义字符,意思是告诉程序,这不是数字0'\0'0两者基本上可以通用,例如: string[i] != '\0'string[i] != 0是一样的。不过'\0'的类型是char型,而0int类型,所以在大多数计算机上,sizeof(0) = 4sizeof('\0') = 1,这在特殊情况下不可通用。另外扩展一下,'\0''0'也是不同的,他们都是字符,但是他们的ASCII码是不同的:'\0' ASCII码值为0'0' 也可以写成'\0x30' ASCII码值为48
  3. C语言中没有专门的字符串变量,通常用一个字符数组来存放一个字符串。字符串总是 '\0'作为串的结束符。因此当把一个字符串存入一个数组时,也把结束符 '\0'存入数组,并以此作为该字符串是否结束的标志。有了'\0'标志后, 就不必再用字符数组的长度来判断字符串的长度了。
  4. '\0'就是 字符串结束标志。比如说,把一个字符串赋值给数组:char str1[] = {"Welcome!"}。实际上数组str1在内存中的实际存放情况为: 'W' 'e' 'l' 'c' 'o' 'm' 'e' '!' '\0'。这后面的'\0'是由C编译系统自动加上的。所以在用字符串赋初值时一般无须指定数组的长度, 而由系统自行处理。 把字符数组str1中的字符串拷贝到字符数组str2中。串结束标志'\0'也一同拷贝。