以下是 GESP C++ 二级知识点的超详细解析,结合语法细节、典型例题、易错点及考试重点,帮助你彻底掌握核心内容。
一、C++ 语法进阶
1. 控制结构(复杂应用)
(1)循环嵌套
核心:外层循环控制“行”,内层循环控制“列”,适用于二维问题(如矩阵、图形打印)。
例题:打印杨辉三角前5行
杨辉三角规律:第 i
行(从0开始)有 i+1
个数,首尾为1,中间数等于上一行左右两数之和(a[i][j] = a[i-1][j-1] + a[i-1][j]
)。
#include <iostream>
using namespace std;
int main() {
int row = 5;
int a[row][row]; // 定义二维数组存储杨辉三角
for (int i = 0; i < row; i++) { // 外层循环:控制行
for (int j = 0; j <= i; j++) { // 内层循环:控制列(第i行有i+1列)
if (j == 0 || j == i) { // 首尾元素为1
a[i][j] = 1;
} else { // 中间元素由上一行计算
a[i][j] = a[i-1][j-1] + a[i-1][j];
}
cout << a[i][j] << " ";
}
cout << endl; // 每行结束换行
}
return 0;
}
输出结果:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
易错点:
- 二维数组索引从0开始,内层循环的终止条件应为
j <= i
(而非j < row
)。 - 计算
a[i][j]
时,需确保i-1 >= 0
且j-1 >= 0
(首尾元素直接赋值1可避免越界)。
(2)条件分支扩展
switch-case:适用于多分支选择,case
后必须为常量,且需 break
终止(否则会穿透)。
例题:成绩等级评定(输入分数,输出A/B/C/D)
#include <iostream>
using namespace std;
int main() {
int score;
cin >> score;
switch (score / 10) { // 分数除以10取整,得到十位数字
case 10: // 100分
case 9: cout << "A" << endl; break;
case 8: cout << "B" << endl; break;
case 7: cout << "C" << endl; break;
case 6: cout << "D" << endl; break;
default: cout << "E" << endl; // 60分以下
}
return 0;
}
注意:若 score
为100,score/10
结果为10,匹配 case 10
,但因无 break
会继续执行后续 case
(此处 case 10
后无代码,最终输出A)。
(3)跳转语句
- break:终止当前所在的最内层循环(或
switch
语句)。 - continue:跳过当前循环的剩余代码,直接进入下一次循环。
- goto:跳转到指定标签(
label:
),但易导致代码混乱,考试中不建议使用(仅在深层嵌套跳出时偶尔用)。
2. 数组(核心重点)
(1)一维数组
定义与初始化:
- 完全初始化:
int a[5] = {1,2,3,4,5};
(元素个数与声明一致)。 - 部分初始化:
int a[5] = {1,2};
(未初始化元素自动为0)。 - 省略长度:
int a[] = {1,2,3};
(长度由初始化列表自动确定)。
遍历数组:
int a[5] = {3,1,4,2,5};
// 正向遍历
for (int i = 0; i < 5; i++) {
cout << a[i] << " ";
}
// 反向遍历
for (int i = 4; i >= 0; i--) {
cout << a[i] << " ";
}
数组操作:
- 求最值:遍历数组,记录当前最大/最小值。
int max_val = a[0]; for (int i = 1; i < 5; i++) { if (a[i] > max_val) max_val = a[i]; }
- 求和/统计频次:累加或计数符合条件的元素。
int sum = 0; int count = 0; for (int i = 0; i < 5; i++) { sum += a[i]; if (a[i] % 2 == 0) count++; // 统计偶数个数 }
- 删除元素:需将后续元素前移,最后长度减1(数组长度固定,无法真正删除)。
int a[5] = {1,2,3,4,5}; int pos = 2; // 删除索引2的元素(值为3) for (int i = pos; i < 4; i++) { // 最后一个元素无需移动 a[i] = a[i+1]; } // 此时数组变为 [1,2,4,5,5],有效长度为4
易错点:
- 数组索引从0开始,最大索引为
长度-1
(如a[5]
的索引是0~4,访问a[5]
越界)。 - 部分初始化时,未显式赋值的元素为0(如
int a[3] = {1};
则a[0]=1, a[1]=0, a[2]=0
)。
(2)二维数组
定义与内存存储:
- 定义:
int b[3][4];
表示3行4列的数组(共12个元素)。 - 内存存储:按行连续存储(
b[0][0], b[0][1], ..., b[0][3], b[1][0], ...
)。
输入输出:
int b[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
// 输出所有元素(按行)
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << b[i][j] << "\t"; // \t 制表符对齐
}
cout << endl;
}
典型应用:
- 矩阵转置:行列互换(
b[i][j]
与b[j][i]
交换)。int b[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; int trans[3][3]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { trans[j][i] = b[i][j]; // 行变列,列变行 } }
- 方阵旋转:顺时针旋转90度(先转置,再反转每行)。
易错点:
- 二维数组定义时,第二维长度必须指定(如
int b[][4] = {{1,2},{3}};
合法,第一维可省略)。 - 访问
b[i][j]
时,i
范围是0~行数-1
,j
范围是0~列数-1
(越界会导致未定义行为)。
3. 指针与引用(基础掌握)
(1)指针
定义与取址:
- 指针变量存储变量的内存地址:
int a = 10; int *p = &a;
(p
是指针,&a
是a
的地址)。 - 解引用:
*p
表示指针指向的变量的值(cout << *p;
输出10)。
指针运算:
- 指针加减整数:
p++
等价于p = p + sizeof(int)
(移动一个int
类型的大小,4字节)。 - 指针与数组的关系:数组名是首元素的地址(
int a[5]; int *p = a;
等价于p = &a[0]
)。
指针作为函数参数:
void modify(int *x) {
*x = 100; // 修改指针指向的变量的值
}
int main() {
int a = 5;
modify(&a); // 传递a的地址
cout << a; // 输出100(原变量被修改)
return 0;
}
(2)引用
定义与绑定:
- 引用是变量的别名,必须初始化且不可重新绑定:
int a = 10; int &ref = a;
(ref
是a
的别名)。 - 对引用的修改直接影响原变量:
ref = 20;
等价于a = 20;
。
引用作为函数参数:
void swap(int &x, int &y) { // 引用传递
int temp = x;
x = y;
y = temp;
}
int main() {
int a = 3, b = 5;
swap(a, b); // 直接修改a和b的值
cout<< a << " " << b; // 输出5 3
return 0;
}
指针 vs 引用:
特性 | 指针 | 引用 |
---|---|---|
初始化 | 可后初始化(int *p; p = &a; ) |
必须初始化(int &ref = a; ) |
空值 | 可指向 nullptr |
不可为空 |
重新绑定 | 可重新指向其他变量 | 不可重新绑定 |
内存占用 | 占用内存(存储地址) | 不占用额外内存(是别名) |
二、函数(核心能力)
1. 函数基础
定义与调用:
- 函数声明(原型):告诉编译器函数的存在(避免编译错误)。
int add(int a, int b); // 函数声明(参数类型、返回值类型) int main() { int c = add(3, 5); // 函数调用 return 0; } int add(int a, int b) { // 函数定义 return a + b; }
返回值:
- 一个函数只能返回一个值,但可通过指针/引用参数返回多个值。
void calc(int a, int b, int &sum, int &diff) { // 通过引用返回和与差 sum = a + b; diff = a - b; }
2. 参数传递进阶
值传递:函数接收的是实参的副本,修改形参不影响实参。
指针传递:函数接收实参的地址,通过解引用修改原变量。
引用传递:函数接收实参的别名,直接修改原变量(更安全,无空指针风险)。
3. 递归函数(重点)
定义:函数直接或间接调用自身,必须包含终止条件。
例题:递归计算阶乘
long long factorial(int n) {
if (n == 0 || n == 1) { // 终止条件
return 1;
} else {
return n * factorial(n - 1); // 递归调用(n * (n-1)!)
}
}
例题:汉诺塔问题(递归经典)
问题描述:将A柱的n个盘子移动到C柱(借助B柱),每次只能移动1个盘子,大盘不能放在小盘上。
递归思路:
- 将n-1个盘子从A移到B(借助C)。
- 将第n个盘子从A移到C。
- 将n-1个盘子从B移到C(借助A)。
代码实现:
void hanoi(int n, char A, char B, char C) {
if (n == 1) { // 终止条件:仅1个盘子
cout << "移动盘子1从"<< A << "到"<< C << endl;
return;
}
hanoi(n-1, A, C, B); // 将n-1个盘子从A移到B(借助C)
cout << "移动盘子"<< n << "从"<< A << "到"<< C << endl;
hanoi(n-1, B, A, C); // 将n-1个盘子从B移到C(借助A)
}
int main() {
hanoi(3, 'A', 'B', 'C'); // 测试3层汉诺塔
return 0;
}
输出结果:
移动盘子1从A到C
移动盘子2从A到B
移动盘子1从C到B
移动盘子3从A到C
移动盘子1从B到A
移动盘子2从B到C
移动盘子1从A到C
三、结构体与联合体(基础应用)
1. 结构体(struct)
定义与成员访问:
struct Student {
string name;
int age;
double score;
};
int main() {
Student s1; // 定义结构体变量
s1.name = "张三";
s1.age = 15;
s1.score = 90.5;
Student *p = &s1; // 结构体指针
cout << p->name; // 用->访问成员(等价于 (*p).name)
return 0;
}
结构体数组:
Student stu[3] = {
{"张三", 15, 90.5},
{"李四", 16, 85.0},
{"王五", 15, 92.0}
};
// 遍历结构体数组
for (int i = 0; i < 3; i++) {
cout << stu[i].name << "的成绩:" << stu[i].score << endl;
}
结构体作为函数参数:
// 值传递:复制整个结构体(效率低)
void printStudent(Student s) {
cout << s.name << " " << s.age << endl;
}
// 引用传递:直接访问原结构体(效率高)
void modifyScore(Student &s, double newScore) {
s.score = newScore;
}
2. 联合体(union)
定义与特性:
union Data {
int i;
float f;
char c;
};
int main() {
Data d;
d.i = 10;
cout << d.f; // 输出乱码(i和f共享内存,修改i会影响f)
d.c = 'a';
cout << d.i; // 输出97('a'的ASCII码)
return 0;
}
注意:联合体的内存大小等于最大成员的大小(如上述 Data
大小为4字节,与 int
或 float
一致)。
四、算法基础(核心考查)
1. 枚举(穷举)算法
核心:遍历所有可能的解,筛选符合条件的。
优化技巧:缩小枚举范围,减少计算量。
例题:百钱买百鸡(公鸡5元/只,母鸡3元/只,小鸡1元/3只,用100元买100只鸡)
思路:枚举公鸡(x)、母鸡(y)数量,小鸡数量为 100-x-y
,需满足:
5x + 3y + (100-x-y)/3 = 100
,且 x,y,100-x-y
均为非负整数。
代码实现:
int main() {
for (int x = 0; x <= 20; x++) { // 公鸡最多20只(5*20=100元)
for (int y = 0; y <= 33; y++) { // 母鸡最多33只(3*33=99元)
int z = 100 - x - y; // 小鸡数量
if (z % 3 == 0 && 5*x + 3*y + z/3 == 100) {
cout << "公鸡:"<< x << " 母鸡:"<< y << " 小鸡:"<< z << endl;
}
}
}
return 0;
}
输出结果(3组解):
公鸡:0 母鸡:25 小鸡:75
公鸡:4 母鸡:18 小鸡:78
公鸡:8 母鸡:11 小鸡:81
2. 排序算法
(1)冒泡排序
原理:相邻元素比较,大的往后移(升序),每轮将最大值“冒”到末尾。
代码实现(升序):
void bubbleSort(int arr[], int len) {
for (int i = 0; i < len-1; i++) { // 轮数:len-1轮(最后一轮只剩1个元素)
bool swapped = false; // 优化:若某轮无交换,已有序
for (int j = 0; j < len-1-i; j++) { // 每轮比较次数:len-1-i次(末尾已排序)
if (arr[j] > arr[j+1]) {
swap(arr[j], arr[j+1]);
swapped = true;
}
}
if (!swapped) break; // 提前结束
}
}
(2)选择排序
原理:每轮找到未排序部分的最小值,与当前位置交换。
代码实现(升序):
void selectionSort(int arr[], int len) {
for (int i = 0; i < len-1; i++) {
int min_idx = i;
for (int j = i+1; j < len; j++) {
if (arr[j] < arr[min_idx]) {
min_idx = j; // 记录最小值索引
}
}
swap(arr[i], arr[min_idx]); // 交换当前位置与最小值
}
}
对比:
- 冒泡排序:稳定(相等元素顺序不变),每轮必交换。
- 选择排序:不稳定(可能打乱相等元素顺序),每轮仅交换一次。
3. 查找算法
(1)二分查找(折半查找)
前提:数组有序(升序或降序)。
原理:每次取中间元素比较,缩小查找范围。
代码实现(升序数组):
// 左闭右闭区间 [left, right]
int binarySearch(int arr[], int len, int target) {
int left = 0, right = len - 1;
while (left <= right) { // 终止条件:left > right(无元素可查)
int mid = left + (right - left)/2; // 防止整数溢出(等价于 (left+right)/2)
if (arr[mid] == target) {
return mid; // 找到目标,返回索引
} else if (arr[mid] < target) {
left = mid + 1; // 目标在右半部分
} else {
right = mid - 1; // 目标在左半部分
}
}
return -1; // 未找到
}
注意:若数组无序,需先排序(排序的时间复杂度为 O(nlogn)O(n\log n)O(nlogn),可能影响整体效率)。
五、文件操作(新增重点)
1. 文件打开与关闭
头文件:#include <fstream>
(输入输出文件流)。
文件流类:
ifstream
:输入文件流(读文件)。ofstream
:输出文件流(写文件)。
打开文件:
ifstream fin; // 定义输入文件流对象
fin.open("data.txt"); // 打开文件(默认模式:ios::in)
ofstream fout("info.txt", ios::out | ios::app); // 定义时直接打开(写模式+追加模式)
打开模式(常用):
模式标志 | 描述 |
---|---|
ios::in |
读模式(默认用于 ifstream ) |
ios::out |
写模式(默认用于 ofstream ,会覆盖原文件) |
ios::app |
追加模式(写数据到文件末尾) |
ios::trunc |
截断文件(写模式时清空原内容,默认与 ios::out 搭配) |
检查文件是否打开成功:
if (!fin.is_open()) {
cerr << "文件打开失败!" << endl;
return 1;
}
2. 文本文件读写
写入数据(ofstream
):
ofstream fout("student.txt");
fout << "姓名:张三" << endl;
fout << "年龄:15" << endl;
fout << "成绩:90.5" << endl;
fout.close(); // 必须关闭文件,确保数据写入磁盘
读取数据(ifstream
):
ifstream fin("student.txt");
string line;
while (getline(fin, line)) { // 读取一行(包含空格)
cout << line << endl;
}
// 或按空格分割读取(适用于无空格的数据)
int age;
double score;
fin >> age >> score; // 读取数值(跳过空格和换行)
fin.close();
注意事项:
- 读取数值时,
>>
会自动跳过空白符(空格、换行),适合结构化数据(如10 20 30
)。 - 读取字符串时,
>>
会以空格为分隔符,若需读取整行(含空格),用getline(fin, str)
。
六、编程规范与调试
1. 代码规范
- 命名:变量/函数名用小写字母+下划线(如
student_name
,calc_sum
);类名用大驼峰(如StudentInfo
)。 - 缩进:循环/条件语句的大括号单独成行,内部代码缩进4空格(或1Tab)。
- 注释:函数开头注释说明功能、参数、返回值;复杂逻辑添加行注释。
2. 常见错误排查
- 语法错误:编译器报错(如
error: expected ';' before '}' token
),根据提示定位行号,检查分号、括号是否匹配。 - 逻辑错误:程序运行但结果错误(如数组越界导致数据错误),通过
cout
输出中间变量排查。 - 运行时错误:访问空指针(
nullptr
)、文件不存在(打开失败),添加错误检查(如if (p == nullptr) { ... }
)。
总结
GESP C++ 二级考试重点考查语法应用能力和算法思维,需熟练掌握数组、函数、结构体、基础算法(排序、查找)及文件操作。学习时需多敲代码、多做练习(尤其是历年真题),注意细节(如数组越界、指针空值),并通过调试掌握排错技巧。