Lesson 7. Ascii 转换与进制转换
目录
在本课中,我们将探讨字符如何通过 ASCII 值存储和处理,并学习如何在不同的进制(如二进制、八进制、十进制和十六进制)之间进行转换。
1. 理解 ASCII
1.1 什么是 ASCII?
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是一种字符编码标准,它使用数值来表示字符。每个字符(如字母、数字、符号等)都有一个唯一的 ASCII 代码,通常范围从 0 到 127。 这些值可以用于字符与数字之间的转换。例如,将字符 '0' 到 '9' 转换为对应的整数,或反向操作。
示例 ASCII 表
字符 | ASCII 值 | 二进制 | 十六进制 |
---|---|---|---|
'A' | 65 | 01000001 | 0x41 |
'a' | 97 | 01100001 | 0x61 |
'0' | 48 | 00110000 | 0x30 |
' '(空格) | 32 | 00100000 | 0x20 |
完整ASCII表格:https://www.ascii-code.com/
1.2 ASCII 的应用
在 C++ 中,每个字符都以其对应的 ASCII 值形式存储为整数。因此我们可以通过将字符视为整数来轻松地操作它们。
通过ASCII值,我们可以对字符进行加减运算,甚至是转换。
1.2.1 使用 +'0'
和 -'0'
进行转换
-'0'
:字符到整数的转换
通过将字符减去 '0'
,可以得到对应的整数值。比如 '3'
- '0'
的结果是整数 3
。
示例:字符到整数的转换
C++ | |
---|---|
Text Only | |
---|---|
+'0'
:整数到字符的转换
通过将整数加上 '0'
,可以得到对应的字符。比如 5
+ '0'
的结果是字符 '5'
。
示例:整数到字符的转换
C++ | |
---|---|
Text Only | |
---|---|
1.2.2 使用 +'A'
和 -'A'
进行转换
通过在字符上加上或减去字符 'A'
,我们可以将字符与字母表的位置关联起来。例如,将字符 'C'
减去 'A'
可以得到 'C'
在字母表中的位置(从0开始计数)。这样对于实现简单的字母编码或解码非常有用。
-'A'
:字符到字母表位置的转换
通过将字符减去 'A'
,可以得到字符在字母表中的位置(从0开始)。比如 'C'
- 'A'
的结果是 2
。
示例:字符到字母表位置的转换
C++ | |
---|---|
输出:
Text Only | |
---|---|
+'A'
:字母表位置到字符的转换
通过将字母表中的位置加上'A'
,可以得到对应的字符。比如 2
+ 'A'
的结果是字符 'C'
。
示例:字母表位置到字符的转换
C++ | |
---|---|
输出:
Text Only | |
---|---|
综合运用 +'0'
和 -'0'
以及 +'A'
和 -'A'
通过结合使用这些操作符,我们可以灵活地在字符、数字和字母表位置之间进行转换。例如,可以编写一个程序,将字符串中的每个数字字符转换为整数,然后进行简单的运算,再将结果转换回字符;或者将字母字符转换为在字母表中的位置,并进行一些加密操作。
示例:处理字符串中的数字和字母
输出:
1.3 ASCII 例题讲解
1.3.1 LC1309 解码字母到整数映射
题目描述:
给定一个加密字符串 s
,它由数字 '0'
到 '9'
、'#'
和字母组成。我们按照下述规则解码:
1. 字符('1'
- '9'
)表示映射到小写字母('a'
- 'i'
)。
2. 字符('10#'
- '26#'
)表示映射到小写字母('j'
- 'z'
)。
返回字符串 s
解码后的结果。
注意:
- 输入只包含数字、字符
'#'
和小写字母。 - 字符串的长度范围是
[1, 1000]
。
示例:
提示
- 准备一个
vector<char>
来存储字母a
到z
的映射。 -
遍历字符串
s
:- 如果当前字符是数字且后面有
'#'
,就将当前的数字和下一个数字拼接成两位数字,然后将其转换为字母。 - 如果当前字符是单独的数字,则直接转换为字母。
- 如果当前字符是数字且后面有
-
构建解密后的字符串。
参考答案
代码讲解
- 构建映射表:
vector<char> mapping(26)
创建一个包含 26 个字符的数组,其中索引0
对应'a'
,索引25
对应'z'
。 -
遍历字符串:循环遍历加密字符串
s
。- 如果当前字符后面两个字符构成一个有效的
"#"
序列,提取该序列,计算对应的数字并映射为字母。 - 如果是单个数字,则直接映射为相应的字母。
- 如果当前字符后面两个字符构成一个有效的
-
结果构建:将解密后的字符拼接到结果字符串中。
1.4 ASCII 举一反三
1.4.1 LC387. 字符串中的第一个唯一字符
问题描述
给定一个字符串 s
,找到第一个不重复出现的字符,并返回它的索引。如果不存在,则返回 -1
。
思路分析
通过 vector
来实现字符频率统计。我们可以使用一个大小为 26 的 vector
来存储每个字母出现的次数。
具体步骤如下:
- 字符频率统计:使用一个大小为 26 的
vector
来统计每个字母的出现次数。通过s[i] - 'a'
可以确定字母在vector
中的索引位置。 - 遍历字符串:先遍历一次字符串,统计每个字符的出现次数。
- 找到第一个唯一字符:再次遍历字符串,根据统计结果找到第一个出现次数为 1 的字符。
参考解答
代码讲解
vector<int> count(26, 0);
:定义一个长度为 26 的vector
,用于存储每个字母的出现次数。vector
的初始值全部设置为0
。- 第一次遍历字符串:在第一次遍历中,通过
s[i] - 'a'
计算出当前字符在vector
中的索引位置,并将其对应的值加 1。 - 第二次遍历字符串:通过第二次遍历字符串,找出第一个出现次数为 1 的字符,并返回该字符的索引。
- 返回结果:如果没有找到唯一字符,则返回
-1
。
1.4.2 LC171. Excel表列序号
问题描述
给定一个字符串 columnTitle
,表示 Excel 表格中的列名。请将其转换为对应的列序号。
例如:
思路分析
通过 进制转换 的方法,将 Excel 列名看作一个以 26 为基数的数字系统,每个字母代表一个数字。A
到 Z
分别代表 1 到 26,我们需要将列名转换成相应的数字。
具体步骤如下:
- 字符转换为数字:每个字符从
A
到Z
对应的值为1
到26
,可以通过columnTitle[i] - 'A' + 1
得到其对应的数值。 - 进制转换:将列名看作是 26 进制数,从右到左遍历字符串,将每个字符转换成数字并乘以 26 的幂次累加到结果中。
- 返回结果:最终的累加结果即为 Excel 表格列名对应的列序号。
参考解答
代码讲解
int extract_char = columnTitle[i] - 'A' + 1;
:将字母A-Z
转换为对应的 1-26 数字。- 进制转换:每个字符的位置对应 26 的幂次,最右边的字符乘以
26^0
,第二个字符乘以26^1
,依次类推。 result += extract_char * pow(26, pow_num);
:将每个字符对应的值乘以 26 的幂次,并累加到结果中。- 返回结果:最终结果是列名对应的列序号。
1.4.3 LC804. 唯一摩斯密码词
问题描述
给定一个字符串数组 words
,其中每个单词由小写字母组成。每个字母可以用摩斯密码表示,例如:
- 'a' -> ".-"
- 'b' -> "-..."
- 'c' -> "-.-."
- ...
你需要将每个单词转换为摩斯密码表示,并返回唯一摩斯密码词的个数。
思路分析
- 摩斯密码映射:每个字母对应一个固定的摩斯密码,可以用数组来表示每个字母的摩斯密码。
- 逐字转换:遍历每个单词,将其转换为摩斯密码组合。
- 唯一性检查:通过向量存储已转换的摩斯密码,并检查每个转换是否是唯一的。
- 返回结果:最终返回唯一摩斯密码的数量。
参考解答
代码讲解
- 摩斯密码映射:用长度为 26 的数组
morse_code
来存储字母 'a' 到 'z' 对应的摩斯密码表示。 - 单词转换:通过遍历单词的每个字符,根据字符的 ASCII 值找到对应的摩斯密码并将其组合起来。
- 检查唯一性:通过
find
检查摩斯密码组合是否已存在于result
中,如果没有则添加。 - 返回结果:返回存储唯一摩斯密码组合的向量
result
的大小,即唯一摩斯密码的数量。
2. 进制转换
2.1 数字进制概述
- 二进制(Base 2): 使用数字 0 和 1。例如:
1010
(二进制) =10
(十进制)。 - 八进制(Base 8): 使用数字 0 到 7。例如:
12
(八进制) =10
(十进制)。 - 十进制(Base 10): 标准数字系统。例如:
10
(十进制)。 - 十六进制(Base 16): 使用数字 0-9 和字母 A-F。例如:
A
(十六进制)=10
(十进制)。
以下表格展示了十进制数字从1到10的二进制、八进制和十六进制表示:
十进制 (Decimal) | 二进制 (Binary) | 八进制 (Octal) | 十六进制 (Hexadecimal) |
---|---|---|---|
1 | 1 | 1 | 1 |
2 | 10 | 2 | 2 |
3 | 11 | 3 | 3 |
4 | 100 | 4 | 4 |
5 | 101 | 5 | 5 |
6 | 110 | 6 | 6 |
7 | 111 | 7 | 7 |
8 | 1000 | 10 | 8 |
9 | 1001 | 11 | 9 |
10 | 1010 | 12 | A |
2.2 十进制到二进制的转换
手动方法:使用短除法
- 步骤1:将十进制数除以 2。
- 步骤2:记录余数。
- 步骤3:用商继续除以 2,直到商为 0。
- 步骤4:二进制数即为逆序读取的余数序列。
示例:将 13 转换为二进制
结果:13 (十进制) = 1101 (二进制)
用 C++ 实现十进制到二进制的转换
代码解释:
ans.push_back('0' + num % 2);
:num % 2
计算当前位的二进制值,'0' + num % 2
将其转换为字符并添加到ans
字符串的末尾。num = num / 2;
:更新num
为当前商,继续下一轮的除法运算。reverse(ans.begin(), ans.end());
:反转字符串ans
,因为计算得到的二进制位是逆序的。
2.3 二进制到十进制
手动方法:
- 步骤1:将每个二进制位乘以 2 的相应次方(从右往左,位置从 0 开始)。
- 步骤2:将所有结果相加。
示例:将 1101 转换为十进制
Text Only | |
---|---|
结果:1101 (二进制) = 13 (十进制)
用 C++ 实现二进制到十进制的转换
C++ | |
---|---|
代码解释:
ans = ans * 2 + (i - '0');
:i - '0'
将字符转换为对应的整数值,ans * 2
将前面计算的十进制值左移一位,然后加上当前位的值。- 这个过程模拟了手动计算的过程,每次乘以 2 相当于在十进制中将位数进一位,然后加上当前的二进制位。
2.4 十进制到十六进制
手动方法:
- 步骤1:将十进制数除以 16。
- 步骤2:记录余数,如果余数大于 9,则将其转换为对应的十六进制字符(A-F)。
- 步骤3:用商继续除以 16,直到商为 0。
- 步骤4:十六进制数即为逆序读取的余数序列。
示例:将 479 转换为十六进制
结果:479 (十进制) = 1DF (十六进制)
用 C++ 实现十进制到十六进制的转换
代码解释:
remainder = num % 16;
:计算当前位的十六进制值。result += (remainder - 10) + 'A';
:如果余数大于或等于10,则将其转换为对应的字母A-F。result += remainder + '0';
:如果余数小于10,则直接转换为对应的字符0-9。reverse(result.begin(), result.end());
:因为计算得到的十六进制位是逆序的,因此需要反转结果字符串。
2.5 二进制到十六进制
手动方法:
二进制转换为十六进制的方法是将二进制数每4位分为一组,然后将每组的二进制数转换为对应的十六进制数。因为十六进制是以4位二进制为基础的,所以这个过程非常直接。
步骤:
- 从右到左,将二进制数每4位分为一组。如果不足4位,则在左侧补零。
- 将每组二进制数转换为对应的十六进制数。
- 将转换后的十六进制数连接起来,得到最终结果。
示例:将二进制 110111101
转换为十六进制
步骤:
1. 将 110111101
分为 1101
和 1110
,不足4位的左侧补零:0110 1110 1101
2. 将每组转换为十六进制:
Text Only | |
---|---|
1 2 3 |
|
- 将这些转换结果连接起来:
6ED
结果:110111101 (二进制) = 6ED (十六进制)
代码实现部分我们现在先不需要深入了解,因为涉及到其他的一些函数和库的使用。当前我们主要专注于理解手动方法,这样可以帮助更好地掌握进制转换的基础概念。
2.6 进制 例题讲解
2.6.1 LC504. 七进制数
问题描述
给定一个整数 num
,返回它的 7 进制表示字符串。
思路分析
将一个整数转换为7进制本质上和转换为任何其他进制的思路一致。我们可以通过不断地取模(%
)和除法(/
)操作来逐位获取7进制的各位数字,最后将其反转即可得到最终的结果。
-
处理负数:
- 如果输入
num
是负数,先取它的绝对值进行计算,最后在结果前添加负号。
- 如果输入
-
处理 0 的特殊情况:
- 如果
num == 0
,直接返回"0"
。
- 如果
-
取模法构建7进制:
- 我们不断将
num
除以 7,取出余数作为当前位的7进制数字,将其加入到结果字符串中。然后将num
更新为num / 7
,直到num
变为 0 为止。
- 我们不断将
-
反转字符串:
- 因为我们从最低位开始计算,所以最终需要将字符串反转。
参考解答
代码讲解
initial_num = num
:保留原始的num
值,用于后续判断是否需要添加负号。- 处理负数:如果
num
为负数,先取绝对值,计算完后再在结果前加上'-'
。 - 处理0的情况:如果
num
为 0,直接返回"0"
。 - 取模和除法:通过
num % 7
获取当前位的7进制数字,并将其加入到结果字符串中;然后通过num /= 7
进行下一位的计算。 - 反转字符串:由于从最低位开始构建字符串,最后需要反转得到正确的顺序。
- 返回结果:经过所有处理后,返回结果字符串。
2.7 进制 举一反三
2.7.1 LC168. Excel表列名称
问题描述
给定一个正整数,返回它在 Excel 表中对应的列标题。Excel 列是以 A-Z
的方式排列的,例如:
思路分析
这是一个典型的进制转换问题。需要将数字转换成以 A-Z
为基础的26进制表示法,类似于十进制转换为二进制或十六进制。由于 Excel 列编号从 1 开始,而不是从 0 开始,所以在转换时需要一些额外的调整。
-
从 1 开始的26进制:
- 我们可以将
A-Z
对应的数字看作是一个基于26的进制系统。 - 但与常见进制不同的是,Excel 列编号是从 1 开始的,而不是从 0 开始,因此在每次取余时需要做一些调整:每次将
columnNumber
减去 1。
- 我们可以将
-
处理逻辑:
- 不断将
columnNumber
减去1,并计算出对应的字母。 - 每次通过
columnNumber % 26
得到一个字母,将其加入结果字符串中。 - 将
columnNumber
除以26,继续处理下一个数字,直到columnNumber
为 0。
- 不断将
-
反转字符串:
- 由于我们从最低位开始构造字符串,因此最后需要将结果字符串反转。
参考解答
代码讲解
-
columnNumber -= 1
:columnNumber
需要减 1 是因为 Excel 列号是从 1 开始的,而 26 进制的字母表示系统是从 0 开始的。减 1 的操作将列号转换为 0 基础的系统,使得数字1
对应字母A
,数字26
对应字母Z
,从而正确地进行进制转换。比如当
columnNumber = 1
时,如果我们不减 1,按照进制转换过程会出错:不减 1 的情况下:
columnNumber = 1
- 直接计算
1 % 26 = 1
,然后加上 'A' 得到1 + 'A' = 'B'
。但是我们希望columnNumber = 1
对应的是A
而不是B
,所以这显然是错误的。
减 1 的情况下:
columnNumber = 1 - 1 = 0
- 计算
0 % 26 = 0
,然后加上 'A' 得到0 + 'A' = 'A'
。 - 因此,
columnNumber = 1
正确对应到字母A
。
-
columnNumber % 26
:计算当前的列对应的字母编号(0 到 25 对应A
到Z
)。 result += temp + 'A'
:将编号转换为字母并添加到结果字符串中。columnNumber /= 26
:将列号除以 26,处理下一位。- 字符串反转:因为字母是从最低位开始计算的,因此需要在最后反转字符串。
2.7.2 LC728. 自除数
问题描述
自除数是指一个整数,它的每一位数字都能整除这个数本身。给定一个范围 [left, right]
,返回该范围内所有的自除数。
一个自除数必须满足以下条件:
1. 该数不能包含数字 0
。
2. 该数能被它的每一位数字整除。
思路分析
- 逐位检查:对于每个数字,将其每一位提取出来,检查该位是否为 0,并且能否整除原数字。
- 遍历范围:对于给定的范围
[left, right]
,逐一检查每个数字是否是自除数,如果是,将其加入结果列表中。 - 返回结果:返回在
[left, right]
范围内所有自除数的列表。
参考解答
代码讲解
-
check_selfDivid
函数:这个函数用于检查一个数是否是自除数。逐位提取数字,并检查每位是否能整除原数字。如果有任何一位不能整除或者包含 0,则返回false
。 -
遍历范围:在
selfDividingNumbers
函数中,遍历范围[left, right]
,使用check_selfDivid
函数判断每个数是否是自除数,如果是则将其加入结果列表。 -
返回结果:最终返回
[left, right]
范围内所有自除数的列表。
2.7.3 LC693. 交替位二进制数
问题描述
给定一个正整数 n
,检查其二进制表示是否为交替位二进制数,交替位二进制数的定义是其相邻的两个位始终不同,即二进制中 0
和 1
交替出现。
思路分析
- 逐位检查:通过不断将
n
右移,并提取其当前的最后一位,检查是否相邻的两位相同。 - 终止条件:当某一位和其相邻的上一位相同时,直接返回
false
。 - 全部检查通过:如果所有相邻的位都不同,则返回
true
。
参考解答
C++ | |
---|---|
代码讲解
now = n % 2;
:提取当前最低位。n = n / 2;
:右移n
,去掉已经检查的最低位。next = n % 2;
:提取右移后的下一位。- 相邻位比较:如果
next
和now
相等,则说明相邻的两位相同,返回false
。 - 检查通过:如果所有位都不同,返回
true
,表示n
是交替位二进制数。
3. 课后练习
ASCII 相关问题
题目编号 | 题目名称 | 简介 |
---|---|---|
LC405 | 数字转换为十六进制数 | 将一个整数转换为其 16 进制表示。 |
LC806 | 写字符串需要的行数 | 给定若干行字符串,计算写入所需的行数。 |
LC2309 | 间距大小写的最好英文字母 | 找到包含大写和小写字母的最大英文字母。 |
LC383 | 赎金信 | 判断一个字符串中的字符是否能由另一个字符串中的字符构成。 |
LC389 | 找不同 | 给定两个字符串,找出一个在其中多出的字符。 |
进制转换相关问题
题目编号 | 题目名称 | 简介 |
---|---|---|
LC868 | 二进制间距 | 找到二进制数中最大的 0 和 1 间的距离。 |
LC1317 | 将整数转换为两个无零整数的和 | 将一个整数转换为两个非零整数之和。 |
LC1281 | 整数的各位积和之差 | 返回一个整数各位数字乘积与和的差值。 |
LC67 | 二进制求和 | 给定两个二进制字符串,返回它们的和(以二进制表示)。 |