深拷贝和浅拷贝
浅拷贝:拷贝对象的成员变量,如果成员变量是指针,仅拷贝指针的地址(指针的指向),而非指针指向的内容。------可能会导致同一块内存多次释放,导致程序崩溃。导致悬空指针
深拷贝:拷贝对象的成员变量,如果成员变量是指针,不仅拷贝指针的地址,而且重新分配内存并复制指针指向的内容
浅拷贝(Shallow Copy) | 深拷贝(Deep Copy) | |
---|---|---|
指针成员的拷贝方式 | 只拷贝指针地址,不分配新内存 | 重新分配新内存,并复制内容 |
优点 | 快速、节省内存 | 互不干扰,避免内存问题 |
缺点 | 可能导致 悬空指针、二次释放 | 需要额外的内存分配,效率稍低 |
适用场景 | 适用于 不涉及动态内存 的类 | 适用于 涉及动态内存 的类 |
悬空指针:
指向已经释放或者无效的内存。
悬空指针(Dangling Pointer) | 空指针(Null Pointer) | |
---|---|---|
定义 | 指向 已被释放 或 无效 的内存 | 指向 nullptr(空地址,表示无效) |
是否安全 | ❌ 不安全,可能导致程序崩溃 | ✅ 安全,nullptr 可以被安全检测 |
示例 | int* p = new int(10); delete p; cout << *p; |
int* p = nullptr; cout << *p; // 会崩溃,但可以检查 |
解决方案 | 释放后设为 nullptr |
直接检查 if (p == nullptr) |
野指针:
-
指针从未被正确初始化,指向一个随机的内存地址。
-
野指针的值是无效的,访问它可能导致程序崩溃、段错误或不可预测的行为。
堆和栈:
栈和堆的对比
特性 | 栈(Stack) | 堆(Heap) |
---|---|---|
内存分配 | 自动分配和释放 | 动态分配和释放,需要手动管理 |
生命周期 | 仅限于函数作用域 | 由程序员控制,直到显式释放 |
分配速度 | 快 | 慢 |
内存大小 | 有限(通常几MB) | 通常较大(受限于系统内存) |
内存碎片 | 无 | 可能出现碎片化 |
使用场景 | 局部变量、函数调用 | 动态数据结构、大型对象 |
左值引用和右值引用
-
左值引用:是指向已分配内存并且生命周期较长的对象,例如有名字的变量。你可以通过左值引用修改这个对象的值。
-
右值引用:是指向临时的对象,其生命周期通常很短。右值引用常用于完美转发,通过
std::move
可以将临时对象的资源“转移”而非复制,从而减少内存占用和提高性能。 -
例子:
#include <iostream>
#include <utility> // std::move
void processLeftValue(int& lvalue) {
std::cout << "Processing left value: " << lvalue << std::endl;
}
void processRightValue(int&& rvalue) {
std::cout << "Processing right value: " << rvalue << std::endl;
}
int main() {
int x = 5;
processLeftValue(x); // 左值引用
processRightValue(10); // 右值引用
processRightValue(std::move(x)); // 使用 std::move 转换为右值引用
return 0;
}
静态库
-
优点:运行效率高、可移植性好、安全性高、减少依赖。
-
缺点:可执行文件体积大、更新和维护困难、内存占用高、不利于共享。
动态库
-
优点:节省内存、可执行文件体积小、更新和维护方便、支持插件机制。
-
缺点:运行时依赖、运行时开销、安全性问题、版本兼容性问题。
面向对象的核心思想
面向对象的核心思想是通过类和对象来模拟现实世界中的事物和事物之间的关系。它强调以下几点:
封装:将数据和操作数据的方法封装到一个逻辑单元(类)中。
抽象:通过类的接口隐藏内部实现细节,只提供必要的操作。
继承:通过继承实现代码复用,减少重复代码。
态多:通过虚函数和继承实现动态绑定,使得程序可以灵活地处理不同类型的对象。
多态的理解
静态多态:
函数重载:可以定义多个同名函数但是参数类型或者个数不同
运算重载:将已有的运算符定义新的操作。operator
动态多态:
继承和虚函数实现的:在父类(基类)中声明virtual的虚函数,子类继承父类重写父类定义的虚函数,这种行为叫动态多态
重载(Overloading)
定义
重载是指在同一个作用域内,允许定义多个同名函数或运算符,只要它们的参数列表(参数的类型、数量或顺序)不同即可。重载的目的是提供更直观、更灵活的接口。
特点
同名函数或运算符:函数或运算符的名称相同。
参数列表不同:参数的类型、数量或顺序必须不同。
返回类型无关:重载函数的返回类型可以相同,也可以不同,但不能仅通过返回类型来区分重载函数。
作用域相同:重载的函数必须在同一个作用域内。
重写(Overriding)
定义
重写是指在派生类(子类)中重新定义基类(父类)中的虚函数(Virtual Function),从而改变基类的行为。重写是实现多态的基础。
特点
虚函数:必须在基类中定义虚函数,并在派生类中重写它。
函数签名相同:重写的函数必须与基类中的虚函数具有相同的名称、参数列表和返回类型。
作用域不同:重写的函数在派生类中,而被重写的函数在基类中。
多态性:通过基类指针或引用调用虚函数时,会根据对象的实际类型调用相应的重写函数。
-
向上转型(Upcasting):
-
通常是安全的。
-
可以使用隐式转换或
static_cast
。
-
-
向下转型(Downcasting):
-
是不安全的,需要谨慎使用。
-
static_cast
用于向下转型,但不会进行运行时检查。 -
dynamic_cast
用于安全的向下转型,会在运行时检查对象的实际类型。
-
static_cast 和 dynamic_cast
特性 | static_cast |
dynamic_cast |
---|---|---|
用途 | 基本类型转换、类继承体系中的转换 | 主要用于类继承体系中的向下转型 |
安全性 | 不安全,不进行运行时检查 | 安全,进行运行时检查 |
运行机制 | 编译时检查,无运行时开销 | 运行时检查,依赖RTTI |
性能 | 高性能,无运行时开销 | 性能低,有运行时开销 |
适用范围 | 所有可以隐式转换的类型 | 具有继承关系的类,基类需有虚函数 |
返回值 | 转换结果 | 指针:nullptr (失败) |
四种类型转换:
转换操作符 | 用途 | 安全性 | 运行机制 | 适用范围 |
---|---|---|---|---|
static_cast |
基本类型转换、类继承体系转换 | 不安全 | 编译时检查 | 基本类型、类继承体系 |
dynam |