一、裸指针
“裸指针”是最基础的,直接存储内存地址的指针类型。特点:①它本身没有自动的内存管理机制:如它不会自动释放内存,也不会检查是否指向有效的内存区域;②直接操作内存地址,不进行任何的边界检查:如空指针解引用。
int main()
{
int x = 10;
int*p = &x; //裸指针p指向x
cout << *p << endl; //输出x的值,10
return 0;
}
1、动态内存分配:new 与 malloc 详解
在 C++ 中,动态内存分配是指在程序运行时(而非编译时)从堆(heap)上分配内存。主要有两种方式:new/delete:C++ 的运算符 malloc/free:C 标准库函数。
①new在堆分配的内存是无名的,返回一个指向该对象的指针。
//分配单个对象
int* p = new int; //p指向一个动态分配的、未初始化的无名对象
int* p2 = new int(42); //分配并初始化为42
int* p3 = new int(); //值初始化,*p3=0
//分配数组
int* arr = new int[10];//分配10个int的数组
//释放内存
delete p;
delete[] arr;
②malloc返回void*,需要强制类型转换。
//分配内存
int* p = (int*)malloc(sizeof(int)); //分配一个int
int* arr = (int*)malloc(10 * sizeof(int)); //分配10个int的数组
//释放内存
free(p);
free(arr);
③两者区别:
a.基本内存分配与初始化:new支持初始化;malloc不初始化,分配后必须手动赋值,否则读取是未定义的行为。
b.类对象的内存管理:new自动调用构造函数,delete自动调用析构函数;malloc只是分配内存,需要配合placement new使用,且必须手动调用析构函数。
c.数组处理:new[]和delete[]配合使用;malloc需要手动计算数组大小、初始化每个元素。
d.内存分配失败处理:new在失败时抛出异常(std::bad_alloc);malloc在失败时返回NULL/nullptr。
e.与realloc的配合:
//new没有直接对应的realloc功能,需要手动实现:
int* arrNew = new int[5];
int* newArrNew = new int[10];
std::copy(arrNew, arrNew + 5, newArrNew); //将 arrNew 的前5个元素复制到 newArrNew
delete[] arrNew;
arrNew = newArrNew;
//malloc可以配合realloc使用
int* arr = (int*)malloc(5 * sizeof(int));
// 扩展内存到10个int
int* newArr = (int*)realloc(arr, 10 * sizeof(int));
if (newArr)
{
arr = newArr;
arr[8] = 42; //使用新分配的空间
}
free(arr);
2、malloc两种内存分配方式
①brk()方式:分配小块内存(通常是小于128KB),通过brk()系统调用会将堆顶指针向更高的内存地址移动,获取内存空间。被free释放时,并不会将内存归还给操作系统,而是将其缓冲在内存池中,供后续分配请求复用。
优点:直接操作堆,内存分配速度较快。缺点:频繁使用,堆内将产生越来越多不可用的碎片,导致“内存泄漏”。
②mmap()方式:当用户请求的内存较大时(通常大于128KB),mmap()在进程的虚拟地址空间中(堆和栈的中间,称为文件映射区域的地方)找一块空闲的虚拟内存。free释放时,会把内存归还给操作系统,内存得到真正释放。
缺点:向操作系统申请内存,是通过系统调用的,执行系统调用是要进入内核态的,然后回到用户态,运行态的切换会耗费不少时间。分配的内存释放都会归还给操作系统。所以每次分配的虚拟地址都是缺页状态,在第一次访问该虚拟地址的时候,就会触发缺页中断。
二、智能指针
“智能指针”是原始指针的封装,解决了裸指针带来的内存管理问题,可以自动管理内存的生命周期。
1、unique_ptr “独占所有权”
①三种创建方式
#include <iostream>
using namespace std;
#include <memory>
class MyClass {
public:
MyClass() { cout << "MyClass created!" << endl; }
MyClass(int x, double y) {} // 带参数的构造函数
~MyClass() { cout << "MyClass destroyed!" << endl; }
};
int main()
{
//创建unique_ptr的三种方式:
//1、使用make_unique "推荐!!!"
unique_ptr<MyClass> ptr = make_unique<MyClass>();//函数模板必须使用函数调用运算符 () 来调用。
auto ptr1 = make_unique<MyClass>(42, 3.14); // 正确:传递参数
//2、使用new运算符
unique_ptr<MyClass> ptr2(new MyClass());
//3、使用unique_ptr的构造函数
MyClass* rawPtr = new MyClass();
unique_ptr<MyClass> ptr3(rawPtr);
return 0;
}
②成员函数get()获取原始指针(裸指针)
MyClass* rawPtr = ptr.get(); //像普通指针一样使用它
注意:get()不改变所有权,unique_ptr仍然拥有资源的所有权,并在销毁时自动释放资源,手动对rawPtr调用delete会导致双重释放,尽量避免滥用get()。
③调用成员函数和解引用
④不可以copy只可以move
不允许复制是为了保证资源管理的唯一性。如果允许就会有多个指针共享相同的资源,导致双重释放等问题。
⑤reset()清空智能指针
reset方法将智能指针指向的对象释放,并将智能指针设置为nullptr。unique_ptr是独占拥有权,因此它会立即释放资源。
⑥作函数参数,值/引用/常量引用传递/右值引用
按值传递:传递所有权。
按引用传递:
、
常量引用传递时,函数无法修改指针的状态。
右值引用:值传递会触发一次移动构造,右值引用直接绑定到传入的右值,没有额外移动
传递所有权。