指针和引用
本质含义:
指针是一个变量,内部存储着内存地址,指向内存中的某个位置,通过指针,我们可以间接地引用和操作内存中的数据。 引用只是一个别名,是一个已经存在变量的别名,引用和原变量共享同一个内存地址,因此对引用的操作实际上是对原变量的操作。
定义和声明:
指针需要特定的语法定义和声明,*号表示指针类型,需要使用取地址符&来获取变量的额地址。 引用& 来表示引用类型,通常在声明时直接给引用赋值。
空值以及可变性:
指针可以具有空值(null),即指向空地址或者未初始化的指针,指针的值可以修改,可以重新分配给其他地址。 引用必须在声明的时候进行初始化,不能为空,一旦引用被初始化为引用某个对象,就会一直引用该对象,不可更改。
操作
指针可以进行指针运算,如加减等,可以使用解引用操作符(*)来获取指针指向对象的值。指针可以有多级 引用相当于变量本身,可以直接使用引用进行操作。
传参
引用使用在源代码级相当于普通变量一样使用,做函数参数时,内部传递是实际变量地址, 指针传参的时候还是值传递,视图修改传进来的指针的值是不可以的额,只能修改指针变量中的地址所在内存大额数据, 引用传参的时候,传进来的是变量本身,因此可以被修改。
野指针
- 野指针也是一个指针,这个指针指向的内存区域不确定。
- 原因有多种,常见定义没有初始化,或者在free 和delete 未赋值为NULL
- 规避初始化为NULL,释放后为NULL
智能指针
智能指针的目的是用于帮助确保程序不会出现内存和资源泄露,并具有异常安全。他们对RAII或者获取资源即初始化编程用法至关重要,确保资源获取和对象初始化同时发生。为将任何堆分配资源的所有权给其析构函数包含用于删除或释放资源的代码以及任何相关清理代码的堆栈分配对象。
shared_ptr
- shared_ptr 多个指针用来指向相同的对象,shared_ptr 使用引用计数,注意的是引用计数不是垃圾回收,引用计数能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待,更能够清晰表明资源的生命周期。
- 每一个shared_ptr 的拷贝都指向相同的内存,使用对象一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减到0的时候,自动删除对象所在的内存,消除显式的调用delete
- shared_ptr 内部的引用计数是线程安全的,但是对象的读取要加锁。可以通过get()获取原始指针,通过reset()来减少一个引用,通过use_count()来查看某个对象的引用计数。
- 注意避免虚循环引用,这样会导致堆内存无法正确释放,导致内存泄露,通过weak_ptr 解决。
- 当进行拷贝或赋值时候,每个shared_ptr 都会记录有多少个其他shared_ptr 指向相同的对象,每个shared_ptr 都有一个关联的引用计数器,当我们拷贝或赋值一个shared_ptr时,该shared_ptr 指向的对象引用计数会递增,当我们给shared_ptr 赋予一个新值或者被销毁,计数器会递减。
unique_ptr
unique_ptr 不能与其他类型的指针共享同一个对象的内存,是一种独占智能指针,保证代码的安全性。如果要更改对象的指向,可以使用标准库中std::move 函数来转移所有权。 一旦转移成功就不能再使用原来的unique_ptr,从实现上来讲,unique_ptr是一个删除了拷贝构造函数,保留了移动构造函数的指针来封装类型,使用右值对unique_ptr 对象来进行构造。 一旦扣在成功,右值对象中指针被窃取,失去了对指针的所有权。 一开始C++ 11中没有 std::make_unique 据说是忘记了
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique(Args&& ...args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
weak_ptr
shared_ptr 不够安全,如果两个shared_ptr对象互相指向对方,那么将会导致循环引用,两个对象的引用计数都会加1,因此当shared_ptr 对象的生存周期已到时候,引用计数并不减为0,也就不会回收内存,从而造成内存泄露
- weak_ptr 可以指向shared_ptr 指针指向对象内存,缺并不拥有该内存,使用weak_ptr 成员lock,可以返回指向内存的一个shared_ptr 对象。
- 在所指对象内存已经无效时候,返回指针空值nullptr,
自己实现自定义智能指针
#include <iostream>
template <typename T>
class SharedPtr;
template <typename T>
class UniquePtr;
template <typename T>
class WeakPtr;
template <typename T>
class SharedPtr {
public:
SharedPtr() : ptr(nullptr), refCount(nullptr), weakCount(nullptr) {}
explicit SharedPtr(T* ptr) : ptr(ptr), refCount(new size_t(1)), weakCount(new size_t(1)) {}
SharedPtr(T* ptr,size_t * refCount,size_t *weakCount):ptr(ptr),refCount(refCount),weakCount(weakCount){}
~SharedPtr() {
release();
}
SharedPtr(const SharedPtr<T>& other) : ptr(other.ptr), refCount(other.refCount), weakCount(other.weakCount) {
if(this != &other)
increaseRefCount();
}
SharedPtr<T>& operator=(const SharedPtr<T>& other) {
if (this != &other) {
release();
ptr = other.ptr;
refCount = other.refCount;
weakCount = other.weakCount;
increaseRefCount();
}
return *this;
}
T& operator*() const {
return *ptr;
}
T* operator->() const {
return ptr;
}
size_t use_count() const {
return refCount ? *refCount : 0;
}
bool expired() const {
return use_count() == 0;
}
WeakPtr<T> weak_ptr() const {
return WeakPtr<T>(*this);
}
explicit operator bool() const {
return ptr != nullptr;
}
void reset() {
if (ptr) {
decreaseRefCount();
ptr = nullptr;
if (use_count() == 0 && *weakCount == 0) {
delete ptr;
delete refCount;
delete weakCount;
refCount = nullptr;
weakCount = nullptr;
}
}
}
private:
void decreaseRefCount() {
if (refCount) {
--(*refCount);
if (use_count() == 0) {
delete weakCount;
weakCount = nullptr;
if (weakCount && *weakCount == 0) {
delete weakCount;
weakCount = nullptr;
}
}
}
}
void increaseRefCount() {
if (refCount)
++(*refCount);
}
void release() {
decreaseRefCount();
if (use_count() == 0 && weakCount && *weakCount == 0) {
delete ptr;
delete refCount;
delete weakCount;
ptr = nullptr;
refCount = nullptr;
weakCount = nullptr;
}
}
template <typename U>
friend class UniquePtr;
template <typename U>
friend class WeakPtr;
T* ptr;
size_t* refCount;
size_t* weakCount;
};
template <typename T>
class UniquePtr {
public:
UniquePtr() : ptr(nullptr) {}
explicit UniquePtr(T* ptr) : ptr(ptr) {}
~UniquePtr() {
release();
}
UniquePtr(const UniquePtr<T>& other) = delete;
UniquePtr<T>& operator=(const UniquePtr<T>&) = delete;
UniquePtr(UniquePtr<T>&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
UniquePtr<T>& operator=(UniquePtr<T>&& other) noexcept {
if (this != &other) {
release();
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}
T& operator*() const {
return *ptr;
}
T* operator->() const {
return ptr;
}
T* Release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}
private:
void release() {
if (ptr) {
delete ptr;
ptr = nullptr;
}
}
template <typename U>
friend class SharedPtr;
T* ptr;
};
template <typename T>
class WeakPtr {
public:
WeakPtr() : sharedPtr(nullptr) {}
explicit WeakPtr(const SharedPtr<T>& sharedPtr) : sharedPtr(sharedPtr.ptr,sharedPtr.refCount,sharedPtr.weakCount){
if (sharedPtr.use_count() > 0) {
++(*sharedPtr.weakCount);
}
}
// explicit WeakPtr(const SharedPtr<T>& sharedPtr) : sharedPtr(sharedPtr) {
//// if (sharedPtr.use_count() > 0) {
//// ++(*sharedPtr.weakCount);
//// }
// }
WeakPtr(const WeakPtr<T>& other) : sharedPtr(other.sharedPtr) {
if (sharedPtr.use_count() > 0) {
++(*sharedPtr.weakCount);
}
}
WeakPtr<T>& operator=(const WeakPtr<T>& other) {
if (this != &other) {
release();
sharedPtr = other.sharedPtr;
if (sharedPtr.use_count() > 0) {
++(*sharedPtr.weakCount);
}
}
return *this;
}
~WeakPtr() {
release();
}
SharedPtr<T> lock() const {
if (expired()) {
return SharedPtr<T>();
} else {
return SharedPtr<T>(sharedPtr);
}
}
bool expired() const {
return sharedPtr.use_count() == 0;
}
private:
void release() {
if (sharedPtr.use_count() > 0) {
--(*sharedPtr.weakCount);
if (sharedPtr.use_count() == 0 && *sharedPtr.weakCount == 0) {
delete sharedPtr.weakCount;
sharedPtr.weakCount = nullptr;
}
}
}
SharedPtr<T> sharedPtr;
};
struct Resource {
Resource() {
std::cout << "Resource acquired" << std::endl;
}
~Resource() {
std::cout << "Resource released" << std::endl;
}
void DoSomething() {
std::cout << "Doing something with the resource" << std::endl;
}
};
int main() {
// 使用 SharedPtr
SharedPtr<Resource> sharedPtr(new Resource);
std::cout << "Shared pointer use count: " << sharedPtr.use_count() << std::endl;
// 使用 UniquePtr
UniquePtr<Resource> uniquePtr(new Resource);
uniquePtr->DoSomething();
std::cout << "Shared pointer use count: " << sharedPtr.use_count() << std::endl;
// 使用 WeakPtr
WeakPtr<Resource> weakPtr(sharedPtr);
std::cout << "Weak pointer expired: " << weakPtr.expired() << std::endl;
std::cout << "Shared pointer use count: " << sharedPtr.use_count() << std::endl;
{
// 创建 SharedPtr 来引用资源
SharedPtr<Resource> sharedPtr2 = weakPtr.lock();
if (sharedPtr2) {
std::cout << "Weak pointer successfully locked" << std::endl;
sharedPtr2->DoSomething();
std::cout << "Shared pointer use count: " << sharedPtr.use_count() << std::endl;
} else {
std::cout << "Weak pointer expired" << std::endl;
}
}
std::cout << "Shared pointer use count: " << sharedPtr.use_count() << std::endl;
return 0;
}
输出结果
Resource acquired Shared pointer use count: 1 Resource acquired Doing something with the resource Shared pointer use count: 1 Weak pointer expired: 0 Shared pointer use count: 1 Weak pointer successfully locked Doing something with the resource Shared pointer use count: 2 Shared pointer use count: 1 Resource released
总结
指针是一个独立变量,存储着内存地址,可以通过间接引用来操作数据。引用也是已经存在变量的别名,共同使用一个内存地址,直接操作来访问双数据。 指针更注重底层的内存管理和灵活性。
malloc/free
malloc/free 是标准库函数,用于动态申请内存和释放内存,molloc 不在编译器控制权限之内,不能够将构造函数和析构函数的任务强加于malloc/free, 另外malloc 返回的是void 指针,分配内存按照指定的大小,可以用realloc 扩容。 malloc 和free 在单线程环境下是线程安全的,因为他们不维护任何和线程相关的状态信息,但是在多线程中,需要额外的同步机制来保护同一块内存的并发访问, 以避免潜在的竞态条件和数据损坏。可以通过互斥锁,读写锁或者其他线程机制来实现。
realloc 原理
- realloc 首先检查传入的指针参数,如果为null,等于malloc(size)即分配一个新的内存块并返回指向他的指针。
- 如果指针不为NULL,realloc 会尝试重新分配给定大小的内存块,检查当前内存块的相邻空间是否满足新的大小需求。
- 如果相邻的空间足够大,并且内存块可以在原地调整大小,realloc 会在原始内存上进行调整,并返回指针。
- 如果不满足,realloc 会分配一个足够大的内存空间,并将原始内存的内容复制到新的内存块中。
- 最后,realloc 会释放原始内存块,并返回新的内存块指针。
注意
- 性能受到底层内存管理实现的影响,不同的内存分配器采用不同策略和算法来处理。
- 对于大的内存调整,可能会引发内存的拷贝和复制,导致性能下降,解决方案使用其他数据结构如链表,或者内存管理技术如自动内存管理,内存池,对象池,ne
new和delete,new[] 和 delete[]
- new 和 delete 是c++ 的运算操作符,new 用来处理动态分配单个对象的内存,并调用对象的构造函数进行初始化。delete是操作符释放new 分配的内存,并调用对象的析构函数进行处理
- new[] 用于动态分配数组对象的内存,并调用每个对象的构造函数进行初始化,delete[]操作符是释放有new[] 分配的数组,并调用对象的析构函数进行释放
- 在单线程情况下是线程安全的,使用了同一块内存,不涉及多个对象的并发访问。
- 这个四个可以被重载,可以使用自定义的内存分配和释放策略,避免内存泄露和潜在的错误。
如何确定new 申请空间成功,delete 删除成功
- 可以初始话一个指针int *ptr = new int ,设置指针为null, 如果分配成功则不为空,否则为空
- 使用try {}catch(const std::bad_alloc & e){内存申请失败} int *p = new int[5] delete[] p;
- delete 一个指针,删除了指针所指向的目标,释放了它所占的内存,而不是删除指针本身,所以删除后将指针=nullptr 避免悬空。
申请一个超过剩余内存的空间,或发生什么问题
- 申请失败,std::bad_alloc,返回空指针
- 内存耗尽,可能会引起虚拟内存重新分配,将部分内存数据放在磁盘中。
- 原因有多种:1.内存碎片化,2.内存泄露 3并发访问冲突,4.内存耗尽。
- 有些编译器通过 new_handler 处理异常,可以直接 std::nothrow
new、operator new与placement new
- new 是c++ 用于动态分配的单个对象的关键字,在堆上分配内存并调用对象的构造函数,在使用 new分配内存时,编译器会自动调用 operator new 来执行实际的内存分配操作,然后调用构造函数初始化对象。
- operator new : operator new 是一个特殊的全局函数,用于执行内存分配,接受分配内存大小作为参数,并返回指向分配内存的指针,operator mew 分配的是原始的未初始化的内存,不会调用任何构造函数。
viod *memory = operator new(sizeof(MyClass));
- placement new 是一个特殊的语法,在已分配的内存上构造对象,可以接受一个指向预分配内存指针,并在该内存上调用对象的构造函数。
void *memory = operator new (sizeof(MyClass));
MyClass *obj = new (memory) MyClass();
obj->~MyClass();
operator delete(memory)