这里写目录标题
1.内存分区
一般分为五个区
五个区 | 存放 |
---|---|
栈 | 保存定义在函数内的非static对象,const 修饰的局部变量[声明周期结束由编译器自动销毁] |
堆 | 存储动态分配对象(new,malloc )[需要手动释放] |
全局/静态存储区 | 保存局部static对象,类static对象以及定义在任何函数之外的对象(全局) [程序结束编译器自动销毁] |
常量存储区 | 存放常量字符串,const 修饰的全局变量. |
代码区 | 存放函数体(类的成员函数、全局函数)的二进制代码; |
int a = 0;
const int b = 0;
const char* c = "hello";
const char* d = "hello";
static int e = 0;
const static int f = 0;
cout << &a << endl; //010FF96C
cout << &b << endl; //010FF960 //说明静态局部变量存储在栈中
cout << &c << endl; //010FF954 //这是个误区,这样输出其实是输出指针的地址,指针存放在栈中
cout << static_cast<const void *>(c)<< endl; //00612B3C //这样才是输出字符串存放的地址
cout << static_cast<const void *>(d)<< endl; //00612B3C //说明指向同一个常量字符串的指针地址相同
cout << &e << endl; //006192E4
cout << &f << endl; //00612B30 //说明const修饰的全局(静态)变量也是放在常量存储区
注意:
- 指针本身存在调用栈中,指针指的数据在堆中。
- 指向同一个常量字符串的指针地址相同
2.智能指针
1. shared_ptr
允许多个指针指向同一个对象,内部有一个引用计数.当引用指针为0时,会调用对象的析构函数释放空间
- 递增
- 当作为一个shared_ptr初始化另外一个shared_ptr
- 作为参数传递给一个函数以及作为函数的返回值
- 递减
- 给一个shared_ptr附上一个新值
- shared_ptr被销毁
- 局部的shared_ptr离开作用域
//智能指针均为模板类
//使用*之前一定要初始化后,而且初始化值不能为空
shared_ptr<int> sptr/shared_ptr<int> sptr(nullptr);;
*sptr = 1; //不可以,这时候sptr为nullptr,不能进行解引用
cout << *sptr;
int a = 0;
shared_ptr<int> sptr(new int(a));
*sptr = 1; //可行,因为sptr已经不为空了,解引用不会报错
cout << *sptr;
//如果是自带初始化的,就可以直接用*解引用
shared_ptr<string> sptr;
if (sptr && sptr->empty())
*sptr = "hello";
//技术
智能指针 = 另一个智能指针 //左边计数-1,右边计数+1
2.函数
//1.make_shared:最安全的分配和使用动态内存的,可以用类型的构造函数
shared_ptr<int> sptr = make_shared<int>(42);
cout << *sptr << endl;
//也可以用auto
auto sptr = make_shared<int>(42);
cout << *sptr << endl;
//初始化和函数
shared_ptr<T> p(q) //p管理内置指针q的对象,q必须是new分配的内存.且能转为T*型;
shared_ptr<T> p(u) //p从unique_ptr那里抢过来对象的所有权,将u置空
shared_ptr<T> p(q,d) //p接管了内置指针q所指向的对象的所有权.q必须能转为T*型.p将使用可调用对象d来代替delete;
shared_ptr<T> p(p2,d) //p是p2的拷贝,区别在于p可调用对象d来代替delete
p.get(); //返回一个内置指针,指向智能指针管理对象.!少用!;如果被清理,智能指针也会变空悬指针
p.reset(); //reset释放p
p.reset(q); //调用delete,释放完后指向q
p.reset(q,d); //调用d释放p,再指向d
3.生存周期
- 可以把他当初一个局部变量
- 只有一种情况会不会释放,就是把指针放在容器中,但是结束的时候没有把他删除掉;用完记得erase()
2. unique_ptr
只允许当前指针独占对象,当unique_ptr被销毁,那么指向的对象也会被销毁
- 没有make_shared函数类型的函数,只能通过new来完成绑定
- 不支持普通的拷贝或者赋值
//直接初始化
unique_ptr<int> p2(new int(42)); //正确,直接初始化
//初始化和函数
unique_ptr<T> u1 //空指针,可以指向T*的对象
unique_ptr<T,D> u2 //u2可以调用D来释放它的对象
unique_ptr<T.D> u(d) //空unique,指向类型为T*的对象,用类型为D的对象d代替delete
u = nullptr; //直接释放对象,变为空指针
u.release(); //u放弃对指针的控制权,返回指针,将u置空, 指针被返回,可用于转移.
//如果不是赋值智能指针,那么记得释放内存
p.reset(); //reset释放p
p.reset(nullptr); //释放p,置空
p.reset(q); //调用delete,释放完后指向q,可用于转移
传递和返回unique_ptr
//返回即将被销毁的智能指针
unique_ptr<int> clone(int p)
{
return unique_ptr<int>(new int(p)); //正确
}
//返回局部对象智能指针
unique_ptr<int> clone(int p)
{
unique_ptr<int> ret(new int(p)) //正确
return ret;
}
3. weak_ptr
是一种软引用,可以指向shared_ptr 的对象.但是不会是shared_ptr的计数增加.
- 因为计数不增加,所以shared_ptr被释放时,weak_ptr对象也被释放.所以在用时要先调用lock().
weak_ptr<T> w; 空weak_ptr可以指向T类型的对象
weak_ptr<T> w(sp); 与weak_ptr sp指向相同的weak_ptr,T必须能转换为sp指向的类型
w = p; p可以是一个shared_ptr或weak_ptr,运行后w和p指向共享对象
w.reset(); 将w置空
w.use_count(); 与w共享对象的shared_ptr的数量
w.expired(); 如果use_count()为0,返回true,否则返回false
w.lock(); 如果w.expired()返回true,返回一个空的shared_ptr;
否则就返回一个指向w的对象的shared_ptr
3. new 关键字
string *ps1 = new string; //默认初始化为空
string *ps2 = new string(); //初始化为空
int *pi1 = new int; //默认初始化,*pi1的值未定义
int *pi2 = new int(); //初始化为0
//正常情况下,new失败就会抛出异常 bad_alloc,但是可以让他不抛出,返回null
int *p2 = new (nothrow)int; //阻止抛出异常
4. delete 关键字
int i = 0;
int *pi1 = &i;
int *pi2 = nullptr;
double *pd = new double(33);
double *pd2 = pd;
delete pi1; //编译通过,运行错误,pi1指向一个局部变量
delete pi2; //正确,空指针可以delete
delete pd; //正确,释放内存
delete pd2; //编译通过,运行错误,未定义,内存以及被释放
//指针释放
空悬指针
double *pd = new double(33);
delete pd; //正确,指针被释放后,会变成空悬指针,即有指向地址,但是地址已经被释放,无效了.再次delete就是未定义操作
pd = nullptr; //如果还需要用,那么就指定为空;
//指向同一内容的指针
double *pd = new double(33);
auto pd2 = pd;
delete pd; //pd2也会变为空悬指针
pd = nullptr; //pd2为空指针,但是pd2仍未空悬指针
delete pd2; //报错
5. new 和 智能指针搭配
//可以让智能指针指向一块内存,但是想初始化必须是直接初始化
shared_ptr<int> p1 = new int(1024); //错误,必须使用直接初始化形式.因为无法从内置指针转化为智能指针
shared_ptr<int> p2(new int(42)); //正确,直接初始化
不要混合使用内置和智能
void process(shared_ptr<int> ptr){...}
int *x(new int(1024));
process(x); //错误,无法从内置指针转化为智能指针
process(shared_ptr<int>(x)); //正确,但是x的内容会被清除,变为空悬指针
*x; //未定义
6.初始化动态数组
- 在new一个非内置对象数组时,会在申请空间的基础上头部多申请4个字节用于存储数组长度,这样delete[]时候才知道对象数组的大小
int *pia = new int[10]; //10个未初始化的int,会调用类型的初始化.如stirng即未空
int *pia2 = new int[10](); //10个0;
//释放
delete [] pia;
//内置对象可以不搭配使用.
int *pi2 = new int[2048]();
delete pi2;
int *pi2 = new int(2048);
delete[] pi2; //不会报错,编译器知道int是内置类型,不需要析构函数,所以也就不需要多4个字节来存放数组长度,只需要直接操作内存即可。
//使用非内置的对象是,动态数组一定要用[]
#include <iostream>
using namespace std;
class test {
public:
test() { cout << "构造" << endl; }
~test() { cout << "析构" << endl; }
};
//
int main(int argc, char *argv[]) {
test *p = new test();
delete[]p; //程序不会当掉,但是会死循环打印析构.因为不知道需要析构多少次.
test *p = new test[2];
delete p; //运行了1次析构后程序当掉.因为释放内存的时候只释放了起始地址为A的内存,然而这不是这一整块内存的起始地址,
//整块内存的起始地址应该是A-4,释放内存如果不从内存起始地址操作就会出现段错误,所以导致了程序挂掉。(长度不对,多了头部)
return 0;
}
7.指向数组的unique_str,shared_ptr
//指向数组的unique_str不支持成员访问运算符(点和箭头),结束时自动调用delete[]
unique_str<T []> U; //创建一个指向数组的unique_str
unique_str<T []> U(p); //u指向内置指针p所指向的动态分配数组,p必须时T*类型
u[i]; //访问对象
//shared_ptr 释放时不会自己调用delete[],需要自己手动设置
shared_ptr<int> sp(new int[10],[](int *p){delete [] p;});
//shared_ptr不支持下标运算符.如果需要访问对象只能用get()返回一个内置指针,然后进行访问;