面向对象
在C++中,创建对象的两种方式有本质的不同,分别代表了在栈上和在堆上创建对象
1. SpeechManager* sm = new SpeechManager();
堆上创建对象(动态分配内存):
- 使用
new
运算符,意味着SpeechManager
对象是在堆上分配的 - 返回类型是
SpeechManager*
,即指向SpeechManager
对象的指针 - 需要显式地释放内存,以避免内存泄漏,通常使用
delete sm;
2. SpeechManager sm;
栈上创建对象(自动分配内存):
sm
是一个SpeechManager
类型的对象,不是指针- 不需要手动释放内存,自动管理内存减少了内存泄漏的风险
- 对象的生命周期受限于其作用域,当该作用域结束时(比如函数返回时),对象会被自动销毁,内存会被自动释放
全局变量\局部变量\静态局部变量
全局变量:
- 作⽤域:整个程序
- ⽣命周期:与程序的⽣命周期相同
- 使用场景:当多个函数需要共享相同的数据时,可以使⽤全局变量
局部变量:
- 作⽤域:限定在定义它的块(⼤括号内)
- ⽣命周期:在块结束时销毁
- 使用场景:当变量只在某个特定作⽤域内有效,并且不需要其他作⽤域访问时,可以使⽤局部变量
静态局部变量:
- 作⽤域:限定在定义它的函数内
- ⽣命周期:与程序的⽣命周期相同,但只能在定义它的函数内部访问
- 关键字:使⽤
static
关键字修饰 - 初始化:仅在第⼀次调⽤函数时初始化,之后保持其值
- 使用场景:当希望在函数调⽤之间保留变量的值(即“静态”),并且不希望其他函数访问这个变量时(即“局部”),可以使⽤静态局部变量
局部变量与静态局部变量的区别:
局部变量在每次函数调用时都会重新创建,并且初始化为指定的初值。但是,静态局部变量只会在第一次调用函数时初始化一次,之后它的值会保留下来,直到程序结束。
静态局部变量: 只初始化一次,之后保持其值,因此输出为 1 2
#include <iostream>
using namespace std;
void test01() {
static int count = 0; // 静态局部变量
count++;
cout << "Count: " << count << endl;
}
int main() {
test01();
test01();
return 0;
}
局部变量: 每次调用函数都要初始化,因此输出为 1 1
#include <iostream>
using namespace std;
void test01() {
int count = 0; // 局部变量
count++;
cout << count << endl;
}
int main() {
test01();
test01();
return 0;
}
&的特性
在C++中,取地址符&和引用符&看似相同,但它们在不同的上下文中有不同的含义
1.取地址符:当你在一个变量前面使用&时,它表示获取该变量的内存地址,例如:
int a = 10;
int* ptr = &a; // ptr 现在指向 a 的地址
2.引用符:当你在一个变量前面使用&时,它表示获取该变量的内存地址,例如:
int b = 20;
int &ref = b; // ref 是 b 的引用,现在 ref 和 b 指向同一个内存
或者:
void increment2(int &value) {
value += 1;
}
引用本质上是一个别名,它指向原始对象而不是创建该对象的副本
当你将一个对象传递给函数或在循环中使用时,如果不使用引用,通常会发生复制,即创建一个新的实例。对于大对象(如复杂的数据结构、类实例等),这种复制过程可能会消耗较多的内存和处理时间
通过使用引用,你仅仅是在操作原始对象,而不是其副本,这意味着:
- 没有额外的内存分配(除了引用本身占用的少量内存)
- 避免了复制构造函数的调用,提高了程序的性能
函数指针
函数指针是指向函数的指针变量,它可以存储函数的地址并允许通过该指针调用函数
返回类型 (*指针变量名)(参数类型1, 参数类型2, ...);
假设有一个简单的函数:
int add(int a, int b) {
return a + b;
}
我们可以定义一个指向 add
函数的函数指针:int (*funcPtr)(int, int) = add;
然后可以通过这个指针调用 add
函数:int result = funcPtr(3, 4);
ps:在 C/C++ 中,函数名本身就代表了其地址,因此不需要使用取地址符 &
指针和成员访问
在 C++ 中,指针指向某个对象时,有两种方式可以访问该对象的成员:ListNode *cur = list;
1.使用解引用和点操作符:
(*cur).val;
这行代码首先解引用指针cur
,然后通过.
操作符访问val
成员。虽然这在语法上是正确的,但相对较冗长
2.使用箭头操作符:
cur->val;
箭头操作符 ->
是专门为指向对象的指针设计的,它同时完成解引用和成员访问的操作
深拷贝 / 浅拷贝
深拷贝和浅拷贝是对象复制的两种不同方式,它们的主要区别在于如何处理对象内部的指针和动态分配的内存
1. 浅拷贝(Shallow Copy)
浅拷贝会复制对象的所有成员,包括指针。对于指针成员,浅拷贝仅复制指针的值(地址),而不复制指针指向的对象
- 复制后,源对象和目标对象指向同一块内存区域
- 如果其中一个对象被修改,另一个对象也会受到影响,因为它们共享同一数据
- 当两个对象被销毁时,它们都会尝试释放相同的内存区域,可能导致双重释放(double free)的问题,进而引发程序崩溃或未定义行为
2. 深拷贝(Deep Copy)
深拷贝会复制对象的所有成员,包括指针指向的内存区域。对于指针成员,深拷贝不仅复制指针的值,还会为指针指向的对象分配新的内存空间,并复制其内容
- 复制后,源对象和目标对象各自拥有独立的内存区域
- 修改一个对象不会影响另一个对象,因为它们有不同的数据副本
- 需要确保在析构函数中正确释放每个对象的动态内存,以避免内存泄漏
- 在深拷贝时,新的内存通常是通过
new
操作符分配在堆上,而不是栈上
优先队列 / 堆
堆(Heap):
- 堆是一种特定的数据结构,通常用于实现优先队列
- 堆可以是最大堆或最小堆,最大堆中的每个节点的值都大于或等于其子节点的值,而最小堆则相反
- 堆的性质使得插入和删除最大(或最小)元素的操作都能在对数时间复杂度内完成
优先队列(Priority Queue):
- 优先队列是一种抽象数据类型,支持按优先级排序元素的操作
- 与普通队列不同,在优先队列中,元素的出队顺序是基于它们的优先级,而不是它们被插入的顺序
- 常见操作包括插入元素(
push
)、获取并删除最高优先级元素(pop
)和查看最高优先级元素(top
) - 优先队列通常使用堆作为其内部数据结构来高效地支持优先级操作
- 在 C++ STL 中,
std::priority_queue
就是基于堆的实现 - 优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系