C++ 核心知识点总结
2025-12-21 22:33:50
发布于:广东
一、函数的定义与调用
1. 函数声明
函数声明:告诉编译器函数的名称、返回值类型和参数列表,不包含函数体。
int add(int a, int b); // 声明
2. 函数定义
函数定义:完整实现函数功能的代码块,包含函数体。
int add(int a, int b) { // 定义
return a + b;
}
3. 函数调用
函数调用:在程序中使用函数名和实际参数来执行函数。
int result = add(3, 4); // 调用
重要规则:如果函数定义在调用点之后,则必须在调用前提供函数声明。
二、形参与实参、作用域
1. 形参和实参
- 形参:在函数定义中括号内声明的变量,用于接收调用者传入的数据。
- 实参:在函数调用时传递给函数的具体值或变量。
- 关系:实参的值会被传递给对应的形参,函数内部通过操作形参来处理数据。
int add(int a, int b) { // a、b 是形参
return a + b;
}
int main() {
int x = 3, y = 4;
int z = add(x, y); // x、y 是实参
}
2. 作用域
变量的作用域分为以下两类:
- 全局变量:在所有函数外部定义的变量,在整个程序生命周期内有效,可被所有函数访问。
- 局部变量:在函数或代码块内部定义的变量,只在定义它的函数或代码块内有效。
- 易错点:当全局变量和局部变量同名时,在函数内部,局部变量会“遮蔽”全局变量。
三、C++指针类型的概念及基本应用
1. 指针的本质
指针的本质:一个变量,用来保存内存地址。
2. 指针的定义与初始化
语法:类型 *指针名;
int a = 10;
int* p = &a; // p 指向 a
int* q = nullptr; // 空指针
3. 两个指针相关运算符
| 运算符 | 名称 | 作用 | 示例 |
|---|---|---|---|
| & | 取地址 | 得变量的地址 | int* p = &a |
| * | 解引用 | 得地址里的值 | cout << *a; |
4. 指针做函数形参
指针做函数形参可以修改指向变量的值。
void addOne(int* ptr) { // 指针做形参
(*ptr)++; // 把外面的变量加 1
}
int main() {
int x = 5;
addOne(&x); // x 变成 6
}
5. 指针和数组
-
数组名就是首元素地址
结论:除sizeof与取地址&外,数组名会自动退化为指向首元素的指针。int a[5] = {10,20,30,40,50}; int* p = a; // 等价于 int* p = &a[0]; cout << *p; // 输出 10 -
指针遍历数组
结论:利用“指针 + 整数”做指针算术,可逐个访问元素。int a[4] = {1,2,3,4}; for (int* p = a; p < a + 4; ++p) //数组大小为4,指针偏移4次 cout << *p << ' '; // 1 2 3 4 -
下标运算符 [] 的指针本质
结论:p[i]等价于*(p + i)。int a[3] = {7,8,9}; int* p = a; cout << p[2] << *(p + 2); // 都输出 9 -
指针形参接收数组
结论:函数形参写成int arr[]或int* arr完全等价,传的是地址。// 两声明等价:void print(int* arr, int n) void print(int arr[], int n) { for (int i = 0; i < n; ++i) cout << arr[i] << ' '; } int main() { int a[3] = {2,4,6}; print(a, 3); // 输出 2 4 6 }
四、函数参数传递的概念(C++值传递、引用传递、指针传递)
1. 三种参数传递
- 参数传递方式(核心考点)
这是四级考试的重点,必须清晰区分三种方式:
| 传递方式 | 语法 | 是否修改原变量 | 特点 | 应用场景 |
|---|---|---|---|---|
| 值传递 | void func(int x) |
否 | 将实参的值复制一份给形参,函数内对形参的修改不影响实参。 | 当函数不需要修改外部变量时,安全简单。 |
| 引用传递 | void func(int &x) |
是 | 形参是实参的别名,对形参的任何操作都等同于对实参操作。效率高,无需复制。 | 当函数需要修改外部变量,或传递大型对象避免复制开销时。 |
| 指针传递 | void func(int *x) |
是 | 将实参的内存地址传递给形参,通过解引用指针 (*x) 来访问和修改实参的值。 |
常用于动态内存管理、数组操作或需要传递“可选”参数(可传空指针)。 |
五、C++结构体
1. 结构体类型定义
struct 类型名 {
数据成员列表;
}; // 分号不能丢
典型示例:
struct Student {
int id;
char name[20];
int score;
};
变量定义与初始化(3 种写法)
a) 先定义类型再定义变量:
Student s1; // 默认初始化
Student s2 = {1001, "Tom", 95};
b) 一次性定义:
struct Point { int x, y; } p1 = {3, 4};
c) C++11 列表初始化:
Student s3{1002, "Amy", 98};
2. 访问成员(“点”与“箭头”)
- 普通变量用
.
cout << s1.score; // 95
- 结构体指针用
->
Student* p = &s1;
cout << p->name; // Tom
3. 结构体数组
Student cls[50]; // 50 个学生
cls[0] = {1001, "Tom", 95};
cin >> cls[1].id >> cls[1].score;
4. 结构体指针
//new向操作系统申请一块足够放下一个 Student 对象的内存
Student* pStu = new Student{1003, "Bob", 88};
cout << pStu->id;
delete pStu; // 把刚才new申请到的那块内存标记为“可再分配”——即还给系统
六、C++二维数组与多维数组基本应用
1. 二维数组的定义及初始化
| 考点 | 语法示例 | 说明 |
|---|---|---|
| 1. 定义 | int a[3][4]; |
3 行 4 列,共 12 个 int,行优先连续存储 |
| 2. 初始化 | int a[2][3]={{1,2,3},{4,5,6}}; |
内层 {} 可省,但建议保留可读性 |
| 3. 下标访问 | a[i][j] |
先行后列,i∈[0,行-1],j∈[0,列-1] |
| 4. 遍历模板 | for(i) for(j) |
双重循环 |
| 5. 形参 | void f(int a[][4], int n) |
列数必须写死,行数可省 |
| 6. 多维扩展 | int b[2][3][4]; |
三维同理,三层循环即可 |
2. 二维数组常见简单应用
- 矩阵输入 + 求和
int a[100][100], n, m, sum = 0; cin >> n >> m; for (int i = 0; i < n; ++i) for (int j = 0; j < m; ++j) { cin >> a[i][j]; sum += a[i][j]; } cout << sum; - 矩阵转置(行列互换)
int a[50][50], b[50][50], n, m; cin >> n >> m; for (int i = 0; i < n; ++i) for (int j = 0; j < m; ++j) { cin >> a[i][j]; b[j][i] = a[i][j]; // 核心一行 }
3. 多维数组不同位置相差字节计算
例 1:二维数组
int a[5][8]; // R=5, C=8
求 &a[2][3] 与 &a[4][1] 相差多少字节?
- 计算元素差:
行差 = 4-2 = 2 行
列差 = 1-3 = -2 列
总元素差 = 2×8 + (-2) = 14 //乘以8是因为int a[5][8]有8列 - 字节差 = 14 × 4 = 56 字节 //乘以4是因为int占4字节
例 2:三维数组
int b[3][4][5]; // X=3, Y=4, Z=5
求 &b[1][2][3] 与 &b[2][0][1] 相差多少字节?
//乘以(4×5)是因为int b[3][4][5]后面是4行5列
- 元素差 = (2-1) × (4×5) + (0-2) × 5 + (1-3) = 20 - 10 - 2 = 8
- 字节差 = 8 × 4 = 32 字节
七、算法:递推
1. 递推算法关键知识点
| 知识点 | 四级要求 |
|---|---|
| 1. 基本思想 | 从已知边界出发,按“状态转移方程”逐步推出目标解 |
| 2. 建模 4 步 | ①定状态变量 ②写初始值 ③找转移方程 ④for 循环求解 |
| 3. 典型问题 | 斐波那契、青蛙跳台阶、杨辉三角、走格子计数 |
| 4. 数组维度 | 一维、二维(杨辉三角、部分背包) |
2. 典型递推问题
- 斐波那契(一维)
long long f[50]; f[1] = f[2] = 1; for(int i = 3; i <= n; ++i) { f[i] = f[i-1] + f[i-2]; } - 青蛙跳台阶(每次 1 或 2 级)
int f[50]; f[0] = f[1] = 1; // 0 级也算 1 种 for(int i = 2; i <= n; ++i){ f[i] = f[i-1] + f[i-2]; } - 杨辉三角(二维递推)
int a[20][20] = {0}; a[0][0] = 1; for(int i = 1; i < n; ++i){ a[i][0] = a[i][i] = 1; for(int j = 1; j < i; ++j){ a[i][j] = a[i-1][j-1] + a[i-1][j]; } } - 走格子(只能向右或向下)
int dp[20][20] = {0}; dp[0][0] = 1; for(int i = 0; i < n; ++i){ for(int j = 0; j < m; ++j){ if(i) dp[i][j] += dp[i-1][j]; if(j) dp[i][j] += dp[i][j-1]; } }
八、算法:排序概念和稳定性
1. 排序知识点
| 编号 | 知识点 | |
|---|---|---|
| 1 | 排序目的 | 将无序序列变成有序(升/降) |
| 2 | 关键字 | 参与比较的那个数据项 |
| 3 | 稳定性定义 | 相等元素的原相对次序在排序后不变 |
| 4 | 稳定排序举例 | 冒泡、插入、归并 |
| 5 | 不稳定排序举例 | 选择、快速 |
| 6 | 内置函数 | 会用 sort(begin, end) |
| 7 | 手写模板 | 冒泡/选择/插入 |
2. 常见排序复杂度表
(整型数组,长度 n)
| 算法 | 平均时间 | 最坏时间 | 额外空间 | 稳定性 |
|---|---|---|---|---|
| 冒泡 | O(n²)) | O(n²) | O(1) | √ |
| 选择 | O(n²) | O(n²) | O(1) | × |
| 插入 | O(n²) | O(n²) | O(1) | √ |
| 归并 | O(n log n) | O(n log n) | O(n) | √ |
| 快速 | O(n log n) | O(n²)) | O(log n) | × |
九、算法:排序算法(冒泡排序、插入排序、选择排序)
1. 三种排序的思想
| 算法 | 核心思想 | 平均/最坏时间 | 额外空间 | 稳定性 | 手写难度 |
|---|---|---|---|---|---|
| 冒泡 | 相邻逆序交换 | O(n²)/O(n²) | O(1) | √稳定 | ★☆☆ |
| 插入 | 把当前元素插到已排好部分 | O(n²)/O(n²) | O(1) | √稳定 | ★★☆ |
| 选择 | 每趟选最小(大)放最前 | O(n²)/O(n²) | O(1) | ×不稳定 | ★★☆ |
2. 三种排序模板代码
- 冒泡(升序)
void bubble(int a[], int n){//数组下标(0到n-1) for(int i = 0; i < n-1; ++i){ // 外层 n-1 趟 for(int j = 0; j < n-i-1; ++j){ // 内层到 n-i-1 if(a[j] > a[j+1]){ swap(a[j], a[j+1]); } } } } - 插入(升序)
void insertion(int a[], int n){//数组下标(0到n-1) for(int i = 1; i < n; ++i){ // 从第 2 个开始 int key = a[i], j = i-1; while(j >= 0 && a[j] > key){ // 找插入位置 注意边界 a[j+1] = a[j]; --j; } a[j+1] = key; } } - 选择(升序)
void selection(int a[], int n){//数组下标(0到n-1) for(int i = 0; i < n-1; ++i){ int minIdx = i;// 最小值的下标 for(int j = i+1; j < n; ++j){ // 从i+1开始往后找 注意边界 if(a[j] < a[minIdx]) minIdx = j; } if(minIdx != i) swap(a[i], a[minIdx]); //交换最小值的第i个元素 } }
十、简单算法复杂度的估算(含多项式、指数复杂度)
1. 常见算法复杂度
| 级别 | 大O记号 | 典型循环形式 | 10⁵ 数据可否过 |
|---|---|---|---|
| 常数 | O(1) | 无循环 | ✔ |
| 线性 | O(n) | 单层for | ✔ |
| 平方 | O(n²) | 双重for | 10⁴ 勉强,10⁵ ✘ |
| 立方 | O(n³) | 三重for | 10³ ✘ |
| 指数 | O(2ⁿ) / O(kⁿ) | 递归、回溯 | n>25 ✘ |
2. 常见代码时间复杂度
- 单层循环
for(i=0;i<n;++i) ... → O(n) - 并列循环相加取最大
for(i=0;i<n;++i) ... → O(n) for(i=0;i<m;++i) ... → O(m)// 总复杂度 = max(O(n),O(m)) - 嵌套循环相乘
for(i=0;i<n;++i) for(j=0;j<m;++j) ... → O(n·m) - 指数递归
void dfs(int dep){ if(dep==n) return; dfs(dep+1); dfs(dep+1); } // 每层 2 分叉 → O(2ⁿ)
3. 常见实例时间复杂度
| 代码片段 | 复杂度 |
|---|---|
| 单层for累加 | O(n) |
| 冒泡排序 | O(n²) |
| 三重循环暴力 | O(n³) |
| 简单递归求斐波那契 | O(2ⁿ) |
十一、文件重定向与文件读写操作
1. 文件重定向和文件读写的概念
| 编号 | 名词 | 解析 | 对应工具 |
|---|---|---|---|
| 1 | 文件重定向 | 让cin/cout不再读写键盘屏幕,而是读写文件 |
freopen |
| 2 | 文件读写 | 用ifstream/ofstream像cin/cout一样读写文件 |
ifstream/ofstream |
2. 文件重定向和文件读写代码
-
文件重定向(最简单,一行搞定)
#include <cstdio> // 只需这个头 using namespace std; int main(){ freopen("input.txt","r",stdin); // 把 cin 变成读文件 freopen("output.txt","w",stdout); // 把 cout 变成写文件int a,b; cin >> a >> b; cout << a + b; return 0; }说明:
"r"读,"w"写;文件名用英文半角引号。- 程序运行后,会在可执行文件同目录下出现
output.txt。
-
文件读写(用
ifstream/ofstream,等价于cin/cout)#include <fstream> using namespace std; int main(){ ifstream fin("data.in"); // 读文件 ofstream fout("data.out"); // 写文件int x; fin >> x; // 就像 cin >> x fout << x * 2; // 就像 cout << ... fin.close(); fout.close(); return 0; } -
同时读写多组数据(循环模板)
#include <fstream> int main(){ std::ifstream fin("input.txt"); std::ofstream fout("output.txt"); int n; while(fin >> n){ // 读到文件末尾自动停 int sum = 0, t; for(int i=0;i<n;++i){ fin >> t; sum += t; } fout << sum << '\n'; } fin.close(); fout.close(); return 0; }
3. 与cin和cout的对比
- 设备对照表
| 方式 | 从哪读 | 写到哪 | 程序里怎么写 |
|---|---|---|---|
cin/cout默认 |
键盘 | 屏幕 | cin >> x; cout << x; |
freopen重定向 |
文件 | 文件 | freopen("in.txt","r",stdin); cin >> x; |
ifstream/ofstream |
文件 | 文件 | ifstream fin("in.txt"); fin >> x; |
- 语法对照表(同样读一个整数)
| 写法 | 需要头文件 | 读入语句 | 输出语句 |
|---|---|---|---|
| 键盘/屏幕 | #include <iostream> |
std::cin >> x; |
std::cout << x; |
freopen |
#include <cstdio> |
同上,cin自动改道 |
同上,cout自动改道 |
| 独立流 | #include <fstream> |
std::ifstream fin("in.txt"); <br> fin >> x; |
std::ofstream fout("out.txt"); <br> fout << x; |
十二、异常处理
1. 异常处理基本知识点
| 编号 | 名词 | 用通俗话说 |
|---|---|---|
| 1 | 异常 | 程序运行时出现的“意外”,如5/0、数组越界 |
| 2 | try块 |
把可能出错的语句包裹起来 |
| 3 | catch块 |
抓到异常后怎么处理 |
| 4 | throw |
手动抛出异常 |
| 5 | 标准异常类 | std::runtime_error 等 |
| 6 | 头文件 | #include <iostream> 已够用(少量编译器需 <stdexcept>) |
2. 异常处理简单模板
-
防止除零
#include <iostream> using namespace std; int main(){ int a, b; cin >> a >> b; try{ if(b == 0) throw "Divide by zero!"; cout << a / b << endl; } catch(const char* msg){//char* msg 抛出的是字符串 cout << "Error: " << msg << endl; } return 0; } -
防止数组越界(手动抛)
int a[10]; int idx; cin >> idx; try{ if(idx < 0 || idx >= 10) throw runtime_error("index out of range"); cout << a[idx] << endl; } catch(const runtime_error& e){ cout << "Error: " << e.what() << endl; } -
通用万能 catch(兜底)
try{// 任何可能出错的代码 } catch(...){ // 三个点代表“捕获所有” catch(...) 必须放在所有catch的最后,否则编译报错。 cout << "Unknown error!" << endl; } -
多个catch
#include <iostream> #include <stdexcept> // runtime_error, logic_error using namespace std; int main() { int x, y; cout << "输入两个整数:"; cin >> x >> y; try { // 1) 除零异常 if (y == 0) { throw runtime_error("除零错误!"); } // 2) 负数参数异常(自定义逻辑) if (x < 0 || y < 0) { throw logic_error("参数不能为负数!"); } // 3) 手动抛一个整数异常 if (x > 1000) { throw 999; // 普通 int 类型 } cout << "结果 = " << x / y << endl; } catch (const runtime_error& e) { // ① 精准捕获 runtime_error cout << "[runtime_error] " << e.what() << endl; } catch (const logic_error& e) { // ② 精准捕获 logic_error cout << "[logic_error] " << e.what() << endl; } catch (int errCode) { // ③ 精准捕获 int cout << "[int 异常] 错误码:" << errCode << endl; } catch (...) { // ④ 兜底捕获所有剩余异常 cout << "[未知异常] 其他类型错误!" << endl; } cout << "程序继续运行..." << endl; return 0; }
全部评论 1
看不懂思密达
3小时前 来自 浙江
0













有帮助,赞一个