Lazy loaded image
C / C++语言程序设计复习大纲
字数 14239阅读时长 36 分钟
2026-1-10
2026-1-16
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讲 数据的存储与表示

  • 基本数据类型分类: 整型(shortintlonglong long)、字符型(char)、浮点型(floatdouble)、布尔型(C++ 中的 bool),以及派生类型(数组、指针、结构体、类等)。
  • 数据在内存中的存储: 各类型占用字节数与实现有关,一般 32/64 位平台上:int 通常为 4 字节,double 通常为 8 字节;数据以二进制补码形式存储整数,以 IEEE754 标准表示浮点数。
  • 整数的表示与溢出问题: 有符号整数采用补码表示,最高位为符号位;当运算结果超出类型能表示的范围时发生溢出,表现为“回绕”,结果不可预测,应尽量避免。
  • 浮点数精度与误差: 浮点运算存在舍入误差和精度损失,不能用 == 直接比较两个浮点数是否相等,应使用“差值小于某个阈值”的方式判断。
  • 字符与编码: char 本质上是一个整数类型,用于存放字符编码值;C/C++ 中字符常量用单引号表示,如 'A',其在内存中是 ASCII 码或其他编码的数值。
  • 常量与变量: 常量在程序运行期间不可改变,包括字面常量(103.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
  • ifswitch 的选用: 条件是区间或复杂逻辑时用 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-catchgoto 不能跨函数跳转,不提供异常对象传递,只是简单的控制流跳转。
  • 合理使用建议: 多数情况可通过重构循环结构、提取函数、使用 break/continue 等方式替代 gotobreakcontinue 使用时,要搞清所在的循环层级和执行顺序。
  • 易错点: 在多重循环中误认为 break 能跳出所有循环;continue 使用不当导致更新表达式未执行,引发死循环;滥用 goto 使程序逻辑混乱。

第6讲 复杂的循环

  • 多重嵌套循环的设计: 对于二维数组或多层嵌套逻辑,外层控制“行”,内层控制“列”;要根据问题逻辑明确每一层循环的含义。
  • 循环与条件综合: 在复杂筛选、统计中,经常在循环内部嵌套 ifswitch;需注意逻辑条件的覆盖与互斥关系,防止遗漏和重复计算。
  • 循环退出条件多样化: 除基于计数的退出方式(如循环 次),还可以基于状态(如输入特定字符结束)、基于收敛(误差小于阈值)等方式停止循环。
  • 复杂模式打印与模拟: 如打印九九乘法表、菱形、对称图案、数字三角形等,需要结合嵌套循环与多重条件控制输出位置和内容。
  • 时间复杂度意识: 嵌套循环常见 () 甚至更高复杂度,应根据数据规模分析是否可接受,必要时优化算法结构。
  • 边界条件与健壮性: 在复杂循环中要特别注意循环变量在每条路径上的变化,避免某些分支中未更新循环变量导致死循环。

第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") 在遇到空白字符时终止读取。
  • 字符处理: 字符本质上是整数,可进行算术运算;常用的分类函数(isalphaisdigitisspace 等)和大小写转换函数(touppertolower)可以简化字符判断与处理。
  • 字符串遍历与操作: 常通过下标或指针遍历到 '\0' 为止;可在遍历中统计长度、统计字符种类、完成替换、过滤等操作。
  • C++ std::string std::string 是 C++ 封装的字符串类,支持动态长度、运算符重载(如 +==<)、成员函数(size()substr()find() 等),相对更安全、易用。
  • 易错点: 忘记预留 '\0' 的空间;使用 strcpy 等函数时目标数组空间不足;字符常量与字符串常量混用('a'"a")。

第12讲 数组习题课

  • 一维数组应用: 求最大值/最小值及其下标、求和与平均值、统计满足条件的元素个数、去重、合并两个有序数组等。
  • 二维数组应用: 矩阵加法、乘法、转置、求对角线元素和、行列统计等;重点在于正确使用两层循环并确保下标范围合法。
  • 数组与查找、排序综合: 将前几讲查找和排序方法应用到数组上,手写实现并能够根据题意合理选择方法和循环结构。
  • 数组与函数配合: 会将数组作为参数传入函数,在函数中进行统计、排序、查找等操作,熟悉“数组退化为指针”的现象及其影响。
  • 典型综合题: 学生成绩管理(录入、平均分、最高分、及格率)、简单数据分析(频数统计、直方图数据准备)等。
  • 易错点回顾: 下标越界、未初始化数组、错误假设数组长度;排序和查找函数参数使用不当。

第13讲 指针

  • 指针的基本概念: 指针变量存放的是“地址”,即内存单元的编号;类型说明了通过该指针访问的对象类型,如 int* 指向 intdouble* 指向 double
  • 取地址与解引用运算: & 用于取得变量的地址,如 &a; 解引用运算符用于访问指针所指向的对象,如 p 表示指针 p 指向的变量。
  • 指针与内存模型: 程序运行时内存通常划分为代码区、全局/静态区、栈区和堆区;指针可以指向上述任一区域中的对象,但不得指向无效或已释放区域。
  • 空指针与野指针: 空指针(通常为 nullptrNULL)不指向任何有效对象,使用前应判断是否为空;野指针是未初始化或悬空(指向已释放内存)的指针,访问将导致未定义行为。
  • 指针运算: 指针可以做加减整数运算(按所指类型的大小偏移);两个指向同一数组的指针可以相减得到元素间距离。
  • 指针与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) { ... };指针法与下标法等价,但更贴近底层实现。
  • 字符串函数与指针:strlenstrcpystrcatstrcmp 等函数的参数实质上都是 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讲 类和对象

  • 面向对象基本概念: 类是对一类对象的抽象,封装了数据(成员变量)和操作(成员函数);对象是类的实例。
  • 封装与访问控制: 使用 publicprivateprotected 控制成员访问权限:public 对外公开,private 仅在类内及友元中可用,protected 在类、友元和派生类中可访问;通过封装隐藏实现细节,对外暴露接口。
  • 类的定义与成员函数: 在 C++ 中 classstruct 都可定义类,区别在默认访问控制不同(classprivatestructpublic);成员函数可以在类外定义时加上作用域限定 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] 申请数组;对应使用 deletedelete[] 释放,必须配对一致。
  • 资源管理与 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讲 建立类层次:继承与访问控制

  • 继承基本概念: 通过关键字 publicprotectedprivate 继承基类,派生类自动获得基类成员(除构造、析构、赋值等);体现“is-a” 关系。
  • 继承方式与访问控制: 公有继承使得基类的 publicprotected 成员在派生类中保持相对访问级别,对外接口也保持兼容,是最常用方式;protectedprivate 继承常用于实现和封装上的考虑。
  • 父类与子类对象间的转换: 派生类对象可隐式转换为基类对象或基类引用/指针(向上转型),反之需显式转换且风险较大;多态通常通过基类指针或引用来实现。
  • 构造和析构中的继承: 创建派生类对象时,会先构造基类部分,再构造本类成员;析构时顺序相反;可以在派生类构造函数初始化列表中调用特定基类构造函数。
  • 名字隐藏与作用域: 派生类中若定义与基类同名的成员(包括函数),默认会隐藏基类的同名成员,可通过作用域运算符 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() 虚函数,派生类 CircleRectangle 各自实现;调用方只需通过基类指针容器逐个调用 draw() 即可实现对不同图形的统一操作。
  • 向下转型与dynamic_cast 当需要从基类指针安全地转换到派生类指针时,可使用 dynamic_cast,在转换失败时返回 nullptr,避免不安全的强制类型转换。
  • 多态与资源管理: 配合虚析构函数与智能指针(std::unique_ptrstd::shared_ptr)使用,可以简化多态对象的生命周期管理。
  • 易错点: 将含有纯虚函数的抽象类当作可实例化类;在派生类中改变虚函数参数列表导致重载而非覆盖;使用 C 风格强制转换破坏类型安全。

