定义:用 virtual
声明,允许派生类重写(覆盖)基类函数,实现运行时多态
核心特性:
class Animal {
public:
virtual void speak() { cout << "Animal speaks" << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "Dog barks" << endl; }
};
定义:在基类中声明但没有实现的虚函数,形式为 virtual 返回类型 函数名() = 0;
核心特性:
class Shape {
public:
virtual double area() const = 0; // 纯虚函数
};
class Circle : public Shape {
public:
double area() const override { return 3.14 * r * r; }
};
若基类析构函数非虚,通过基类指针删除派生类对象时,只会调用基类析构函数,导致派生类独有的资源(如动态内存、文件句柄)泄漏。
class AbstractBase {
public:
virtual ~AbstractBase() = 0; // 声明为纯虚析构函数
};
// 必须提供纯虚析构函数的实现
AbstractBase::~AbstractBase() {
// 基类析构逻辑(可空)
}
class Derived : public AbstractBase {
public:
~Derived() override {
// 派生类析构逻辑
}
};
int main() {
AbstractBase* obj = new Derived();
delete obj; // 正确调用Derived和AbstractBase的析构函数
return 0;
}
允许非成员函数或另一个类访问当前类的私有成员。
class A {
int secret;
friend void printSecret(const A& a); // 友元函数
friend class B; // 友元类
};
void printSecret(const A& a) {
cout << a.secret; // 合法访问私有成员
}
确保一个类只有一个实例,并提供全局访问点,常用于需要全局唯一对象或共享资源的场景。
#include
class Singleton {
public:
// 删除拷贝构造和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 获取唯一实例的静态方法
static Singleton& getInstance() {
static Singleton instance; // C++11 保证局部静态变量的线程安全性
return instance;
}
// 示例方法
void doSomething() {
// 业务逻辑
}
private:
// 私有化构造函数
Singleton() = default;
~Singleton() = default;
};
进程
线程
答:缓存,就是数据交换的缓冲区,是一种用于临时存储数据的高效存储机制,其主要目的是加快访问速度、减轻后台系统压力,从而提升整体性能。我们平时说的缓存大多是指内存。目的是,把读写速度慢的介质的数据保存在读写速度快的介质中(这里的快与慢是相对概念),从而提高读写速度,减少时间消耗。例如:
作用:自动管理动态内存,避免内存泄漏(基于RAII机制)。
常见类型:
unique_ptr
**
std::move
)。shared_ptr
**
weak_ptr
解决)。weak_ptr
**
shared_ptr
循环引用问题。lock()
转为shared_ptr
以访问对象。底层原理:
make_shared
/make_unique
(避免直接new
,更高效且安全)核心机制:通过虚函数表(vtable)和虚表指针(vptr)实现动态绑定。
实现条件:
virtual
关键字)。override
)基类虚函数。栈:由编译器自动管理(隐式分配/释放)。
函数中的局部变量、参数、返回值等由编译器自动压栈(分配)和弹栈(释放),无需手动干预。栈:向低地址方向增长(地址递减)
cpp
void func() {
int a = 10; // 栈上分配,函数结束自动释放
}
堆:需程序员手动管理(显式分配/释放)。堆:向高地址方向增长(地址递增)
通过 new
/malloc
申请内存,delete
/free
释放,忘记释放会导致内存泄漏。
cpp
void func() {
int* p = new int(10); // 堆上分配,需手动 delete
delete p; // 必须显式释放
}
push
/pop
操作),无碎片问题。std::string
的短字符串优化)。“栈由编译器自动管理,分配高效但空间有限,适合局部变量;堆需手动管理,空间大但可能碎片化,适合动态内存需求。栈变量随作用域结束销毁,堆内存需显式释放。实际开发中,优先使用栈,避免不必要的堆分配以提高性能。”
维度 | 堆(Heap) | 栈(Stack) |
---|---|---|
管理方式 | 手动管理(new /malloc 和delete /free ) |
编译器自动管理(压栈/弹栈) |
分配效率 | 低(需动态查找可用内存块) | 高(仅移动栈指针) |
内存大小 | 受系统虚拟内存限制(理论上可达数GB) | 默认较小(如Windows默认1MB,Linux 8MB) |
生命周期 | 由程序员控制(显式释放前一直存在) | 与作用域绑定(如函数结束时自动释放) |
碎片问题 | 可能存在外碎片(频繁分配不同大小内存块) | 无碎片(严格先进后出) |
访问速度 | 较慢(需指针间接访问) | 极快(CPU缓存优化,直接寻址) |
典型用途 | 动态数据结构(链表、树)、大内存对象(图像缓存) | 局部变量、函数参数、临时对象 |
造成堆内存利用率很低的一个主要原因就是内存碎片化。内存碎片化就是计算机程序在运行过程中,频繁地内存分配与释放引起的内存空间不连续性问题,可能导致内存利用率降低甚至无法分配所需的内存。内存碎片主要分为内碎片和外碎片两种类型。
:
•定义:内碎片指已分配的内存块未被实际使用的部分。即程序请求的内存小于分配的内存块大小时,多余的部分形成内碎片。
•产生原因:内存分配器通常按固定的对齐规则分配内存块(如对齐到4字节或8字节),分配大小往往是申请大小的倍数。
•举例:程序需要13字节内存,但内存分配器按16字节对齐规则分配了16字节。多出的3字节就是内碎片
•定义:外碎片是指系统中有足够总量的空间内存,但这些空闲内存不连续,无法满足一个较大的分配请求。
•产生原因:频繁的小内存块的分配和释放导致内存分布变得零散和不连续的小块空闲内存无法自动组合成足够大的连续块。
•举例:系统中有多个小块空闲内存,总量为100MB,但是由于这些空闲内存块彼此不连续,无法分配一个需要50MB的大块。
内存池的固定大小块分配等机制,可以减少有效外碎片,内存池的内存分配策略根据实际需求制定的越精细产生的内碎片越少。ps:内存碎片是不可能减少的。
区域 | 存储内容 | 特点 |
---|---|---|
**.text段** | 编译后的机器代码(函数、指令) | 只读,不可修改 |
**.data段** | 已初始化的全局变量、静态变量(如 int a = 10; ) |
程序启动时加载,生命周期与程序一致 |
**.bss段** | 未初始化的全局变量、静态变量(如 int b; ) |
程序启动时清零,不占磁盘空间 |
堆(Heap) | 动态分配的内存(new /malloc 分配的对象) |
手动管理,需显式释放 |
栈(Stack) | 局部变量、函数参数、返回值等 | 自动管理,随作用域结束释放 |
11. new/delete
vs malloc/free
特性 | new/delete (C++) |
malloc/free (C) |
---|---|---|
本质 | C++运算符 | C标准库函数 |
构造/析构 | 调用对象的构造函数和析构函数 | 仅分配/释放内存,不处理对象生命周期 |
类型安全 | 类型明确(如 new int ),无需计算内存大小 |
需手动计算字节数(如 malloc(sizeof(int)) ) |
异常处理 | 分配失败时抛出 std::bad_alloc 异常 |
返回 NULL ,需手动检查 |
内存对齐 | 按类型对齐规则处理 | 需手动指定对齐方式 |
重载 | 支持运算符重载(自定义内存分配逻辑) | 不可重载 |
示例 | cpp int* p = new int(10); delete p; |
c int* p = malloc(sizeof(int)); free(p); |
内存池是一种预分配内存并进行重复利用的技术,通过减少频繁的动态内存分配与释放操作,从而提高程序运行效率。内存池通常预先分配一块大的内存区域,将其划分为多个小块,每次需要分配内存时直接从这块区域中分配,而不是调用系统的动态分配函数(如new或malloc)。简单来说就是申请一块较大的内存块(不够继续申请),之后将这块内存的管理放在应用层执行,减少系统调用带来的开销。
性能优化:
·1减少动态内存分配开销:系统级内存分配(如malloc/new)需要处理复杂逻辑(如内存合并、碎片整理),导致性能较低,而内存池通过预分配和简单的管理逻辑显著提高了分配和释放的效率。
·2避免内存碎片:动态分配内存会产生内存碎片,尤其在大量小对象频繁分配和释放的场景中,导致的后果就是:当程序长时间运行时,由于所申请的内存块的大小不定,频繁使用时会造成大量的内存碎片从而降低程序和操作系统的性能。内存池通过管理固定大小的内存块,可以有效避免碎片化。
·3降低系统调用频率:系统级内存分配(如malloc)需要进入内核态,频繁调用会有较高的性能开销。内存池通过减少系统调用频率提高程序效率。
确定性(实时性):
·4稳定的分配时间:使用内存池可以使分配和释放操作的耗时更加可控和稳定,适合实时性有严格要求的系统。
高频小对象分配:
·游戏开发:游戏中大量小对象(如粒子、子弹、NPC)的动态分配和释放非常频繁,使用内存池可以显著优化性能。
·网络编程:网络编程中,大量请求和响应对象(如消息报文)和频繁创建和销毁非常适合使用内存池。
·内存管理库:一些容器或数据结构(如std::vector或std::deque)在内部可能使用内存池来优化分配性能。
#include
#include
#include
std::mutex mtx;
int shared_data = 0;
void increment() {
std::lock_guard guard(mtx); // 自动加锁
for (int i = 0; i < 10000; ++i) {
++shared_data;
}
}
int main() {
std::thread t1(increment); //分别是独立的线程
std::thread t2(increment);
t1.join(); //会阻塞主线程(main函数),直到执行完毕
t2.join();
std::cout << "Result: " << shared_data << std::endl; // 正确输出 20000
return 0;
}
互斥锁与自旋锁的区别?
什么是 RAII?它的核心思想是什么?
std::lock_guard
和智能指针均基于 RAII举例说明 RAII 的实际应用场景。
std::unique_ptr
)管理堆内存std::fstream
)自动关闭文件std::lock_guard
)自动加锁/解锁深入问题:
std::vector
)自动管理元素内存;智能指针封装动态资源