字符是计算机中表示文本的基本单位。在C++中,字符用 char
类型表示,通常占用 1 个字节(8位)的内存空间。字符可以用单引号 ''
括起来表示,例如 'a'
、'1'
、'@'
等。
字符串是由多个字符组成的序列。在C++中,字符串可以用字符数组或 string
类来表示。字符串通常用双引号 "" 括起来表示,例如 "hello"、"how are you" 等。
字符与字符串处理,在编程中一直是一个比较重要的主题,相较整数类型而言,也更为复杂一些。下面分别来看看。
在计算机,字符通常是被编码成整数,最终以 0 和 1 来存储的,每个字符都有不同整数与之对应。
编码的方式有很多种,在 C++ ,使用的是 ASCII 编码,ASCII(American Standard Code for Information Interchange,美国信息交换标准代码),这是一种基于拉丁字母的编码系统,用于数字、大小写字母以及一些特殊符号的编码。
下面这张 ASCII 编码表来自维基百科。
通过这张 ASCII 码表可以看到,ASCII 码表定义了128个字符,对应于整数 0 ~ 127,包括英文字母、数字、标点符号和控制字符,每个字符对应一个 ASCII 码值,例如字符 'A' 的 ASCII 码值是 65 。
ASCII 码表分为以下几个部分:
\n
(ASCII 10)、回车符 \r
(ASCII 13)等。这张表不需要记忆,但下面几个特点最好知道:
0 ~ 9
之间是连续的,A ~ Z
之间是连续的;,a ~ z
之间也是连续的。0
对应的码值是 48;字符 A
对应的码值是 65;字符 a
对应的码值是 97 。我们可以通过一个循环来输出数字 0 ~ 127 及其对应的字符。我们只需要在输出时做一个强制类型转换就可以了。
输出结果类似下面这样(这里只选取了其中一部分显示),大家可以对照上面的 ASCII 码表看看是否正确呢。
在 C++ 中,字符和ASCII码之间可以相互转换。字符在内存中是以整数形式存储的,因此可以直接将字符赋值给整数变量,或者将整数赋值给字符变量。字符也可参与运算,运算时会转换为对应的码值来计算。
下面的代码演示了字符与对应的 ASCII 码值相互转换。
运行程序,输出
在使用
cout
输出时,C++ 编译器会根据数据类型来决定最终输出什么,如果数字i
在 0 ~ 127 之间,char(i)
则会被解读为数字对应的字符并输出,否则将会直接将数字输出。
下面通过几个练习来熟悉一下字符与 ASCII 码的一些常见操作。
由 ASCII 码表可知,大写字母之间是连续,小写字母之间也是连续,我们可以利用这个特性来判断一个字符是否是大写或小写字母,或者是数字。
在这里我们直接比较的是字符,程序运行时,实际上会转换为 ASCII 所对应的整数来进行比较,强烈推荐这样来使用。
在判断比较时,初学者容易像下面这样写:
尽管程序没问题,但不建议这样写,这需要我们能记住字母所对应的 ASCII 码值是多少(至少范围的开始与结束),一旦记错,程序的判断逻辑就错了,而且这种错误不容易发现。
ASCII 码表中,大写字母和小写字母的ASCII码值相差 32。因此,可以通过加减 32 来实现字符的大小写转换。
如果从键盘读入一个字符,这个字符可能是大写字母也可能是小写字母。如果大写字母,输出其对应的小写字母;如果是小写字母,输出其对应的大写字母。
同样的,我们这里并没有像下面这样通过直接加减数字 32 来实现判断逻辑。
但不建议这么做,因为这个 32 很可能会记错,比如记成 33,那结果就完全错了。我们知道,小写字母对应的大写字母之间的码值是固定的,因此可以通过字符运算计算出来的,实际上, 'a' - 'A' 刚好就是 32,但不用我们去记。
char
只代表单个的字符,而在实际应用中,更多是多个字符组成的文字,比如 hello, c++
,由多个字符组成的这种类型我们称之为字符串(string)。字符串通常是用 ""
双引号括起来表示的。
C++ 标准库提供了 string
类,用于更方便地处理字符串。而在 C 语言中并没有 string
类型,在 C 语言中是用字符数组来对字符串进行处理的。
字符串本质上就是以空字符 \0
为结尾的字符数组,\0
表示字符串的的结束;而字符数组就是一个存储字符的普通数组,并非一定要以 \0
为结尾。
字符数组可以通过以下方式声明和初始化:
需要特别注意的是,当我们使用字符串来初始化字符时,字符数组末尾会自动添加空字符 \0
,因此字符数组的长度实际上要比看到的字符串长度多 1 。
对于字符串 str2
,如果像下面这样实始化字符数组:
运行会报错,原因是没有没有足够的数组空间来存放字符。字符串 "Hello" 共 5 个字符,长度为 5,但对应的字符数组长度实际应该为 6,因为末尾还有一个空字符 \0
。
我们可以像普通数组一样对字符数组进行操作,例如,我们可以通过下标访问和修改字符数组中的字符。
获取字符串长度
字符数组的长度可以通过 strlen
函数获取,该函数返回字符串的长度(不包括 '\0'
)。
示例代码:
遍历字符数组
需要注意的是,这里我们不需要输出字符数组末尾的 \0
,因此 i < strlen()
这里没有取等号。
也可以通过判定是否到末尾来遍历字符数组,像下面这样。
字符操作函数主要针对单个字符,通常用在判断字符类型或转换字符大小写。
函数名 | 作用 |
---|---|
isalpha(c) | 判断字符 c 是否是字母 |
isdigit(c) | 判断字符 c 是否是数字 |
isalnum(c) | 判断字符 c 是否是字母或数字 |
islower(c) | 判断字符 c 是否是小写字母 |
isupper(c) | 判断字符 c 是否是大写字母 |
isspace(c) | 判断字符 c 是否是空白字符 |
函数名 | 作用 |
---|---|
tolower(c) | 将大写字母 c 转换为小写字母 |
toupper(c) | 将小写字母 c 转换为大写字母 |
有了字符的判断与转换函数,前面的字母大小写转换示例我们就可以用这些函数改写一下。
改写后的代码如下:
使用现有的函数后,程序更简洁一些,而且可读性也更强了一些。
从学习的角度来说,既要学会使用函数,也需要知道函数背后的实现原理(如何利用 ASCII 字符的规律),两种方式都需要掌握。
字符串是由多个字符组成的序列。在C++中,字符串用 string
类型来表示。string
类封装了字符数组,并提供了丰富的成员函数来操作字符串。
下面的程序演示了几种字符串定义与初始化的方式。
运行程序,得出结果
需要注意的是,在 C 语言中并没有 string
类型,如果想要使用 printf()
进行输出,需要使用字符串中内置的函数 c_str()
把 string
转换为 C 语言接受的形式(实际上转换成为字符数组)。
同样的,我们也可以用 cin
去读入字符串,例如:
和之前读入整数类型完全一致的用法,非常简单!不过如果输入的是一串带空格的字符串,输出就会有点问题了,例如输入:
运行程序,发现只输出了 Hello
,后面的 C++
并没有读入进去。
在使用
cin
读入字符或字符串时,一旦遇到空白字符(如 tab、空格、换行等),就会自动停止读取,所有的空白字符都会被跳过,不会成为字符串的一部分。
如果要读入一串带空白字符的字符串,需要用到getline()
,从名称就可以看出,这个指令是以「行」为单位来读入数据的。
在实际应用中,我们经常会遇到读入多行字符串的情况。
例如,输入正整数 n,要求依次读入接下来的 n 行字符串并输出。
例如,按以下格式输入数据:
直接应该输出
很容易写出下面这样的代码:
感觉没什么毛病,运行程序看看,结果显示:
发现最后的 18
没读入进来,但输出结果前面有一个空行,这是为什么呢?
这是因为 getline()
在每次读入数据时会读到换行符才停止,而 cin
读完数据就停下来了。对于上面的代码,第 7 行的 cin
读入,只会读入数字 3,读取的就停在 3 和后面的换行符之间了。
后面 getline()
接着读,会接着读,首先就读到了换行符,因此会产生一个空行,再读入后面的 Alan
。
解决的办法是,在 cin
后加上一句 cin.ignore()
,这样就忽略掉 cin
后面的换行符了,后面的读入也就正常了。
字符串本质上就是一个字符数组,因此我们可以像常见的数组那样去访问和操作字符串。
在 C++ 中,我们可以通过
s.length()
和s.size()
来获取字符串的长度,两者是等价的,s.size()
是后面为了兼容 STL 容器加入的,推荐使用s.size()
即可。
因此我们可以像遍历数组那样用循环去遍历字符串中的每个字符,下面这个例子就是将字符中的所有字符依次打印出来,每个字符一行。
运行结果:
下面列举了 string
类的一些常用方法。
方法名 | 作用 |
---|---|
s.size() / s.length() | 获取字符串 s 的长度 |
s.empty() | 判断字符串 s 是否为空 |
s.substr(pos, len) | 截取子串 |
s.find(str) | 在字符串 s 中查找子串 str 的位置 |
s.insert(pos, str) | 在字符串 s 中指定位置插入字符串 str |
s.erase(pos, len) | 删除子串 |
s.replace(pos, len, str) | 替换子串 |
题目描述
输入一个很大的整数(不超过 200 位),求各位上的数字和。
如,输入数字 12345
,输出 15
。
题目解析
因为输入的是一个很大的整数,我们就不能用 int
或 long long
变量来存储了。比较好的方法是用字符数组或直接用字符串来存储。
无论用字符数组还是字符串,我们都需要解决一个问题,因为读入的数字每一位都是一个字符,我们要求数字和,需要将字符数字变为真正的数字。
参考代码
这里用字符串来存储输入的大数字。
输入一个字符串,判断该字符串是不是一个回文字符串。如果是回文数,则输出 "Y",否则输出 "N"。
输入数据
输出数据
题目分析
如果一个字符串正着读和倒着读是一样,这个字符串就是一个回文字符串,否则就不是回文字符串。我们可以找到多种方法来实现回文字符串的判断。
实现代码 - 方法一
利用字符串拼接,将一个字符串倒序,再将倒序后的字符串与原始字符串对比,如果相等,则是回文字符串,否则不是回文字符串。
实际上,由于拼接是有顺序的,"a" + "b"
与 "b" + "a"
得到的结果相好是逆序,我们可以巧妙地利用这个特点,不用倒着来一遍,正着来也是一样的。
实现代码 - 方法二
方法二稍微巧妙一点,我们可以同时对比字符串的第一个字符和最后一个字符是否相等,如果不相等,那肯定不是回文字符串,如果相等,再对比第 2 个字符与倒数第 2 个字符是否相等,如果不相等,那肯定不是回文字符串,如果相等,再按同样的规则继续对比下去...
如下图所示,下标 i
和 j
从两相向推进,只要 i < j
就一直对比下去。
实现代码 - 方法三
在我们学习 STL 后,就可以直接使用 reverse()
来实现字符串逆序操作。判断回文数就更简单了。
要使用 reverse()
,需要加入头文件 #include <algorithm>
。当然你如果使用的是万能头文件 #include <bits/stdc++.h>
,就没这个问题了。
在信息传输过程中,为了保证信息的安全,往往需要对信息进行一定的加密处理。恺撒密码(Caesar Cipher) 是一种非常简单的加解密方法,因古罗马时代的恺撒大帝曾使用过这种密码而得名。
恺撒密码是属于移位密码(Shift Cipher)中的一种,简单来说就是将明文中所有的字母在字母表中按照一定的字数进行「平移」,平移后对应的字母组成密文。
关于信息加密,这里有几个概念:
明文
,指我们需要加密的内容,也就是加密前的内容密文
,也就是加密后的内容密钥
,也就是「平移」多少位,例如:如果平移 3 位,密钥就是 3例如将字母ABC进行加密,密钥是 3,根据规则,字母 A 将被替换成它向后移 3 位的字母 D,字母 B 与 C 按同样的规则进行替换,因此 ABC(明文)加密后就变成了 DEF(密文)。如下图所示:
题目描述
给定一个字符串,根据恺撒密码,输出加密后的字符串。
输入数据
输出数据
参考代码
代码解析
上面的代码中唯一需要注意的是如何处理字母循环移位的,例如,如果字母 z
需要向后移位,应该又再回字母 a
从头开始计算,z
向后移 3 位,得到的是字母 c
。
这个转换是通过取模运算来实现的,先把字符转换为数字,再移位,对移位后得到的数字进行取模运算,取模运算后再转换为对应的字母。
字符是构成文本的基本单元,而字符串则是由字符组成的序列,这两者都是非常重要的数据类型,在处理文本和字符串数据时非常有用。在算法竞赛中也很常见,如字符统计与转换、字符串匹配、字符串压缩等等。