第25讲 友元与运算符重载

  • 友元函数与友元类: 通过在类内使用 friend 声明,可以将某个函数或类设为友元,使其访问本类的 privateprotected 成员;友元不受访问控制限制,但会打破封装,应慎用。
  • 运算符重载的目的: 使自定义类型支持类似内置类型的运算符语法,提高代码可读性和直观性,例如为复数类型重载 +-*<< 等。
  • 成员运算符与非成员运算符: 一般二元运算符可作为成员或非成员重载;当需要支持隐式类型转换或对称性(如 a + bb + a)时,经常采用非成员函数并配合友元。
  • 常用运算符重载: 算术运算符(+-*/)、关系运算符(==< 等)、赋值运算符(=)、下标运算符(operator[])、函数调用运算符(operator())、输入输出运算符(>><<)等。
  • 输入输出运算符重载: 通常以非成员形式并返回流引用,如:ostream& operator<<(ostream& os, const ClassName& obj);,可支持链式输出。
  • 重载时的const 使用: 对不修改对象状态的运算符(如比较运算符、读操作)应声明为 const 成员函数;参数和返回值的 const 使用也应符合语义。
  • 易错点: 将不应重载的运算符(如 .?: 等)误认为可重载;重载运算符时违反运算习惯(如使 == 不具有对称性);重载输入输出运算符时忘记返回流引用。

