password
comment
type
status
date
slug
summary
tags
category
icon
第1讲 概述
- 程序与程序设计: 程序是为解决特定问题而编写的一系列有序指令,程序设计是将现实问题抽象为计算机能处理的形式,并用程序语言加以实现的过程。
- 算法与特性: 算法是解决问题的有限步骤集合,应具有输入、输出、有穷性、确定性和可行性五大特征。
- 从自然语言到程序: 一般过程为:问题描述 → 抽象建模 → 设计算法(伪代码/流程图)→ 选择语言(C/C++)→ 编码 → 编译、链接 → 调试与测试 → 维护与升级。
- 高级语言与机器语言的关系: 高级语言(如 C/C++)通过编译器(或解释器)转换为目标代码和可执行文件,最终由机器语言在硬件上执行。
- C/C++语言特点: 接近底层、执行效率高、表达能力强;支持结构化程序设计(C)和面向对象程序设计(C++);拥有丰富的标准库和良好的可移植性。
- 程序开发环境与基本流程: 典型流程为:编辑源文件(.c/.cpp)→ 编译生成目标文件(.obj/.o)→ 链接生成可执行文件(.exe)→ 运行和调试。
- 问题抽象与模块化思想: 复杂问题分解为若干功能模块,每个模块实现单一职责,有利于开发、调试和复用。
第2讲 数据的存储与表示
- 基本数据类型分类:
整型(
short、int、long、long long)、字符型(char)、浮点型(float、double)、布尔型(C++ 中的bool),以及派生类型(数组、指针、结构体、类等)。
- 数据在内存中的存储:
各类型占用字节数与实现有关,一般 32/64 位平台上:
int通常为 4 字节,double通常为 8 字节;数据以二进制补码形式存储整数,以 IEEE754 标准表示浮点数。
- 整数的表示与溢出问题: 有符号整数采用补码表示,最高位为符号位;当运算结果超出类型能表示的范围时发生溢出,表现为“回绕”,结果不可预测,应尽量避免。
- 浮点数精度与误差:
浮点运算存在舍入误差和精度损失,不能用
==直接比较两个浮点数是否相等,应使用“差值小于某个阈值”的方式判断。
- 字符与编码:
char本质上是一个整数类型,用于存放字符编码值;C/C++ 中字符常量用单引号表示,如'A',其在内存中是 ASCII 码或其他编码的数值。
- 常量与变量:
常量在程序运行期间不可改变,包括字面常量(
10、3.14、'a'、"abc")和const修饰的符号常量;变量则是可在运行过程中被赋值和修改的存储单元。
- 类型转换:
包括隐式转换(如
int自动提升为double)和显式转换(强制类型转换(int)x);类型提升遵循从低精度到高精度,从小范围到大范围的原则,防止精度丢失。
- 运算符与表达式: 算术运算符、关系运算符、逻辑运算符、赋值运算符、逗号运算符、条件运算符等构成表达式;应熟悉优先级和结合性,必要时使用括号提高可读性与正确性。
第3讲 选择结构
- 顺序结构与选择结构对比: 顺序结构按语句书写顺序执行;选择结构则根据条件表达式的值在多条路径中择一执行。
if语句基本形式:if (条件) 语句;、if (条件) 语句1; else 语句2;、if-else if-else多分支形式,用于实现二分及多分支逻辑。
- 复合语句与作用域:
if后若需执行多条语句,必须用{}组成复合语句;花括号内定义的变量作用域仅限于该块。
- 条件表达式与真值规则:
在 C/C++ 中,非零即真,零为假;表达式中可直接使用整型、指针等作为条件。逻辑运算符
&&、||具有短路特性。
switch语句: 适用于等值多分支选择,表达式一般是整数或枚举类型;各case标签必须为常量表达式,注意break的使用,否则会“贯穿”执行后续case。
if与switch的选用: 条件是区间或复杂逻辑时用if更灵活;条件是若干离散常量(如菜单编号、星期几)时用switch更清晰。
- 条件运算符
?:: 形式为条件 ? 表达式1 : 表达式2,常用于简单的二分选择;但过度嵌套会降低可读性,考试中需要掌握其结合方向和优先级。
- 典型易错点:
使用
if (a = b)误写为赋值表达式;switch中忘记写break导致逻辑错误;条件表达式书写不当导致优先级错误。
第4讲 循环结构
- 循环的基本思想: 对某段语句在满足条件的前提下重复执行,直至条件不再满足;适用于计数、累加、逐项处理等场景。
while循环: 先判断条件,再根据条件决定是否执行循环体;可能一次也不执行,适合“先判断后执行”的场景,如输入控制。
do...while循环: 先执行循环体,再判断条件;至少执行一次,适合“至少执行一次”的情形,如菜单式程序。
for循环: 形式为for(初始化; 条件; 更新),集成了初始化、条件判断和更新三部分,适合已知次数或循环变量明确的情形;三部分都可以省略,但中间分号不能省略。
- 循环控制变量与边界: 常用整型变量作为计数器;要特别注意初值、终值和步长,防止少循环或多循环一遍;对于数组下标,应确保不越界。
- 嵌套循环: 一重循环体内再包含循环,用于处理二维数据(如矩阵)或多层结构;注意内、外层变量命名、循环条件及时间复杂度。
- 典型应用: 累加和、阶乘、统计计数、乘法表打印、模式输出(如三角形、菱形图案)等。
- 易错点: 循环条件永远为真导致死循环;更新表达式遗漏或写错;对浮点变量作为循环控制变量带来精度问题。
第5讲 流程转移
break语句: 用于提前终止当前最近一层的switch或循环结构;执行后跳出该结构的剩余部分。
continue语句: 结束本次循环,直接进入下一次迭代:在for中会执行更新表达式后再判断条件,在while/do...while中则直接回到条件判断。
goto语句与标签: 通过标签在函数内部任意跳转;虽然语言允许,但会破坏结构化程序设计思想,一般不推荐使用,仅在特殊场合(如异常处理、复杂清理)中谨慎使用。
- 与异常处理的区别:
C 无内建异常机制,C++ 有
try-catch;goto不能跨函数跳转,不提供异常对象传递,只是简单的控制流跳转。
- 合理使用建议:
多数情况可通过重构循环结构、提取函数、使用
break/continue等方式替代goto;break与continue使用时,要搞清所在的循环层级和执行顺序。
- 易错点:
在多重循环中误认为
break能跳出所有循环;continue使用不当导致更新表达式未执行,引发死循环;滥用goto使程序逻辑混乱。
第6讲 复杂的循环
- 多重嵌套循环的设计: 对于二维数组或多层嵌套逻辑,外层控制“行”,内层控制“列”;要根据问题逻辑明确每一层循环的含义。
- 循环与条件综合:
在复杂筛选、统计中,经常在循环内部嵌套
if或switch;需注意逻辑条件的覆盖与互斥关系,防止遗漏和重复计算。
- 循环退出条件多样化: 除基于计数的退出方式(如循环 次),还可以基于状态(如输入特定字符结束)、基于收敛(误差小于阈值)等方式停止循环。
- 复杂模式打印与模拟: 如打印九九乘法表、菱形、对称图案、数字三角形等,需要结合嵌套循环与多重条件控制输出位置和内容。
- 时间复杂度意识: 嵌套循环常见 () 甚至更高复杂度,应根据数据规模分析是否可接受,必要时优化算法结构。
- 边界条件与健壮性: 在复杂循环中要特别注意循环变量在每条路径上的变化,避免某些分支中未更新循环变量导致死循环。
第7讲 函数
- 函数的基本概念: 函数是完成特定任务的独立程序模块,有利于代码重用、简化主程序、提高可读性与可维护性。
- 函数定义与声明: 定义包括返回类型、函数名、形式参数列表及函数体;函数声明(原型)通常放在文件开头或头文件中,向编译器说明函数的返回类型和参数类型。
- 值传递与形参与实参: 调用函数时,实参的值被复制给形参,函数内部对形参的修改不影响外部实参(C 中均为值传递;C++ 中可通过引用实现“引用传递”)。
- 返回值与
void函数: 非void函数必须通过return返回一个与返回类型兼容的值;void函数可以仅用于执行动作,也可以省略return。
- 作用域与存储类别:
局部变量在块内定义,离开该块后即失效;全局变量在所有函数外定义,整个文件可见;
static局部变量在函数间保留值,生命周期为整个程序运行期,但仅在函数内可见。
- 函数递归与分解: 复杂任务可拆分为多个函数,每个函数处理一部分;通过清晰的接口与参数传递完成模块间协作。
- 函数重载(C++): C++ 允许同名函数根据参数类型或个数不同加以区分,增强接口的统一性和灵活性;C 不支持重载。
- 易错点:
函数声明与定义的参数列表不一致;忽略返回值类型导致默认
int(在现代编译器中已不允许);递归函数缺少正确的结束条件。
第8讲 递归
- 递归的基本思想: 函数直接或间接调用自身,通过“规模减小”的方式求解问题;每次递归调用都在栈上建立新的调用帧。
- 递归的两个关键要素: 明确的递归终止条件(基本情形)与“向终止条件逼近”的递归式;否则容易造成无限递归,直至栈溢出。
- 典型递归问题: 阶乘()、斐波那契数列、二分查找、汉诺塔问题、树的遍历等,具有明显的“自身相似”结构。
- 递归与迭代的关系: 理论上大多数递归都可以改写为迭代;递归代码往往更清晰直观,但开销更大;迭代更高效、更节省栈空间。
- 递归深度与栈空间: 每次递归调用都需要保存返回地址、局部变量等信息,函数嵌套层数过深可能导致栈溢出,需要控制递归深度或改为迭代实现。
- 尾递归与优化: 若递归调用是函数中最后一个操作,则称为尾递归,某些编译器可进行尾递归优化,将其转化为迭代形式以节省栈空间。
- 易错点: 忘记编写或错误编写递归终止条件;每次递归调用未向终止条件前进(如参数未变化或变化方向错误);递归返回值未正确传递和组合。
第9讲 数组
- 数组的基本概念: 数组是在内存中按连续地址存放、类型相同的一组数据;通过下标(索引)访问元素,下标从 0 开始。
- 一维数组定义与初始化:
如
int a[10];定义 10 个int元素;可在定义时初始化:int a[5] = {1,2,3,4,5};或部分初始化,未显式初始化的元素自动置 0(静态/全局数组)。
- 二维数组与内存布局:
二维数组如
int a[3][4];可理解为“3 行 4 列”;在内存中按行优先存储,每行连续;访问时要注意下标顺序。
- 数组与循环:
数组遍历常用
for循环,根据长度依次访问各元素;对于二维数组通常采用嵌套循环(外层控制行,内层控制列)。
- 数组作为函数参数:
在函数形参中,数组会退化为指向首元素的指针,如
void f(int a[], int n);实质上是void f(int* a, int n);;函数内部对数组元素的修改会影响实参数组。
- 数组边界与安全性: 访问数组越界是 C/C++ 中常见且危险的错误,可能导致程序崩溃或不可预知行为;编译器通常无法完全检测,需要程序员自己保证下标合法。
- 字符串与字符数组:
C 风格字符串由字符数组加结尾的
'\0'组成;需预留一个字节存放结束符。
- 易错点: 将数组名当作可修改的左值(C 中不允许给数组名赋值);数组长度与循环边界不匹配;函数传参时误以为数组是“值传递”。
第10讲 查找和排序
- 顺序查找: 从数组起始位置逐个比较目标值,直到找到或遍历完毕;适用于无序数组,实现简单但效率较低,时间复杂度为 。
- 二分查找: 适用于有序数组,通过不断折半缩小查找区间;每次比较后舍弃一半元素,时间复杂度为 ,效率远高于顺序查找。
- 冒泡排序: 通过多次相邻元素比较和交换,将较大(或较小)元素逐步“冒泡”到一端;算法实现简单,但平均和最坏时间复杂度为 。
- 选择排序: 每一趟从未排序部分中选择最小(或最大)元素,放到已排序部分末尾;交换次数较少,但时间复杂度同样为 。
- 插入排序: 维护一个有序区,将未排序元素逐一插入到有序区合适位置;对“基本有序”的数据表现优良。
- 排序稳定性概念: 若排序后相等关键字的记录相对位置不变,则该排序算法是稳定的;理解稳定性对某些应用场景(如多关键字排序)很重要。
- 复杂度与适用场景比较: 测试中通常需要掌握简单排序算法的思想、步骤和复杂度;高阶排序(快排、归并等)可作了解,重点在思想而非代码细节。
- 易错点: 循环边界写错导致访问越界或少(多)比较;交换元素时临时变量使用错误;二分查找中中间位置和区间更新条件写错。
第11讲 字符串和字符数组
- C 风格字符串:
本质是以
'\0'结束的char数组;字符数组的长度应至少比实际字符数多 1,用于存放结束符。
- 常见库函数(
<cstring>/<string.h>): 如strlen(求长度,不含'\0')、strcpy/strncpy(复制)、strcat/strncat(连接)、strcmp(比较)等;使用时要保证目标数组空间足够,防止溢出。
- 输入输出:
C 中常用
scanf("%s", s);和gets/ fgets进行输入,printf输出;C++ 中可用cin >> s;和getline(cin, s)等;scanf("%s")在遇到空白字符时终止读取。
- 字符处理:
字符本质上是整数,可进行算术运算;常用的分类函数(
isalpha、isdigit、isspace等)和大小写转换函数(toupper、tolower)可以简化字符判断与处理。
- 字符串遍历与操作:
常通过下标或指针遍历到
'\0'为止;可在遍历中统计长度、统计字符种类、完成替换、过滤等操作。
- C++
std::string:std::string是 C++ 封装的字符串类,支持动态长度、运算符重载(如+、==、<)、成员函数(size()、substr()、find()等),相对更安全、易用。
- 易错点:
忘记预留
'\0'的空间;使用strcpy等函数时目标数组空间不足;字符常量与字符串常量混用('a'与"a")。
第12讲 数组习题课
- 一维数组应用: 求最大值/最小值及其下标、求和与平均值、统计满足条件的元素个数、去重、合并两个有序数组等。
- 二维数组应用: 矩阵加法、乘法、转置、求对角线元素和、行列统计等;重点在于正确使用两层循环并确保下标范围合法。
- 数组与查找、排序综合: 将前几讲查找和排序方法应用到数组上,手写实现并能够根据题意合理选择方法和循环结构。
- 数组与函数配合: 会将数组作为参数传入函数,在函数中进行统计、排序、查找等操作,熟悉“数组退化为指针”的现象及其影响。
- 典型综合题: 学生成绩管理(录入、平均分、最高分、及格率)、简单数据分析(频数统计、直方图数据准备)等。
- 易错点回顾: 下标越界、未初始化数组、错误假设数组长度;排序和查找函数参数使用不当。
第13讲 指针
- 指针的基本概念:
指针变量存放的是“地址”,即内存单元的编号;类型说明了通过该指针访问的对象类型,如
int*指向int,double*指向double。
- 取地址与解引用运算:
&用于取得变量的地址,如&a; 解引用运算符用于访问指针所指向的对象,如p表示指针p指向的变量。
- 指针与内存模型: 程序运行时内存通常划分为代码区、全局/静态区、栈区和堆区;指针可以指向上述任一区域中的对象,但不得指向无效或已释放区域。
- 空指针与野指针:
空指针(通常为
nullptr或NULL)不指向任何有效对象,使用前应判断是否为空;野指针是未初始化或悬空(指向已释放内存)的指针,访问将导致未定义行为。
- 指针运算: 指针可以做加减整数运算(按所指类型的大小偏移);两个指向同一数组的指针可以相减得到元素间距离。
- 指针与
const:const int* p表示“指向常量的指针”,不能通过p修改所指对象;int* const p表示“常量指针”,指针本身不可改变但可修改所指对象;二者可以组合。
- 二级指针与指针数组:
int** p表示“指向int*的指针”;指针数组如int* a[10];表示元素为指针的数组。
- 易错点:
忘记初始化指针;对空指针或野指针解引用;滥用指针运算导致越界;误解
const与指针的关系。
第14讲 指针和数组
- 数组名与指针:
在大多数表达式中,数组名会自动退化为指向首元素的指针,如
int a[10]; int* p = a;等价于&a[0];但sizeof(a)得到的是整个数组大小而非指针大小。
- 指针访问数组元素:
(a + i)等价于a[i],(p + i)等价于p[i];熟悉这些等价关系有助于理解底层实现和指针运算。
- 数组指针与指针数组:
数组指针如
int (*p)[4];指向一个“含 4 个int的数组”;指针数组如int* q[3];是一个数组,其元素为int*。
- 二维数组与指针:
二维数组名在表达式中会退化为指向“行数组”的指针,可用数组指针进行处理;函数参数常写为
int a[][N]或int (*a)[N]。
- 指针与动态数组:
通过
new(C++)或malloc(C)在堆区动态申请数组空间,用指针进行管理,需配对delete[]或free释放。
- 易错点:
混淆数组指针与指针数组;使用
sizeof时误认为得到数组长度,实为指针大小;对动态申请的数组忘记释放或重复释放。
第15讲 指针和字符串
- 指向字符和字符串的指针:
char* p = "hello";在旧代码中很常见,但字符串字面量通常存放在只读区域,不宜修改;更安全的做法是使用const char*或char 数组存放可修改字符串。
- 字符串遍历的指针写法:
如
for (char* p = s; *p != '\0'; ++p) { ... };指针法与下标法等价,但更贴近底层实现。
- 字符串函数与指针:
如
strlen、strcpy、strcat、strcmp等函数的参数实质上都是char*或const char*,通过指针操作字符串。
- 指针数组与多字符串处理:
通过
char* s[10];或const char* s= {"one", "two", "three"};来保存多个字符串的指针,便于排序、查找等操作。
- C++ 中的
string与 C 字符串互转:std::string可通过c_str()获得const char*,与 C 函数交互;从 C 字符串构造string也非常方便,有利于平滑过渡。
- 易错点:
对字符串字面量执行写操作(未使用
const修饰);忘记在字符数组末尾添加'\0';字符串复制时目标空间不足。
第16讲 结构体与共用体
- 结构体的定义与使用:
使用
struct定义由若干不同类型成员组成的聚合数据类型,如:struct Student { int id; char name[20]; double score; };,可通过点运算符访问成员:stu.score。
- 结构体数组与结构体指针:
可定义
Student s[100];保存多名学生信息,通过循环遍历实现查找、排序、统计等;Student* p = &s[0];可用指针访问结构体成员p->score。
- 结构体作为函数参数与返回值: 可按值传递结构体(会有拷贝开销),也可传递指向结构体的指针以提高效率;C++ 中可以安全地以值方式返回结构体/类对象。
- 共用体(联合)的概念:
共用体
union的所有成员共享同一片内存空间,大小等于最大成员的大小;任意时刻只有一个成员是“有意义的”,适用于节省内存或表示多种解释方式。
- 结构体与共用体的区别: 结构体成员各自拥有独立存储空间,大小为各成员大小之和(加上必要的对齐);共用体成员重叠存储,大小为最大成员大小。
- 位段: 在结构体中可定义按位分配的成员,用于紧凑表示标志位,常用于嵌入式场景。
- 易错点: 忘记初始化结构体;错误地在共用体中同时使用多个成员;对齐和大小计算不清楚而导致误判结构体大小。
第17讲 类和对象
- 面向对象基本概念: 类是对一类对象的抽象,封装了数据(成员变量)和操作(成员函数);对象是类的实例。
- 封装与访问控制:
使用
public、private、protected控制成员访问权限:public对外公开,private仅在类内及友元中可用,protected在类、友元和派生类中可访问;通过封装隐藏实现细节,对外暴露接口。
- 类的定义与成员函数:
在 C++ 中
class或struct都可定义类,区别在默认访问控制不同(class为private,struct为public);成员函数可以在类外定义时加上作用域限定ClassName::func。
- 对象的创建与使用:
可在栈上定义对象,如
Student s;,也可在堆上通过new创建,如Student* ps = new Student;,并在使用完成后delete。
- 成员函数与普通函数的区别:
成员函数隐式地接收一个指向当前对象的指针(
this指针),可以访问对象的成员;普通函数没有this。
- 对象数组与对象指针:
可以定义数组
Student s[100];管理多对象;可以通过指针遍历数组并调用成员函数,如p->print();。
- 易错点: 忽略访问控制导致数据暴露;在类外定义成员函数时忘记加作用域限定;动态创建对象后忘记释放内存。
第18讲 构造函数与析构函数
- 构造函数的作用与特性: 构造函数在对象创建时自动调用,用于初始化对象成员;名称与类名相同,无返回类型;可重载(参数列表不同)。
- 默认构造函数与带参构造: 若未显式定义构造函数,编译器会生成默认构造函数(有时仅执行“什么都不做”的初始化);自定义带参构造可便于在创建对象时直接设定成员初值。
- 初始化列表:
推荐在构造函数的初始化列表中对成员进行初始化,如
ClassName(): x(0), y(1) {};对于const成员、引用成员、基类成员和成员对象,必须在初始化列表中初始化。
- 析构函数:
在对象生命周期结束时自动调用,用于释放资源(内存、文件句柄、锁等);名称为
~ClassName,无参数、无返回类型,不能重载。
- 对象生命周期:
栈上对象在离开作用域时自动调用析构函数;堆上对象需通过
delete主动释放以触发析构;全局/静态对象在程序结束时析构。
- 构造/析构的调用顺序: 对于含有成员对象的类,构造时先构造成员再执行自己的构造函数体;析构时顺序相反,先执行自身析构函数体,再依次析构成员。
- 易错点: 在构造函数中遗漏重要成员初始化;未在析构函数中释放动态申请的资源导致内存泄漏;误以为可以指定析构函数的参数。
第19讲 C++语法增强–引用与const
- 引用的概念:
引用是对象的别名,须在定义时立即绑定到某个有效对象,之后不可再绑定到其他对象;语法如
int a; int& r = a;。
- 引用与指针的比较: 引用必须初始化,且不能为空;使用时语法简单(像普通变量),无需使用解引用运算符;实参可通过引用形参在函数内部被直接修改。
- 引用在函数参数与返回值中的应用: 通过引用参数避免值传递的拷贝开销,并允许函数修改实参;返回引用可用于支持链式赋值和操作符重载,需要确保返回的引用指向有效对象。
const的用途: 修饰变量表示只读,修饰指针或引用时可限定修改权限;可以提高代码的自文档性和安全性。
const与成员函数: 在成员函数末尾加const表示该函数不会修改任何非mutable成员;编译器对const成员函数进行额外检查,有利于接口设计。
const对象与常量成员:const对象只能调用const成员函数;类中的const成员必须在构造函数初始化列表中初始化。
- 易错点:
忽视
const导致不必要的修改能力;对引用的生命周期与绑定对象理解不清;返回局部变量的引用或指针导致悬空引用。
第20讲 C++语法增强–内存管理与拷贝构造
- 动态内存分配(
new/delete): 使用new Type在堆上申请单个对象,new Type[n]申请数组;对应使用delete和delete[]释放,必须配对一致。
- 资源管理与 RAII 思想: 通过对象的构造函数获取资源,在析构函数中释放资源,使资源生命周期与对象生命周期绑定;避免内存泄漏和资源泄露。
- 拷贝构造函数:
形式为
ClassName(const ClassName& other),在对象通过拷贝方式初始化时调用,如ClassName b = a;或按值传参/返回对象时;若未自定义,编译器生成按成员拷贝的默认拷贝构造。
- 浅拷贝与深拷贝: 浅拷贝仅复制指针值,会导致多个对象共享同一块内存,析构时可能重复释放;深拷贝会分配新的内存并复制内容,使各对象拥有自己的资源。
- 赋值运算符重载与拷贝控制三法则:
若类中需要自定义拷贝构造函数和析构函数,通常也需要重载赋值运算符
operator=以实现正确的资源管理(“三/五法则”思想)。
- 内存泄漏与悬空指针:
申请后未释放形成泄漏;释放后仍然使用或保留该指针形成悬空指针,可将指针在释放后置为
nullptr以减少错误。
- 易错点:
使用
new[]却用delete释放;在拷贝构造中忘记分配新空间;未定义拷贝控制导致默认浅拷贝引发资源问题。
第21讲 构建复合对象:组合、静态成员与常量安全
- 组合(“
has-a” 关系): 一个类可以将其他类的对象作为成员,从而构建更加复杂的复合对象;组合关系体现为“一种对象拥有其他对象”。
- 成员对象的构造与析构顺序: 构造时先按声明顺序构造成员对象,再执行本类构造函数体;析构时先执行本类析构,再按与构造相反的顺序析构成员对象。
- 静态成员变量:
静态成员为所有对象共享,在类外需要进行一次定义与初始化;可通过
ClassName::member访问,也可通过对象访问。
- 静态成员函数:
无
this指针,只能访问静态成员;可用于实现“与对象无关”的类级功能(如计数器、工厂方法等)。
- 常量成员与常量对象:
类内
const成员必须在构造函数初始化列表中初始化;使用const ClassName obj;声明的常量对象只能调用const成员函数。
- 接口的“常量安全”:
通过合适使用
const修饰成员函数和参数,保证不应被修改的数据不会被误修改,提高类设计的健壮性。
- 易错点: 忘记在类外定义静态成员导致链接错误;在初始化列表中初始化顺序与声明顺序不一致(实际仍按声明顺序执行);未区分成员对象构造顺序与初始化列表的书写顺序。
第22讲 建立类层次:继承与访问控制
- 继承基本概念:
通过关键字
public、protected或private继承基类,派生类自动获得基类成员(除构造、析构、赋值等);体现“is-a” 关系。
- 继承方式与访问控制:
公有继承使得基类的
public和protected成员在派生类中保持相对访问级别,对外接口也保持兼容,是最常用方式;protected和private继承常用于实现和封装上的考虑。
- 父类与子类对象间的转换: 派生类对象可隐式转换为基类对象或基类引用/指针(向上转型),反之需显式转换且风险较大;多态通常通过基类指针或引用来实现。
- 构造和析构中的继承: 创建派生类对象时,会先构造基类部分,再构造本类成员;析构时顺序相反;可以在派生类构造函数初始化列表中调用特定基类构造函数。
- 名字隐藏与作用域:
派生类中若定义与基类同名的成员(包括函数),默认会隐藏基类的同名成员,可通过作用域运算符
Base::func调用基类版本。
- 多重继承与菱形继承: C++ 支持一个类从多个基类继承,但会带来命名冲突和菱形继承中的“二义性”,可通过虚继承解决共享基类的重复问题。
- 易错点:
误用继承表达“
has-a”关系(本应使用组合);忽略继承方式导致接口访问权限不符合预期;在构造派生类时忘记正确初始化基类部分。
第23讲 从继承到多态:派生类机制与多文件组织
- 静态绑定与动态绑定:
默认情况下,成员函数调用在编译期就已确定(静态绑定);通过
virtual关键字可启用运行期多态,即动态绑定,根据对象实际类型选择函数版本。
- 虚函数与覆盖:
在基类中将成员函数声明为
virtual,派生类中用相同函数签名实现覆盖;通过基类指针或引用调用时,会根据对象实际类型调用合适版本。
- 虚析构函数:
若基类会被作为多态基使用(存在通过基类指针删除派生类对象的情况),必须将析构函数设为
virtual,确保删除时能正确调用派生类析构,避免资源泄漏。
- 多文件组织与模块化:
一般将类的声明放在头文件(
.h/.hpp)中,将实现放在源文件(.cpp)中;其他文件通过#include头文件进行使用。
- 头文件保护与依赖管理:
使用头文件宏或
#pragma once防止重复包含;尽量在头文件中使用前置声明减少依赖,避免不必要的编译开销和循环依赖。
- 编译与链接: 多个源文件分别编译生成目标文件,再统一链接;若忘记将实现文件加入工程,可能出现链接错误。
- 易错点:
忘记在基类中添加
virtual导致多态失效;未将析构函数设为虚函数就通过基类指针删除派生类对象;头文件中包含实现代码不当导致重复定义。
第24讲 多态机制与实践:虚函数、抽象类与多态设计
- 运行时多态的实现机制(概念): 典型实现是通过虚函数表(vtable)与虚指针(vptr);每个含有虚函数的类都有一张虚表,包含各虚函数入口地址,对象中含有指向该表的指针。
- 抽象类与纯虚函数:
包含至少一个纯虚函数(
virtual void f() = 0;)的类称为抽象类,不能直接实例化,只能作为接口或基类使用;派生类必须实现所有纯虚函数方能实例化。
- 接口分离与职责单一: 抽象类应只包含与某种行为相关的接口,不掺杂无关职责,以利于扩展和维护;通过多态设计可在无需修改现有代码的情况下添加新派生类。
- 多态在设计中的应用:
如图形系统中基类
Shape提供draw()虚函数,派生类Circle、Rectangle各自实现;调用方只需通过基类指针容器逐个调用draw()即可实现对不同图形的统一操作。
- 向下转型与
dynamic_cast: 当需要从基类指针安全地转换到派生类指针时,可使用dynamic_cast,在转换失败时返回nullptr,避免不安全的强制类型转换。
- 多态与资源管理:
配合虚析构函数与智能指针(
std::unique_ptr、std::shared_ptr)使用,可以简化多态对象的生命周期管理。
- 易错点: 将含有纯虚函数的抽象类当作可实例化类;在派生类中改变虚函数参数列表导致重载而非覆盖;使用 C 风格强制转换破坏类型安全。
第25讲 友元与运算符重载
- 友元函数与友元类:
通过在类内使用
friend声明,可以将某个函数或类设为友元,使其访问本类的private和protected成员;友元不受访问控制限制,但会打破封装,应慎用。
- 运算符重载的目的:
使自定义类型支持类似内置类型的运算符语法,提高代码可读性和直观性,例如为复数类型重载
+、-、*、<<等。
- 成员运算符与非成员运算符:
一般二元运算符可作为成员或非成员重载;当需要支持隐式类型转换或对称性(如
a + b与b + a)时,经常采用非成员函数并配合友元。
- 常用运算符重载:
算术运算符(
+、-、*、/)、关系运算符(==、<等)、赋值运算符(=)、下标运算符(operator[])、函数调用运算符(operator())、输入输出运算符(>>、<<)等。
- 输入输出运算符重载:
通常以非成员形式并返回流引用,如:
ostream& operator<<(ostream& os, const ClassName& obj);,可支持链式输出。
- 重载时的
const使用: 对不修改对象状态的运算符(如比较运算符、读操作)应声明为const成员函数;参数和返回值的const使用也应符合语义。
- 易错点:
将不应重载的运算符(如
.、?:等)误认为可重载;重载运算符时违反运算习惯(如使==不具有对称性);重载输入输出运算符时忘记返回流引用。
第26讲 函数模板与类模板
- 模板的基本思想: 模板支持参数化类型编程,使同一份代码能够适用于多种数据类型;编译器在使用模板时进行“实例化”,生成具体类型版本。
- 函数模板:
使用
template <typename T>声明类型参数,通过T编写通用函数,如通用swap、max函数;调用时可显式指定类型参数,也可由编译器进行类型推导。
- 类模板:
类定义中使用模板参数,如
template <typename T> class Vector { ... };;使用时需指定具体类型参数,如Vector<int> v;。
- 模板参数种类:
包括类型参数(
typename T/class T)、非类型参数(如整型常量)、模板模板参数(较高级);基本复习中重点掌握类型参数。
- 模板与代码膨胀: 对不同类型的实参实例化会生成多份代码,可能导致可执行文件大小增加;但通过模板能显著提升代码复用和类型安全性。
- 标准模板库(STL)的基础:
STL 大量依赖模板技术构建通用容器、算法和迭代器,如
std::vector<int>、std::list<std::string>等。
- 易错点: 模板定义与实现放在不同源文件中导致链接错误(模板一般需写在头文件中);模板函数与普通函数存在重载/特化时的匹配规则理解不清。
第27讲 运算符重载深入与实践
- 复合赋值运算符重载:
如
operator+=、operator-=等,通常以成员函数形式实现,并返回this的引用,以支持链式操作。
- 前置与后置自增自减运算符:
通过函数签名区分:
Type& operator++();为前置形式,Type operator++(int);为后置形式;后置形式一般返回修改前的副本,实现时注意效率问题。
- 关系运算符与排序:
重载
operator<、operator==等,可使对象在 STL 容器中进行排序、查找;应遵守自反性、对称性、传递性等数学属性。
- 下标运算符与函数调用运算符:
operator[]常用于自定义“数组”或“容器”类;operator()可将对象“函数化”,如仿函数(函数对象)在 STL 算法中的使用。
- 类型转换运算符重载:
可重载为成员函数
operator Type()实现向其他类型的隐式转换;应谨慎使用,避免产生不明确的转换路径。
- 与模板结合的重载: 模板类中的运算符重载常与类型参数共同编写,以支持多类型对象的操作;要注意模板与重载解析之间的相互影响。
- 易错点: 自增自减运算符实现逻辑反;在重载比较运算符时逻辑不自洽;返回局部变量的引用或指针。
第28讲 STL容器与迭代器
- STL 基本组成:
包含容器(如
vector、list、deque、set、map等)、算法(如sort、find、for_each等)、迭代器和函数对象,是 C++ 标准库的重要组成部分。
- 顺序容器:
vector提供动态数组功能,支持随机访问,尾部插入/删除较高效;list为双向链表,适合频繁中间插入/删除但不支持随机访问。
- 关联容器:
set、multiset、map、multimap基于平衡树实现,按键有序存储;插入、查找、删除平均时间复杂度为 。
- 迭代器概念: 迭代器是对容器元素位置的抽象,可视作“广义指针”;不同容器提供不同能力的迭代器(随机访问、双向、前向等)。
- 泛型算法的使用:
STL 算法通过迭代器与容器解耦,如使用
sort(v.begin(), v.end());排序,find查找等;要求迭代器满足相应的概念。
auto与范围for循环: 使用auto推断迭代器类型,配合范围for循环(for (auto& x : v)) 可简化遍历语法。
- 易错点:
对迭代器失效问题认识不足(如在
vector中插入元素后旧迭代器失效);在错误容器上使用不适当算法(如对list使用sort,应调用成员函数list::sort)。
第29讲 文件操作
- 文件与流的概念:
文件是存储在外部介质上的数据集合;C++ 使用流(
ifstream、ofstream、fstream等)来抽象输入输出设备和文件。
- 打开与关闭文件:
使用
fstream类的open成员函数或构造函数打开文件,需指定文件名和打开模式(如ios::in、ios::out、ios::binary等);使用close关闭文件,或通过析构自动关闭。
- 文本文件与二进制文件: 文本文件按字符序列存储,具有可读性;二进制文件按原始字节序列存储,更适合高效存取复杂数据结构。
- 读写操作:
文本模式下可使用流插入运算符
<<与提取运算符>>,或getline读取整行;二进制模式可使用read与write,指定缓冲区和字节数。
- 文件状态与错误检测:
通过
is_open判断文件是否成功打开,通过eof、fail、bad等函数检测流状态;读写出错时应及时采取措施或报错。
- 文件位置指针:
使用
seekg(输入)和seekp(输出)移动文件读写位置,配合tellg、tellp获取当前位置,实现随机访问。
- 易错点: 忽略文件打开失败的情况直接进行读写;混用文本模式与二进制模式导致读取结果异常;未正确刷新缓冲区导致数据未及时写入文件。
《C/C++语言程序设计》知识梳理表
模块名称 | 包含章节 | 核心知识点(简要) | 典型案例(名称) | 易错点和高频考点 |
模块一:程序基础与流程控制 | 第1讲 概述
第2讲 数据的存储与表示
第3讲 选择结构
第4讲 循环结构
第5讲 流程转移
第6讲 复杂的循环 | 程序与算法概念;数据类型与存储表示;表达式与运算符;顺序、选择( if、switch)与循环(while、for、do … while)结构;break、continue、goto;嵌套与复杂循环设计 | 学生成绩统计(最大值、平均值、及格率);九九乘法表;菜单式程序;条件分支计算器 | 赋值与比较混淆( if(a=b));switch 缺少 break;循环边界错误导致少/多循环或死循环;浮点比较使用 ==;对 break/continue 作用范围理解错误 |
模块二:函数与递归 | 第7讲 函数
第8讲 递归 | 函数定义与声明;形参与实参、值传递;作用域与存储类别;函数重载(C++);递归思想、终止条件与递归式;递归与迭代关系 | 阶乘与斐波那契数;递归实现二分查找;汉诺塔问题;函数分解的学生成绩管理 | 函数原型与定义不一致;未正确返回值;递归终止条件缺失或错误导致栈溢出;对局部变量、静态变量生命周期理解不清 |
模块三:数组与字符串 | 第9讲 数组
第10讲 查找和排序
第11讲 字符串和字符数组
第12讲 数组习题课 | 一维与二维数组定义、初始化、遍历;数组参数与指针退化;顺序查找、二分查找;冒泡、选择、插入等排序方法;C 字符串与字符数组,字符串函数;综合数组应用 | 学生成绩数组的统计与排序;数组中最大/最小值及下标;二分查找实现;字符串长度统计与大小写转换 | 数组越界访问;数组长度与循环边界不匹配;二分查找区间更新错误;排序过程中交换或循环控制错误;忘记为字符串预留 '\0' 空间 |
模块四:指针与结构体 | 第13讲 指针
第14讲 指针和数组
第15讲 指针和字符串
第16讲 结构体与共用体 | 指针定义、取地址与解引用;指针运算与空/野指针;数组名与指针、数组指针与指针数组;指针与字符串操作;结构体定义、结构体数组、结构体指针;共用体特性与内存共享 | 使用指针遍历数组和字符串;学生结构体数组管理系统;共用体实现多种数据解释 | 指针未初始化或悬空访问;混淆数组指针和指针数组;对字符串字面量写操作;结构体和共用体的内存布局与大小判断错误 |
模块五:面向对象基础 | 第17讲 类和对象
第18讲 构造函数与析构函数
第19讲 C++语法增强-引用与 const | 类与对象概念;封装与访问控制;成员函数与 this 指针;构造函数与析构函数,初始化列表;引用与引用参数;const 对象、const 成员与 const 成员函数 | 简单类(如复数类、分数类、学生类)的设计与使用;构造/析构日志输出;通过引用参数实现交换函数 | 构造函数/析构函数声明与实现不规范;未使用初始化列表初始化 const 成员或引用成员;引用返回/参数绑定临时或局部对象;const 成员函数缺失导致 const 对象无法调用 |
模块六:面向对象高级 | 第20讲 内存管理与拷贝构造
第21讲 构建复合对象
第22讲 继承与访问控制
第23讲 多态机制
第24讲 多态实践 | new/delete 与动态内存管理;拷贝构造、赋值运算符与深/浅拷贝;组合与成员对象;静态成员与类级数据;继承方式与访问级别;虚函数、虚析构与运行时多态;抽象类与纯虚函数;多文件组织 | 动态数组封装类;含有成员对象的复合类;图形类层次( Shape/Circle/Rectangle)及多态绘制;基类指针数组存放派生类对象 | new/delete 与 new[]/delete[] 混用;未自定义拷贝构造导致浅拷贝问题;错误理解继承方式影响访问控制;基类析构函数未设为 virtual;虚函数签名不匹配导致未实际覆盖 |
模块七:运算符重载与模板 | 第25讲 友元与运算符重载
第26讲 函数模板与类模板
第27讲 运算符重载深入 | 友元函数与友元类;常用运算符重载(算术、关系、下标、输入输出等);前置/后置自增自减;复合赋值运算符;函数模板与类模板的定义与实例化;模板参数与类型推导 | 复数类的加减乘除与输出;自定义容器/矩阵类的下标与算术运算;通用 swap/max 函数模板;简单类模板(如栈 Stack<T>) | 误以为所有运算符都可重载;运算符重载逻辑违反直觉(如比较不具有自反性);返回局部对象引用;模板定义与实现分离导致链接错误 |
模块八:STL与文件操作 | 第28讲 STL容器与迭代器
第29讲 文件操作 | STL 容器( vector、list、set、map 等)及其特点;迭代器与泛型算法(sort、find 等);C++ 文件流 ifstream/ofstream/fstream;文本与二进制文件读写;文件状态检测与随机访问 | 使用 vector 存放和处理成绩;用 map 统计单词频率;学生信息文件读写与查询;简单配置文件解析 | 迭代器失效(插入、删除后继续使用旧迭代器);错误选择容器与算法组合;文件打开失败后继续读写;混用文本与二进制模式导致数据错乱 |
模块九:综合与复习 | 第30讲 银行账户管理系统
第31讲 思维热身
第32讲 总复习 | 综合运用函数、数组、指针、结构体/类、继承与多态、STL与文件操作等知识构建完整程序;需求分析与模块划分;接口设计与错误处理;代码风格与调试技巧 | 银行账户管理系统(开户、存取款、查询、持久化);多态图形绘制或几何计算系统;综合试题训练与知识点串联 | 模块接口设计不清晰导致耦合度过高;数据结构选择不当影响效率;资源管理和异常情况处理不完善;复习中知识点孤立、未形成体系化理解 |
- 作者:计算机类2507班
- 链接:https://learning.lcyteam.me//article/c-cpp-review-26
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。







