48.总结
48.1 知识点
学习的主要内容
强调
48.2 核心要点速览
函数
函数基础
- 作用:封装代码、提升复用率、抽象行为。
- 声明与定义:标准在头文件(.h)声明、源文件(.cpp)定义;临时可在.cpp中处理,需提前声明。
- 语法:
返回类型 函数名(参数列表) { 逻辑; return 值; }
,void
无返回值,return
可提前结束函数。
形参与实参
- 形参:函数内声明的参数(如
int a
)。 - 实参:调用时传入的具体值(如
add(5, 10)
)。 - 值传递:形参是实参拷贝,修改不影响实参。
参数默认值
- 作用:调用时可省略参数,使用默认值。
- 注意:声明可带默认值,定义不可;可选参数需放在普通参数后。
变量作用域
- 局部变量:语句块内声明,块结束后销毁。
- 全局变量:函数外声明,程序结束后销毁,可被全局访问。
- 同名变量:内部作用域屏蔽外部。
函数重载
- 条件:同作用域、同名函数,参数数量/类型/顺序不同(与返回值无关)。
- 注意:可选参数可能导致编译器歧义。
递归函数
- 关键:必须有结束条件(如
if (num>10) return;
),函数自调用。
内联函数
- 作用:用
inline
声明,编译时将代码插入调用处,减少函数调用开销。 - 限制:递归/大型函数不适用,可能导致代码膨胀。
变量的存储类型
auto变量
- 作用:让编译器自动推断变量类型,减少手动定义。
- 特点:
- 需声明时初始化,否则无法推断类型。
- 优点:代码简洁;
- 缺点:影响可读性,初学阶段不建议频繁使用。
- 示例:
auto i = 10;
(编译器推定为int
)
static变量
- 作用:声明周期贯穿程序运行,存储全局或局部静态变量。
- 分类:
- 静态局部变量:函数内声明,仅初始化一次,函数返回后不销毁。
- 静态全局变量:文件内作用域,不可跨文件访问(普通全局变量可跨文件)。
- 特点:
- 优点:适合缓存或全局共享数据,仅初始化一次。
- 缺点:持续占用内存,可能增加内存消耗。
register变量
- 作用:建议存储在CPU寄存器中,提高访问速度(现代编译器已自动优化,较新版本C++已废弃)。
- 限制:
- 无法获取地址(无寄存器地址)。
- 现代开发中极少使用,编译器优化更高效。
- 对比:
类型 存储位置 速度 容量限制 寄存器 CPU内部 极快 有限 内存 内存条 较慢 较大
extern变量
- 作用:跨文件共享全局变量或函数,声明时不定义,需在另一文件中定义。
- 注意:
- 不允许跨文件定义同名全局变量,会编译报错。
- 仅声明外部已定义的内容,不能重复定义。
- 示例:
// 文件1.cpp中定义 int globalVar = 10; // 文件2.cpp中声明 extern int globalVar;
数组
一维数组
核心要点 | 说明 |
---|---|
基本概念 | 相同类型数据的线性集合,内存连续存储。 |
声明方式 | int arr[5]; (指定容量)、int arr[] = {1,2,3}; (自动推导容量)。 |
长度计算 | sizeof(arr) / sizeof(int) ,获取数组元素个数。 |
元素访问 | 索引从0开始,arr[0] 为首个元素,越界访问可能返回不确定值。 |
初始化规则 | 未指定元素初始化为0,如int arr[3] = {1}; ,其余元素为0。 |
操作限制 | 静态数组容量固定,增删元素需重新分配数组(如复制到新数组)。 |
二维数组
核心要点 | 说明 |
---|---|
基本概念 | 行列结构的数据集合,用arr[row][col] 访问元素。 |
声明方式 | int arr[3][4]; (3行4列)、int arr[][4] = {{1,2},{3,4}}; (自动推导行数)。 |
行列计算 | 行数:sizeof(arr)/sizeof(arr[0]) ,列数:sizeof(arr[0])/sizeof(int) 。 |
元素访问 | arr[i][j] ,i为行索引,j为列索引。 |
函数传参 | 需指定列数,如void func(int arr[][4], int rows) 。 |
字符数组
核心要点 | 说明 |
---|---|
字符串初始化 | 用char str[] = "hello"; 时,末尾自动添加\0 作为结束符。 |
乱码原因 | 未初始化或无\0 时,打印会读取到随机内存,导致乱码(如“烫烫烫”)。 |
手动添加结束符 | char arr[] = {'A','B','\0'}; ,确保字符串正确结束。 |
与字符串区别 | 字符数组是数组,字符串是string 类型,后者更灵活(如自动管理长度)。 |
指针基础
内存地址与指针概念
核心要点 | 说明 |
---|---|
内存地址 | 内存单元的唯一编号(字节为单位),如int i=2 占4字节,地址为连续4个字节的首地址。 |
指针定义 | 存储变量地址的变量,通过& 获取地址,* 解引用获取值。例:int* p=&i ,p 存i 的地址,*p 取i 的值。 |
指针基础操作
操作 | 语法 | 示例 |
---|---|---|
声明 | 类型* 指针名 |
int* ptr; |
初始化 | 指针=&变量 |
int a=10; int* p=&a; |
解引用 | *指针 |
cout << *p; (输出10) |
空指针 | void* p=nullptr; |
不指向任何有效地址,避免野指针。 |
野指针 | 未初始化或指向已释放内存的指针,可能导致程序崩溃。 |
指针进阶操作
要点 | 细节 | 示例 |
---|---|---|
内存占用 | 64位系统下占8字节(32位占4字节),与类型无关。 | sizeof(int*)=8 |
&与*混合使用 | &*p :先解引用再取地址(等价于p );*&a :先取地址再解引用(等价于a )。 |
&*ptr == ptr |
指针自增减 | 按类型大小跳转:int* ptr 自增跳4字节,char* 跳1字节。 |
int a=10; int* p=&a; p++ 后地址+4。 |
空类型指针 | void* 可指向任意类型,使用时需强转。 |
void* p=&s; short s2=*(short*)p; |
指针与常量
类型 | 定义 | 特点 |
---|---|---|
指向常量的指针 | const int* p; |
可改指向,不可改值。p=&j; *p=10 报错,p=&i 允许。 |
指针常量 | int* const p; |
不可改指向,可改值。p=&j 报错,*p=20 允许。 |
指向常量的指针常量 | const int* const p; |
不可改指向和值。p=&j 和*p=10 均报错。 |
指针和数组
指针与一维数组
核心要点 | 说明 |
---|---|
内存关系 | 数组连续存储,指针指向首地址,通过指针增减访问元素。 |
建立联系 | - 数组名即首地址:int* p = arr; - 首元素地址: p = &arr[0]; |
元素访问 | - 解引用:*p (首元素),*(p+1) (次元素) - 下标语法: p[0] 等价于*(p+0) |
指针运算 | 自增/减按元素大小偏移(如int* 自增跳4字节)。 |
数组名限制 | 数组名是固定地址,不能重定向;指针可指向任意合法内存。 |
指针与二维数组
核心要点 | 说明 |
---|---|
内存结构 | 二维数组按行连续存储,可视为一维数组的一维数组。 |
数组指针 | - 定义:int (*p)[4] (指向含4个int的一维数组) - 偏移: p+1 跳一行(4×4=16字节) |
元素访问 | - 按元素指针:int* p = &arr[0][0]; 指向首个元素,*p 访问元素,p+1 跳1个int(4字节)。- 按行指针: int (*p)[4] = arr; 指向整行,*(p+i) 取第i行首地址,*(*(p+i)+j) 取i行j列元素。示例: *(*(p+1)+2) 等价于arr[1][2] ,步骤为:1. p+1 :指向第2行(偏移16字节)。2. *(p+1) :转换为行内首元素地址(类型int* )。3. *(p+1)+2 :地址加2(偏移8字节),指向第2行第3列。4. *(*(p+1)+2) :解引用得到元素值。 |
二维数组名 | - arr 是行指针,*arr 是列指针(首元素地址) - sizeof(arr) 为整体大小,sizeof(p) 为指针大小(8字节) |
指针与字符数组
核心要点 | 说明 |
---|---|
字符串初始化 | - char arr[] = "hello"; 自动加\0 - 纯字符数组需手动加 \0 ,否则打印乱码 |
字符指针 | - 指向常量字符串:const char* p = "test"; - 可直接打印,遇 \0 结束 |
遍历方式 | - while (*p != '\0') { cout << *p++; } - 偏移量控制: while (*(p+i) != '\0') |
注意事项 | - 常量字符串不可修改(如"test"[0] = 'a' 报错) - 指针未初始化或越界访问导致崩溃 |
指针数组
操作 | 语法示例 | 说明 |
---|---|---|
定义与初始化 | int* p[3] = {&a, &b, &c}; |
每个元素存储变量地址,需初始化或后续赋值 |
访问值 | cout << *p[0]; |
解引用指针数组元素,获取指向的值 |
修改指向 | p[0] = &d; |
改变指针数组元素存储的地址 |
修改值 | *p[0] = 99; |
通过指针修改指向变量的值 |
遍历 | for (int i=0; i<3; i++) cout << *p[i]; |
遍历每个指针,解引用获取值 |
数组指针 vs 指针数组
对比项 | 数组指针 | 指针数组 |
---|---|---|
定义 | 指向一个数组的指针 | 由多个指针组成的数组 |
本质 | 指针变量,存储数组的首地址 | 数组变量,元素为指针 |
语法 | 类型 (*指针名)[元素个数] |
类型* 数组名[数组长度] |
示例 | int (*p)[4]; (指向含4个int的数组) |
int* p[4]; (含4个int*指针的数组) |
存储内容 | 数组的首地址 | 多个变量的地址 |
访问元素 | *(*(p+i)+j) (二维数组第i行j列) |
*(p[i]) (解引用第i个指针指向的值) |
应用场景 | 操作二维数组、矩阵 | 存储多个指针(如函数指针、动态内存) |
多级指针
各级指针对比
级别 | 定义 | 语法声明 | 示例 | 访问值 | 应用场景 |
---|---|---|---|---|---|
一级指针 | 指向普通变量的指针 | int* p; |
int a=10; int* p=&a; |
*p |
普通变量地址存储、函数参数传递 |
二级指针 | 指向一级指针的指针 | int** pp; |
int* p=&a; int** pp=&p; |
**pp |
指针数组、函数中修改指针指向 |
三级指针 | 指向二级指针的指针 | int*** ppp; |
int** pp=&p; int*** ppp=&pp; |
***ppp |
复杂数据结构(如指针的指针数组) |
内存分配
内存存储区域对比
区域 | 存储内容 | 生命周期 | 读写属性 | 管理方式 |
---|---|---|---|---|
代码段 | 函数机器码、执行指令 | 程序运行期 | 只读 | 系统自动加载 |
数据段 | 全局变量、静态变量 | 程序运行期 | 可读写 | 系统自动管理 |
常量段 | 字符串字面量、只读数据 | 程序运行期 | 只读 | 系统自动管理 |
栈 | 局部变量、函数参数、返回值 | 函数调用周期 | 可读写 | 编译器自动分配/释放 |
堆 | 动态分配的内存(new申请) | 程序员手动控制 | 可读写 | 手动用new分配/delete释放 |
堆内存分配与释放
操作 | 语法 | 示例 | 注意事项 |
---|---|---|---|
单个对象分配 | 类型* 指针 = new 类型(初始值); |
int* p = new int(10); |
- 指针变量在栈,指向堆内存 - 必须初始化或赋值 |
数组分配 | 类型* 指针 = new 类型[容量]; |
int* arr = new int[5]; |
- 需用delete[] 释放- 可聚合初始化: new int[3]{1,2,3} |
释放单个对象 | delete 指针; |
delete p; p = nullptr; |
- 释放后指针置为nullptr ,避免野指针- 重复释放会崩溃 |
释放数组 | delete[] 指针; |
delete[] arr; arr = nullptr; |
- 必须与new[] 配对使用- 释放后指针置空 |
内存安全核心要点
问题类型 | 原因 | 解决方案 |
---|---|---|
内存泄漏 | 堆内存未用delete 释放 |
- 及时调用delete /delete[] - 指针释放后置 nullptr |
越界访问 | 数组索引超出范围、野指针操作 | - 用sizeof 计算数组长度- 避免访问未分配的内存 |
空指针错误 | 访问nullptr 或未初始化指针 |
- 声明时置nullptr - 使用前检查 if (p != nullptr) |
野指针 | 指向已释放的内存或未分配区域 | - 释放后立即置nullptr - 不 |
引用
引用的基础使用
核心要点 | 说明 |
---|---|
定义 | 变量的别名,与原变量共享内存地址,声明时用& ,如int& ref = a; 。 |
初始化 | 必须初始化,如int& ref = a; (不能int& ref; )。 |
类型一致性 | 引用类型必须与原变量一致,否则编译报错(如short& ref = intVar 会报错)。 |
绑定限制 | 绑定后不能更改指向,如ref = b; 是修改a 的值而非重新绑定。 |
内存占用 | 不占用新内存,与原变量共用同一内存地址。 |
左值引用 vs 右值引用
类别 | 左值引用(& ) |
右值引用(&& ) |
---|---|---|
语法 | int& ref = a; (绑定左值变量) |
int&& ref = 10; (绑定右值临时对象) |
绑定对象 | 持久化对象(如变量、数组、指针) | 临时对象(如字面量10 、函数返回值) |
生命周期 | 与原变量一致,随作用域结束销毁 | 延长右值生命周期至引用作用域结束 |
可修改性 | 可修改原变量的值(如ref = 20 修改a ) |
可修改绑定的临时对象(如ref = 20 修改临时值) |
应用场景 | 函数参数传递(避免拷贝)、变量别名 | 移动语义(避免临时对象拷贝)、右值优化 |
引用与函数
场景 | 说明 |
---|---|
左值引用参数 | - 函数参数为int& value ,可直接修改实参,避免值拷贝开销。- 例: void swap(int& a, int& b) 交换两个变量的值。 |
右值引用参数 | - 函数参数为int&& value ,绑定右值临时对象,优化临时值操作。- 例: void process(int&& tmp) 处理临时计算结果。 |
常量引用参数 | - 函数参数为const int& value ,防止修改实参,常用于只读场景。- 例: void print(const string& str) 打印字符串,不修改原数据。 |
函数返回引用 | - 不能返回局部变量的引用(局部变量销毁后引用无效)。 - 例: int& getRef(int& d) { return d; } 返回传入变量的引用,修改返回值会影响原变量。 |
枚举
对比项 | 普通枚举 | 强类型枚举(enum class) |
---|---|---|
声明语法 | enum E_Name { A, B, C }; |
enum class E_Name { A, B, C }; |
命名空间 | 枚举项直接暴露在全局作用域 | 枚举项属于枚举类作用域(如E_Name::A ) |
类型安全 | 无类型安全,可与整数混用 | 严格类型安全,禁止与整数隐式转换 |
隐式转换 | 自动转换为int (如int x = A; ) |
需显式转换(如int x = static_cast<int>(E_Name::A); ) |
底层类型 | 默认为int ,不可指定 |
可指定(如enum class E : short { A, B }; ) |
作用域 | 枚举项在全局作用域,可能冲突 | 枚举项在枚举类内,避免冲突 |
跨枚举比较 | 不同枚举项可比较(如A == B ) |
禁止不同枚举类比较(编译错误) |
初始化规则 | 未赋值时从0开始递增(如A=1, B, C →B=2, C=3) |
未赋值时从0开始递增,需显式访问(如E_Name::A ) |
结构体
结构体基本用法
核心要点 | 说明 |
---|---|
声明语法 | struct Student { int age; string name; }; |
初始化方式 | - 聚合初始化:Student s = {18, "张三"}; - 构造函数初始化: Student s(18, "张三"); |
成员访问 | - 普通变量:s.age - 指针变量: s_ptr->name |
内存占用 | 包含所有成员变量的字节和内存对齐填充字节,如struct {int a; char b;} 占8字节(4+1+3填充) |
结构体嵌套与内存对齐
核心要点 | 说明 |
---|---|
嵌套语法 | struct A { struct B { int b; } b; }; |
内存对齐规则 | 1. 成员变量起始地址为自身类型大小的倍数 2. 整体大小为最大成员类型的倍数 |
示例 | struct {double a; int b;} 占16字节(8+4+4填充) |
优化建议 | 按成员大小降序排列,减少填充字节,如double 在前,int 在后 |
构造函数与析构函数
类型 | 构造函数 | 析构函数 |
---|---|---|
声明 | Student() {} / Student(int a) : age(a) {} |
~Student() {} |
作用 | 初始化结构体对象 | 释放对象资源(如堆内存) |
调用时机 | 声明对象时自动调用 | 对象释放时自动调用 |
注意事项 | 可重载,无返回值,函数名与结构体同名 | 不可重载,无参数,函数名前加~ |
结构体与函数
场景 | 说明 |
---|---|
参数传递 | - 值传递:拷贝副本,不影响原对象 - 指针/引用传递:直接操作原对象 |
返回值 | 可返回结构体,但避免返回局部变量(生命周期结束后失效),推荐返回堆内存对象 |
示例 | void func(Student& s) { s.age++; } (引用传递修改原对象) |
结构体数组与指针
核心要点 | 说明 |
---|---|
声明与初始化 | - 静态数组:Student arr[5]; (默认调用无参构造)- 聚合初始化: Student arr[3] = {{18,"A"}, {20,"B"}, {22,"C"}}; - 动态数组(堆): Student* ptr = new Student[5]; (需匹配delete[] 释放) |
指针指向数组 | - 指针指向数组首地址:Student* p = arr; - 指针偏移访问: p->age (首元素)、(p+1)->name (次元素) |
数组元素访问 | - 下标访问:arr[0].age - 指针访问: p[0].age (等价于arr[0].age )、*(p+1).name (等价于arr[1].name ) |
动态内存管理 | - 堆上数组分配:Student* heapArr = new Student[5]{ {18,"A"}, ... }; - 释放: delete[] heapArr; (必须与new[] 配对,否则内存泄漏) |
构造与析构调用 | - 静态数组:声明时调用构造函数,作用域结束时调用析构函数 - 动态数组: new[] 时调用构造函数,delete[] 时调用析构函数 |
指针数组 vs 数组指针 | - 指针数组:Student* ptrArr[3]; (数组元素为结构体指针)- 数组指针: Student (*arrPtr)[5]; (指针指向结构体数组) |
类型别名(typedef/using)
核心要点 | 说明 |
---|---|
语法 | - typedef 原类型 别名; - using 别名 = 原类型; |
作用 | 简化复杂类型书写,提高可读性,如:typedef void (*FuncPtr)(int, double); |
示例 | - 数组别名:typedef int Arr[10]; Arr arr; - 函数指针别名: using FPtr = void(*)(Student&); |
宏
核心概念
要点 | 说明 |
---|---|
本质 | 预处理指令,编译前进行文本替换,无类型检查,类似“文本别名”。 |
定义语法 | #define 宏名 替换内容 (不带参数)#define 宏名(参数) 表达式 (带参数) |
分类与示例
类型 | 示例 | 注意事项 |
---|---|---|
不带参数宏 | #define PI 3.14159 |
常用于定义常量,如PI 、MAX_SIZE 。 |
带参数宏 | #define ADD(x,y) (x+y) |
表达式需加括号(如(x)*(y) ),避免优先级错误。 |
关键用法
- 常量定义:
#define ELEMENT_NUM 100
- 代码简化:
#define PRINT(x) cout << x << endl
- 条件编译:
#ifdef DEBUG cout << "调试模式" << endl; #endif
- 取消定义:
#undef MACRO_NAME
优缺点
优点 | 缺点 |
---|---|
1. 简化重复代码,便于全局修改。 2. 条件编译灵活(适配不同平台/版本)。 |
1. 无类型检查,可能引发隐性错误。 2. 命名冲突风险,缺乏作用域限制。 |
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com