第26讲 函数模板与类模板

  • 模板的基本思想: 模板支持参数化类型编程,使同一份代码能够适用于多种数据类型;编译器在使用模板时进行“实例化”,生成具体类型版本。
  • 函数模板: 使用 template <typename T> 声明类型参数,通过 T 编写通用函数,如通用 swapmax 函数;调用时可显式指定类型参数,也可由编译器进行类型推导。
  • 类模板: 类定义中使用模板参数,如 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 基本组成: 包含容器(如 vectorlistdequesetmap 等)、算法(如 sortfindfor_each 等)、迭代器和函数对象,是 C++ 标准库的重要组成部分。
  • 顺序容器: vector 提供动态数组功能,支持随机访问,尾部插入/删除较高效;list 为双向链表,适合频繁中间插入/删除但不支持随机访问。
  • 关联容器: setmultisetmapmultimap 基于平衡树实现,按键有序存储;插入、查找、删除平均时间复杂度为
  • 迭代器概念: 迭代器是对容器元素位置的抽象,可视作“广义指针”;不同容器提供不同能力的迭代器(随机访问、双向、前向等)。
  • 泛型算法的使用: STL 算法通过迭代器与容器解耦,如使用 sort(v.begin(), v.end()); 排序,find 查找等;要求迭代器满足相应的概念。
  • auto 与范围for循环: 使用 auto 推断迭代器类型,配合范围 for 循环(for (auto& x : v)) 可简化遍历语法。
  • 易错点: 对迭代器失效问题认识不足(如在 vector 中插入元素后旧迭代器失效);在错误容器上使用不适当算法(如对 list 使用 sort,应调用成员函数 list::sort)。

第29讲 文件操作

  • 文件与流的概念: 文件是存储在外部介质上的数据集合;C++ 使用流(ifstreamofstreamfstream 等)来抽象输入输出设备和文件。
  • 打开与关闭文件: 使用 fstream 类的 open 成员函数或构造函数打开文件,需指定文件名和打开模式(如 ios::inios::outios::binary 等);使用 close 关闭文件,或通过析构自动关闭。
  • 文本文件与二进制文件: 文本文件按字符序列存储,具有可读性;二进制文件按原始字节序列存储,更适合高效存取复杂数据结构。
  • 读写操作: 文本模式下可使用流插入运算符 << 与提取运算符 >>,或 getline 读取整行;二进制模式可使用 readwrite,指定缓冲区和字节数。
  • 文件状态与错误检测: 通过 is_open 判断文件是否成功打开,通过 eoffailbad 等函数检测流状态;读写出错时应及时采取措施或报错。
  • 文件位置指针: 使用 seekg(输入)和 seekp(输出)移动文件读写位置,配合 tellgtellp 获取当前位置,实现随机访问。
  • 易错点: 忽略文件打开失败的情况直接进行读写;混用文本模式与二进制模式导致读取结果异常;未正确刷新缓冲区导致数据未及时写入文件。

《C/C++语言程序设计》知识梳理表

模块名称
包含章节
核心知识点(简要)
典型案例(名称)
易错点和高频考点
模块一:程序基础与流程控制
第1讲 概述 第2讲 数据的存储与表示 第3讲 选择结构 第4讲 循环结构 第5讲 流程转移 第6讲 复杂的循环
程序与算法概念;数据类型与存储表示;表达式与运算符;顺序、选择(ifswitch)与循环(whilefordo … while)结构;breakcontinuegoto;嵌套与复杂循环设计
学生成绩统计(最大值、平均值、及格率);九九乘法表;菜单式程序;条件分支计算器
赋值与比较混淆(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/deletenew[]/delete[] 混用;未自定义拷贝构造导致浅拷贝问题;错误理解继承方式影响访问控制;基类析构函数未设为 virtual;虚函数签名不匹配导致未实际覆盖
模块七:运算符重载与模板
第25讲 友元与运算符重载 第26讲 函数模板与类模板 第27讲 运算符重载深入
友元函数与友元类;常用运算符重载(算术、关系、下标、输入输出等);前置/后置自增自减;复合赋值运算符;函数模板与类模板的定义与实例化;模板参数与类型推导
复数类的加减乘除与输出;自定义容器/矩阵类的下标与算术运算;通用 swap/max 函数模板;简单类模板(如栈 Stack<T>
误以为所有运算符都可重载;运算符重载逻辑违反直觉(如比较不具有自反性);返回局部对象引用;模板定义与实现分离导致链接错误
模块八:STL与文件操作
第28讲 STL容器与迭代器 第29讲 文件操作
STL 容器(vectorlistsetmap 等)及其特点;迭代器与泛型算法(sortfind 等);C++ 文件流 ifstream/ofstream/fstream;文本与二进制文件读写;文件状态检测与随机访问
使用 vector 存放和处理成绩;用 map 统计单词频率;学生信息文件读写与查询;简单配置文件解析
迭代器失效(插入、删除后继续使用旧迭代器);错误选择容器与算法组合;文件打开失败后继续读写;混用文本与二进制模式导致数据错乱
模块九:综合与复习
第30讲 银行账户管理系统 第31讲 思维热身 第32讲 总复习
综合运用函数、数组、指针、结构体/类、继承与多态、STL与文件操作等知识构建完整程序;需求分析与模块划分;接口设计与错误处理;代码风格与调试技巧
银行账户管理系统(开户、存取款、查询、持久化);多态图形绘制或几何计算系统;综合试题训练与知识点串联
模块接口设计不清晰导致耦合度过高;数据结构选择不当影响效率;资源管理和异常情况处理不完善;复习中知识点孤立、未形成体系化理解
 
上一篇
C语言中的函数指针与回调函数(C14)
下一篇
难题解惑(2026.01.10)

评论
Loading...