好久不见各位同学,我是人,前段时间老师也是特别忙,所以停更了一周,不过,和大家说好了会更完这个系列,老师就一定不会食言的!那么今天咱们《代码世界冒险指南》的第四篇——GESP三级通关宝典,他来啦!
一、数据编码
核心知识:原码、反码、补码的由来与详解 计算机只知道0和1。如何表示负数?如何让加减法统一运算?
原码
最高位作为符号位:0表示正,1表示负。
其余位表示该数的绝对值。 例如,用8位二进制表示:
+5:绝对值二进制是 101,凑齐8位,符号位0,所以是 0000 0101
-5:绝对值二进制是 101,符号位1,所以是 1000 0101 优点:非常直观,人类容易理解。 缺点:
存在两个0:+0 (0000 0000) 和 -0 (1000 0000)。这在数学上是没有意义的,而且计算机需要处理两种零(+0和-0),增加复杂性。
加减运算复杂:
对于加法:如果是同号,绝对值相加,符号不变;如果是异号,需要比较绝对值大小,然后用大的减小的,结果符号与大的相同。这意味着电路需要设计加法器和减法器两种,且要判断符号,非常麻烦。
反码
为了解决减法问题,人们引入了反码。 定义:
正数的反码与其原码相同。
负数的反码:符号位不变,其余位按位取反(0变1,1变0)。 例如,8位二进制:
+5 的反码:0000 0101
-5 的原码:1000 0101,反码:1111 1010 意图:使用反码,可以将减法转换为加法。原理是:一个数减去另一个数,等于加上这个数的相反数。那么如何表示相反数?就是用反码。 验证:计算 5 - 3,可以转化为 5 + (-3)。
language
5 的原码/反码: 0000 0101
-3 的反码: 1111 1100 (因为3的原码是0000 0011,取反得1111 1100,符号位为1)
相加:
0000 0101
* 1111 1100
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 0000 0001 // 最高位产生进位
这里出现了一个问题:多出来的进位怎么处理?反码的规定是:如果最高位有进位,则要在结果的最低位加1(称为“循环进位”)。 所以,得到 0000 0001 后,再加1,得到 0000 0010,即 2,结果正确。 缺点:
仍然有两个零:+0 的反码是 0000 0000,-0 的反码是 1111 1111。
循环进位增加了电路复杂度。
补码
为了彻底解决两个零和循环进位的问题,补码诞生了。 定义:
正数的补码与其原码、反码相同。
负数的补码:在其反码的基础上加1(丢弃最高位的进位)。 转换步骤(负数):
写出该负数的绝对值所对应的二进制(原码)。
按位取反(得到反码)。
加1(得到补码)。 快捷方法:从右往左扫描原码,遇到第一个1之前(包括这个1)的位保持不变,其余位取反(符号位不变)。这实际上是“取反加1”的另一种实现。 例子:求 -5 的8位补码。
绝对值5的二进制:0000 0101
按位取反:1111 1010 (反码)
加1:1111 1011 (补码) 验证:计算 5 + (-5) 用补码:
language
5的补码: 0000 0101
-5的补码: 1111 1011
相加:
0000 0101
* 1111 1011
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 0000 0000 // 最高位进位
//注意,现在是8位表示,所以最高位的进位被丢弃(因为超出了表示范围),得到 0000 0000,即 0。完美!
优点:
唯一零:0 的补码只有一种表示:0000 0000。而 -0 呢?我们尝试用补码表示 -0:
0的原码:0000 0000
取反:1111 1111
加1:1 0000 0000(进位溢出,丢弃后为 0000 0000),所以和 +0 一样。
加减法统一:减法可以转换为加法,不需要额外的电路处理符号,也不需要循环进位。计算机的CPU只需一个加法器就能完成加减运算。
表示范围扩展:对于n位二进制,补码能表示的范围是 [-2^(n-1), 2^(n-1)-1]。例如8位补码范围是 -128 ~ 127。
🚨 易错点与难点:
混淆概念:必须清晰区分原码、反码、补码各自的定义和转换步骤,尤其在处理负数时。
补码的范围:对于n位有符号整数(如8位char),补码范围是 -2^(n-1) 到 2^(n-1)-1。例如8位是-128到127,而不是-127到127。-128没有原码和反码,只有补码(1000 0000)。
关于原码、反码、补码这一块的知识点内容比较多,老师希望大家可以耐心看完,其实这一块的知识点难度不大,老师写的比较多,主要还是希望给大家写的细致一点,让同学们更容易理解
二、进制转换
1、进制的概念
十进制:我们日常生活中使用的进制,有0-9十个数字,逢十进一。
二进制:计算机内部使用的进制,只有0和1两个数字,逢二进一。
八进制:曾经在计算机中常用,有0-7八个数字,逢八进一。
十六进制:在计算机中广泛使用,有0-9和A-F(或a-f)十六个数字,其中A-F表示10-15,逢十六进一。
2、进制转换方法
其他进制转换为十进制
方法:按权展开,求和 对于一个N进制的数,从右往左(从低位到高位),第i位(i从0开始)的权值为N^i,将该位数字乘以对应的权值,然后全部相加即可得到十进制数。
示例1:二进制转十进制
将二进制数(1101.01)₂转换为十进制。
整数部分:1×2³ + 1×2² + 0×2¹ + 1×2⁰ = 8 + 4 + 0 + 1 = 13 小数部分:0×2⁻¹ + 1×2⁻² = 0 + 0.25 = 0.25 所以,(1101.01)₂ = 13.25
示例2:十六进制转十进制
将十六进制数(2A.3)₁₆转换为十进制。
整数部分:2×16¹ + A×16⁰ = 2×16 + 10×1 = 32 + 10 = 42 小数部分:3×16⁻¹ = 3×0.0625 = 0.1875 所以,(2A.3)₁₆ = 42.1875
十进制转换为其他进制
十进制转换为其他进制时,需要将整数部分和小数部分分开处理。 整数部分:除基取余,逆序排列 用目标基数不断去除十进制整数,记下每次的余数,直到商为0,然后将余数逆序排列(最先得到的余数放在最低位)。 小数部分:乘基取整,顺序排列 用目标基数去乘十进制小数,记下每次乘积的整数部分(作为转换后的一位),然后将小数部分继续乘以基数,直到小数部分为0或达到所需精度。将取得的整数部分顺序排列(最先得到的整数放在最高位)。
示例:将十进制数25.375转换为二进制。
整数部分:25 25 ÷ 2 = 12 ... 1 12 ÷ 2 = 6 ... 0 6 ÷ 2 = 3 ... 0 3 ÷ 2 = 1 ... 1 1 ÷ 2 = 0 ... 1 逆序排列:11001,所以整数部分为(11001)₂。
小数部分:0.375 0.375 × 2 = 0.75 → 整数部分0,小数部分0.75 0.75 × 2 = 1.5 → 整数部分1,小数部分0.5 0.5 × 2 = 1.0 → 整数部分1,小数部分0.0 顺序排列:011,所以小数部分为(.011)₂。 因此,25.375 = (11001.011)₂。
注意: 如果题目要求将两个非十进制进行转换(如二进制转十六进制、八进制转十六进制),可以先转为十进制,在转为目标进制
三、位运算
核心拓展:逐位操作 这些运算直接对整数的二进制位进行操作,效率极高。
与运算(AND)- & 规则:两位都是1,结果才是1,否则为0
LANGUAGE
0110 (6)
& 1010 (10)
= 0010 (2)
或运算(OR)- | 规则:至少有一位是1,结果就是1
language
0110 (6)
1010 (10) = 1110 (14) 异或运算(XOR)- ^ 规则:两位不同为1,相同为0
LANGUAGE
0110 (6)
^ 1010 (10)
= 1100 (12)
非运算(NOT)- ~ 规则:0变1,1变0
language
~0110 (6)
--------
=1001 (9?不对!这里有个大坑!)
重点注意:这是最容易出错的地方! 在计算机中,整数是用补码存储的,取反操作是对补码取反。
移位 (<<, >>): 左移 (<<):高位丢弃,低位补0。等效于乘以2^n。 右移 (>>):对于有符号数,高位补符号位(算术右移);对于无符号数,高位补0(逻辑右移)。等效于除以2^n(向下取整)。
🚨 易错点与难点:
逻辑运算混淆:&&, || 是逻辑运算(结果true/false),&, | 是位运算(结果整数),不要用混。
移位 vs 乘除:移位虽然快,但注意溢出。左移可能把符号位移丢,导致负数变正。右移负数时,结果仍是负数(补符号位)。
优先级低:位运算的优先级通常低于算术运算(如+, -),最好多用括号保证顺序。
四、算法与描述
核心拓展:从思想到描述
枚举法:“暴力破解”。逐一尝试所有可能的情况,找出符合条件的解。
关键:
确定不重复、不遗漏的枚举范围;
设计高效的判断条件。 应用:百钱百鸡、找水仙花数。
模拟法:“情景再现”。按照题目描述的规则,一步步复现过程。
应用:时间推移、队列排队、游戏过程。
描述方法:
自然语言:易懂,但可能啰嗦、有歧义。 流程图:直观展示流程和控制逻辑。 伪代码:在自然语言和编程语言之间的完美平衡。忽略语法细节,用结构化的语言描述算法步骤,是设计算法的利器。
🚨 易错点与难点:
枚举效率低下:不加优化地枚举,范围过大导致程序“超时”。需要结合数学知识缩小枚举范围。
模拟过程遗漏细节:模拟题对边界条件(开始、结束、特殊情况)和状态更新的顺序要求极高,一步错,步步错。
伪代码写成了真代码:伪代码的重点是可读性,应避免纠结于int、++等具体语法,而应清晰表达“做什么”。
五、数据结构
在gesp3级中,还不涉及到太复杂的数据结构,最主要的考点还在聚焦在一维数组上,所以这方面的知识点老师主要还是以大家在考试中的一些易错点为主 定义 C++一维数组:“一排连续编号的储物柜”。存储同一类型的多个元素,通过下标(从0开始) 快速访问任何位置。特点:长度固定,访问极快。
初始化数组:往储物柜里放东西
language
// 方法1:全部初始化为0
int arr1[5] = {}; // {0, 0, 0, 0, 0}
int arr2[5] = {0}; // {0, 0, 0, 0, 0}
// 方法2:指定部分值,其余自动为0
int arr3[5] = {1, 2}; // {1, 2, 0, 0, 0}
// 方法3:完全初始化
int arr4[5] = {1, 2, 3, 4, 5};
// 方法4:不指定大小,编译器自动计算
int arr5[] = {1, 2, 3}; // 自动创建大小为3的数组
// 方法5:C++11开始的统一初始化
int arr6[]{1, 2, 3, 4}; // 省略等号,更现代
错误初始化示例
language
// 错误1:初始值太多
int arr1[3] = {1, 2, 3, 4}; // × 编译错误:too many initializers
// 错误2:大小为0
int arr2[0]; // × 标准C++不允许(某些编译器允许,但没意义)
// 错误3:用变量声明数组(VLA - 变长数组)
int n = 5;
int arr3[n]; // × C标准不支持(C99支持,C用vector代替)
计算数组大小
language
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 错误方法:sizeof(arr)返回的是总字节数
cout << sizeof(arr); // 输出40(10个int × 4字节)
// 正确方法:总字节数 ÷ 单个元素字节数
int size = sizeof(arr) / sizeof(arr[0]); // 40 ÷ 4 = 10
// 这样写遍历更安全:
for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) {
cout << arr[i] << " ";
}
🚨 易错点与难点:
数组下标越界:访问 a[n] 时,有效下标是 0 到 n-1。越界是危险且常见的错误。
language
假设数组:int arr[5] = {10, 20, 30, 40, 50};
物理位置: [0] [1] [2] [3] [4]
值: 10 20 30 40 50
人类习惯: 第1个 第2个 第3个 第4个 第5个
程序访问: arr[0] arr[1] arr[2] arr[3] arr[4]
注意:没有arr[5]!那是第6个位置,越界了!
六、字符串及其函数
核心拓展:字符串是字符数组
本质:在C++中,是字符数组;在Python中,是不可变的字符序列。因此支持很多列表/数组的操作(如索引、切片)。
常用操作:
1、获取字符串长度
language
string str = "Hello";
cout << str.size() << endl; // 5
cout << str.length() << endl; // 5
// 注意:size()和length()完全等价,都返回字符数(不包括结尾的空字符)
2、访问字符
language
string str = "Hello";
// 使用下标运算符(不检查边界)
char c1 = str[0]; // 'H'
// 使用at()成员函数(检查边界,越界抛出异常)
char c2 = str.at(1); // 'e'
// 遍历字符串
for (size_t i = 0; i < str.size(); ++i) {
cout << str[i];
}
// 使用范围for循环(C++11)
for (char ch : str) {
cout << ch;
}
3、字符串连接
language
string s1 = "Hello";
string s2 = "World";
// 使用+运算符
string s3 = s1 + " " + s2; // "Hello World"
// 使用append成员函数
s1.append(" ").append(s2); // s1变为"Hello World"
// 使用+=运算符
s1 += "!"; // s1变为"Hello World!"
4、字符串的搜索
language
string str = "Hello, world! Hello again.";
// 查找子串
size_t pos1 = str.find("Hello"); // 返回0
size_t pos2 = str.find("Hello", 1); // 从下标1开始查找,返回14
// 查找字符
size_t pos3 = str.find('o'); // 返回4
size_t pos4 = str.find('o', 5); // 从下标5开始查找,返回8
// 如果找不到,返回stdstringnpos
if (str.find("abc") == string::npos) {
cout << "Not found" << endl;
}
5、字符串的替换
language
string str = "Hello, world!";
// 从下标7开始,替换5个字符为"there"
str.replace(7, 5, "there"); // 结果为"Hello, there!"
// 也可以用迭代器范围替换
str.replace(str.begin(), str.begin()+5, "Hi"); // 结果为"Hi, there!"
6、字符串的大小写转换
language
#include <algorithm>
#include <cctype>
string str = "Hello, World!";
// 转换为大写
string upper = str;
transform(upper.begin(), upper.end(), upper.begin(), toupper);
// 结果为"HELLO, WORLD!"
// 转换为小写
string lower = str;
transform(lower.begin(), lower.end(), lower.begin(), tolower);
// 结果为"hello, world!"
终于看完啦,那么稍微休息一下,然后快去做几套真题练练手吧 https://htoj.com.cn/cpp/oj/training/detail?tid=22293424029440