C++(12):动态内存

news2024/11/26 14:46:15

除了自动和static对象外,C+还支持动态分配对象。动态分配的对象的生存期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会销毁。

静态内存 用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。
栈内存 用来保存定义在函数内的非static对象。

分配在静态或栈内存中的对象由编译器自动创建和销毁。
对于栈对象,仅在其定义的程序块运行时才存在:static对象在使用之前分配,在程序结束时销毁。

除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作 自由空间(free store)堆(heap)

程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,代码必须显式地销毁它们

忘记释放内存会产生内存泄漏,在尚有指针引用内存的情况下释放它会产生非法内存的指针。

动态内存和智能指针

c++ 使用 newdelete 管理动态内存
new:在动态内存中为对象分配空间并返回一个指向该对象的指针
delete:接受一个动态对象的指针,销毁该对象,并释放与之有关的内存。

新的标准库提供了两种智能指针类型来管理动态对象,都定义在 memory 头文件中。其类似于常规指针,主要区别在于管理底层指针的方式:
1.shared_ptr 允许多个指针指向同一个对象;
2.unique_ptr 则“独占”所指向的对象。
3.weak_ptr 伴随类,是一种弱引用,指向 shared_ptr 所管理的对象。

shared_ptr 类

类似 vector,智能指针也是模板。因此,创建一个智能指针时,必须提供额外的信息——指针可以指向的类型。

shared_ptr<string> pl;// shared_ptr,可以指向string
shared_ptr<list<int>> p2;// shared_ptr,可以指向int的list

默认初始化的智能指针中保存着一个空指针。

智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空:

//如果p1不为空,检查它是否指向一个空
string if(p1 && p1->empty())
	*pl = "hi";//如果pl指向一个空string,解引用p1,将一个新值赋予string

shared_ptrunique_ptr 都支持的操作
在这里插入图片描述
shared_ptr 独有的操作
在这里插入图片描述

make_shared 函数

最安全的分配和使用动态内存的方法是调用一个名为 make_shared 的标准库函数,在 memory 头文件中。
在动态内存中分配一个对象并初始化它,返回指向此对象的 shared_ptr

//指向一个值为42的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);
// p4指向一个值为"9999999999"的string
shared_ptr<string> p4 = make_shared<string>(10,'9');
// p5指向一个值初始化的int,即,值为0
shared_ptr<int> p5 = make_shared<int>();

类似于顺序容器的 emplace 成员,make_shared 是函数模板,用其参数来构造给定类型的对象。

shared_ptr 的拷贝和赋值

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:

auto p = make_shared<int>(42);// p 指向的对象只有 p 一个引用者
auto q(p) ; // p 和 q 指向相同对象,此对象有两个引用者

可以认为每个 shared_ptr 都有一个关联的计数器,通常称其为引用计数。无论何时拷贝一个 shared_ptr,计数器都会递增。
比如当用一个 shared_ptr 初始化另一个 shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。
如果 shared_ptr 的计数器变为 0,就会自动释放管理的对象。给 shared_ptr 赋予一个新值或是shared_ptr 被销毁时,计数器会递减。

auto r = make_shared<int>(42);//r 指向的 int 只有一个引用者
r =q;//给 r 赋值,令它指向另一个地址
//递增 q 指向的对象的引用计数
//递减 r 原来指向的对象的引用计数
//r 原来指向的对象已没有引用者,会自动释放

shared_ptr 自动销毁所管理的对象……

shared_ptr 通过析构函数来完成销毁。

shared_ptr 的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

……shared_ptr 还会自动释放相关联的内存

当动态对象不再被使用时,shared_ptr 类会自动释放动态对象。

最后一个 shared_ptr 销毁前内存都不会释放,所以要保证 shared_ptr 无用之后就不要再保留了。

如果将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素,否则会浪费内存。

程序使用动态内存的三种原因

1.程序不知道自己需要使用多少对象;
2.程序不知道所需对象的准确类型;
3.程序需要在多个对象间共享数据。

直接内存管理

运算符 new 分配内存,delete 释放 new 分配的内存。
相比于智能指针,这两个运算符管理内存非常容易出错,而且自己直接管理内存的类与使用智能指针的类不同,它们不依赖于类对象拷贝、赋值和销毁操作的任何默认定义。

使用 new 动态分配和初始化对象

在自由空间分配的内存是无名的,因此 new 无法为其分配的对象命名,而是返回一个指向该对象的指针:

int *pi = new int;//pi指向一个动态分配的、未初始化的无名对象
// 此 new 表达式在自由空间构造一个 int 型对象,并返回指向该对象的指针

默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值将是未定义的。而类类型对象将用默认构造函数进行初始化:

int* p = new int;   //默认初始化 10
string* sp = new string(10,'9');//直接初始化“9999999999”
vector<int>* vp = new vector<int>{0,1,2,3};//列表初始化

对于定义了自己的构造函数的类类型,不管采用什么形式,对象都会通过默认构造函数来初始化。
对于内置类型,值初始化的内置类型对象有着良好定义的值,而默认初始化的对象的值则是未定义的。
类似的,对于类中那些依赖于编译器合成的默认构造函数的内置类型成员,如果它们未在类内被初始化,那么它们的值也是未定义的。

动态分配 const 对象

new 分配 const 对象是合法的:

//分配并初始化一个const int
const int *pci = new const int (1024);
//分配并默认初始化一个const的空 string 
const string *pcs = new const string;

类似其他任何const对象,一个动态分配的const对象必须进行初始化。对于一个定义了默认构造函数的类类型,其const动态对象可以隐式初始化,而其他类型的对象就必须显式初始化。由于分配的对象是const的,new返回的指针是一个指向const的指针。

内存耗尽

一旦一个程序用光了它所有可用的内存,new表达式就会失败。
默认情况下,如果 new 不能分配所要求的内存空间,会抛出一个类型为 bad_alloc 的异常。
可以改变使用 new 的方式来阻止它抛出异常:

//如果分配失败,new返回一个空指针
int *pl = new int; //如果分配失败,new抛出std::bad_alloc
int *p2 = new (nothrow) int;//如果分配失败,new返回一个空指针

释放动态内存

delete 执行两个动作:销毁给定的指针指向的对象;释放对应的内存。

delete p;// p必须指向一个动态分配的对象或是一个空指针

指针值和 delete

传递给 delete 的指针必须指向动态分配的内存,或者是一个空指针。释放一块并非 new 分配的内存,或者将相同的指针释放多次,其行为是未定义的:

int i, *pi1 = &i, *pi2=nullptr;
double *pd = new double(33),*pd2 = pd;
delete i;//错误:i不是一个指针
delete pi1;//未定义:pi1指向一个局部变量
delete pd;//正确
delete pd2;//未定义:pd2指向的内存已经被释放了
delete pi2;//正确:释放一个空指针总是没有错误的

const 动态对象的值不可改变,但是可以通过 delete 指向它的指针进行销毁。

动态对象的生存期知道释放时为止

shared_ptr 管理的内存在最后一个 shared_ptr 销毁时会被自动释放;
对于通过内置指针类型来管理的内存(动态对象),需要显式释放。
通常编译器不能分辨 delete 的对象是动态还是静态分配的对象,也不能分辨一个指针所指的内存是否已被释放。

动态内存的管理容易出现的问题

1.忘记 delete 内存。产生“内存泄漏”:内存无法归还给自由空间;
2.使用已释放掉的对象;
3.同一块内存释放两次:自由空间可能会被破坏。

使用智能指针可以避免这些问题。对于一块内存,只有在由任何智能指针指向它的情况下,智能指针才会自动释放它。

delete 之后重置指针值

delete 一个指针后,其指针值变为无效,但仍然保存着(已经释放掉的)动态内存的地址。
delete 之后,指针就变成了空悬指针,即指向一块曾经保存数据对象但现在已经无效的内存的指针。
未初始化指针的所有缺点空悬指针也都有。
避免空悬指针的问题: 在指针即将要离开其作用域之前释放掉它所关联的内存。这样,在指针关联的内存被释放掉之后,就没有机会继续使用指针了。如果我们需要保留指针,可以在 delete之后将nullptr赋予指针,这样就清楚地指出指针不指向任何对象。

shared_ptr 和 new 结合使用

如果不初始化一个智能指针,它就会被初始化为一个空指针。
还可以用 new 返回的指针来初始化智能指针:

shared_ptr<double> pl;// shared_ptr 可以指向一个double
shared_ptr<int> p2(new int (42));// p2指向一个值为42的int

接受指针参数的智能指针构造函数是explicit的。explicit 一般用来防止隐式转换。
必须使用直接初始化形式来初始化一个智能指针:

shared_ptr<int> pl = new int(1024);//错误:必须使用直接初始化形式
shared_ptr<int> p2(new int(1024));//正确:使用了直接初始化形式

定义和改变 shared_ptr 的其他方法:

shared_ptr<T> p(q);// p 管理内置指针 q 所指向的对象;q必须指向new分配的内存,且能够转换为T*类型
shared_ptr<T> p(u)// p从 unique_ptr u那里接管了对象的所有权;将u置为空
shared_ptr<T> p(a,d)//p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用对象d来代替delete
shared ptr<T> p(p2,d)//p是shared ptrp2的拷贝,唯一的区别是p将用可调用对象d来代替delete
p.reset()
p.reset (q)
p.reset(q,d)//若p是唯一指向其对象的shared_ptr,reset会释放此对象。若传递了可选的参数内置指针q,会令p指向q,否则会将p置为空。若还传递了参数d,将会调用d而不是delete来释放q

不要混合使用普通指针和智能指针

shared_ptr可以协调对象的析构,但这仅限于其自身的据贝(也是shared_ptr)之间。这也是为什么推荐使用make_ shared而不是new的原因。

make_ shared 能在分配对象的同时就将 shared_ptr 与之绑定,从而避免将同一块内存绑定到多个独立创建的 shared_ptr 上。

使用一个内置指针来访问一个智能指针非常危险,因为无法知道对象何时会被销毁。

也不要使用 get 初始化另一个智能指针或为智能指针赋值

智能指针类型定义了一个名为 get 的函数,它返回一个内置指针指向智能指针管理的对象。此函数设计目的:需要向不能使用智能指针的代码传递一个内置指针。
使用get返回的指针的代码不能delete此指针。

sharedptr<int>p(newint(42));//引用计数为1
int*q=p.get();//正确:但使用q时要注意,不要让它管理的指针被释放
{//新程序块
//未定义:两个独立的sharedptr指向相同的内存
shared ptr<int>(q);
)//程序块结束,q被销毁,它指向的内存被释放
int foo=*p;//未定义:p指向的内存已经被释放了

get 用来将指针的访问权限传递给代码,只有在确定代码不会 delete 指针的情况下,才能使用 get。
特别是,永远不要用 get 初始化另一个智能指针或者为另一个智能指针赋值。

智能指针和异常

使用异常处理的程序能在异常发生后令程序流程继续,需要确保在异常发生后资源能被正确地释放,一种简单的确保资源被释放的方法是使用智能指针。

如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放。内置指针管理内存时,在 new 之后在对应的 delete 之前发生了异常,则内存不会被释放。

智能指针和哑类

C++ 中很多类都定义了析构函数,负责清理对象使用的资源,但是那些为 C 和 C++ 两种语言设计的类,通常要求用户显式地释放所使用地任何资源。

那些分配了资源,而又没有定义析构函数来释放这些资源的类,可能会遇到与使用动态内存相同的错误——忘记释放资源。类似的,如果在资源分配和释放之间发生了异常,程序也会发生资源泄漏。

使用自己的释放操作

默认情况下,shared_ptr 假定它们指向的是动态内存。因此,当一个shared_ptr 被销毁时,它默认地对它管理的指针进行 delete操作。可以使用 shared_ptr 管理其他对象,但需要定义一个相应的删除器函数来代替 delete,完成对 shared_ptr 中保存地指针进行释放地操作。

//删除器接受单个类型位 connection* 的参数:
//void end_connection (connection *p){disconnect(*p);}

void f(destination &d/*其他参数*/){
	connection c = connect(&d);
	shared_ptr<connection> p(&c,end_connection);
	//使用连接
	//当f退出时(即使是由于异常而退出),connection会被正确关闭
}

p 被销毁时,它不会对自己保存的指针执行delete,而是调用end_connection。接下来,end_connection 会调用disconnect,从而确保连接被关闭。如果f正常退出,那么 p 的销毁会作为结束处理的一部分。如果发生了异常,p 同样会被销毁,从而连接被关闭。

正确使用智能指针的一些基本规范:

  • 不使用相同的内置指针值初始化(或reset)多个智能指针。
  • 不delete get()返回的指针。
  • 不使用get()初始化或reset另一个智能指针。
  • 如果使用get()返回的指针,记住当最后一个对应的智能指针销毁后,指针就变为无效了。
  • 如果使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。

unqie_ptr

一个 unique_ptr "拥有"它所指向的对象。与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
unique_ptr 独有它指向的对象,所有它不支持拷贝和赋值操作。但可以"拷贝"或"赋值"一个将要被销毁的unique_ptr

//从函数返回一个`unique_ptr`
unique_ptr<int> clone(int p){
//正确:从int*创建一个unique_ptr<int>
	return unique_ptr<int>(new int (p));
}

//返回一个局部对象的拷贝
unique ptr<int> clone(int p){
	unique_ptr<int> ret(new int (P))// ...
	return ret;
}

在这里插入图片描述

auto_ptr

标准库的较早版本包含了一个名为auto_ptr的类,它具有unique_ptr的部分特性,但不是全部。特别是,不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr
虽然auto_ptr仍是标准库的一部分,但编写程序时应该使用unique_ptr.

weak_ptr

weak_ptr 是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr 管理的对象。
将一个 weak_ptr 绑定到一个 shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。

在这里插入图片描述

weak_ptr 定义和初始化

]创建一个weak_ptr时,要用一个shared_ptr来初始化它:

auto p= make shared<int>(42);
weak_ptr<int> wp(p); // wp弱共享p;p的引用计数未改变

//wp和p指向相同的对象。由于是弱共享,创建wp不会改变p的引用计数;wp指向的对象可能被释放掉。

由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr。与任何其他shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。

if (shared_ptr<int> np - wp.1ock()){ //如果np 不为空则条件成立
//在if中,np与p共享对象
}

动态数组

newdelete运算符一次分配/释放一个对象,但某些应用需要一次为很多对象分配内存的功能。例如,vector 和 string 都是在连续内存中保存它们的元素,因此,当容器需要重新分配内存时,必须一次性为很多元素分配内存

C++ 语言和标准库提供了两种一次分配一个对象数组的方法:1.表达式语法new ;2. 类allocator,允许将分配和初始化分离

new 和数组

new 分配要求数量的对象并(假定分配成功后)返回指向第一个对象的指针。

// 调用 get_size 确定分配多少个 int
int *pia = new int [get_size()];// pia指向第一个int
//方括号中的大小必须是整型,但不必是常量。

也可以用一个表示数组类型的类型别名来分配一个数组:

// new 表达式不再需要方括号
typedef int arrT [42]; // arrT表示42个int的数组类型
int *p = new arrT; //分配一个42个int的数组;p指向第一个int

//new分配一个int数组,并返回指向第一个int的指针。
//即使代码中没有方括号,编译器执行这个表达式时还是会用 new[]。
//等价于执行:
int *P = new int[42];

分配一个数组会得到一个元素类型的指针
当用 new 分配一个数组时,并未得到一个数组类型的对象,而是得到一个数组元素类型的指针。
即使使用类型别名定义了一个数组类型,new 也不会分配一个数组类型的对象。
由于分配的内存并不是一个数组类型,因此不能对动态数组调用beginend。也不能用范围 for 语句来处理(所谓的)动态数组中的元素。
通常所说的动态数组并不是一种数组类型。

初始化动态分配对象的数组
默认情况下,new分配的对象,不管是单个分配的还是数组中的,都是默认初始化的。可以对数组中的元素进行值初始化,方法是在大小之后跟一对空括号。也可以进行列表初始化。

int *pia = new int [10];// 10 个未初始化的 int
int *pia2 = new int[10]();// 10个值初始化为0的int
string *psa = new string[10];// 10个空string
string *psa2 = new string[10]();// 10个空string

// 10个int分别用列表中对应的初始化器初始化
int *pia3 = new int [10] {0,1,2,3,4,5,6,7,8,9};
// 10个string,前4个用给定的初始化器初始化,剩余的进行值初始化
string *psa3 = new string[10] {"a", "an""the",string(3, 'x')};

与内置数组对象的列表初始化一样,初始化器会用来初始化动态数组中开始部分的元素。如果初始化器数目小于元素数目,剩余元素将进行值初始化。
如果初始化器数目大于元素数目,则new表达式失败,不会分配任何内存。

虽然用空括号对数组中元素进行值初始化,但不能在括号中给出初始化器,这意味着不能用auto分配数组。

动态分配一个空数组是合法的
虽然不能创建一个大小为 0 的静态数组对象,但当 n 等于 0 时,调用 new[n] 是合法的。

char arr[0];//错误:不能定义长度为0的数组
char *cp = new char[0]; //正确:但cp不能解引用

new分配一个大小为0的数组时,new返回一个合法的非空指针。此指针保证与new返回的其他任何指针都不相同。对于零长度的数组来说,此指针就像尾后指针一样,可以像使用尾后迭代器一样使用这个指针。
可以用此指针进行比较操作,可以向此指针加上(或从此指针减去)0,也可以从此指针减去自身从而得到0。但此指针不能解引用——它不指向任何元素

释放动态数组
使用 delete [] 来释放动态数组

delete p;//p必须指向一个动态分配的对象或为空
delete [] pa;// pa必须指向一个动态分配的数组或为空

数组中的元素按逆序销毁即,最后一个元素首先被销毁,然后是倒数第二个,依此类推。
delete[][] 是必需的:如果在 delete 一个指向动态数组的指针时忽略了方括号,行为是未定义的。

智能指针和动态数组
标准库提供了一个可以管理 new 分配的数组的 unique_ptr 版本。

// up指向一个包含10个未初始化int的数组
unique_ptr<int[]> up (new int [10]);
up.release();//自动用 delete[]销毁其指针

类型说明符中的方括号(<int[]>)指出up指向一个int 数组而不是一个 int。由于up指向一个数组,当up销毁它管理的指针时,会自动使用delete[]

指向数组的 unique_ptr:

//指向数组的unique ptr不支持成员访问运算符(点和箭头运算符)。
//其他unique ptr操作不变。

unique_ptr<T[]> u      // u 可以指向一个动态分配的数组,数组元素类型为 T
unique_ptr<T[]> u(p)   // u 指向内置指针 p 所指向的动态分配的数组,p 必须能转换为类型 T*
u[i]                   // 返回 u 拥有的数组中位置 i 处的对象

shared_ptr 不直接支持管理动态数组。如果希望使用 shared_ptr 管理一个动态数组 ,必须提供自己定义的删除器。

//为了使用shared_ptr,必须提供一个删除器
shared ptr<int> sp(new int[10],[](int*p){ delete[] p;});
sp.reset();//使用我们提供的 lambda释放数组,它使用delete[]

如果未提供删除器,shared_ptr 默认使用 delete 来销毁动态数组,是未定义的。
shared_ptr 访问数组中的元素需要使用 get()。

allocator 类

灵活性上的局限,new 将内存分配和对象构造组合在了一起。类似的,delete将对象析构和内存释放组合在了一起
分配单个对象时,通常希望将内存分配和对象初始化组合在一起。因为在这种情况下,几乎肯定知道对象应有什么值。

当分配一大块内存时,通常计划在这块内存上按需构造对象。这种情况下,希望将内存分配和对象构造分离。意味着可以分配大块内存,但只在真正需要时才真正执行对象创建操作(同时付出一定开销)。

allocator

标准库allocator类定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的

类似vectorallocator是一个模板。为了定义一个allocator对象,必须指明这个allocator可以分配的对象类型。
当一个allocator对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对齐位置:

allocator<string> alloc;//可以分配string 的allocator对象
auto const p = alloc.allocate(n);//分配n个未初始化的string
// 这个 allocate 调用为 n 个 string 分配了内存

标准库 allocator 类及其算法
在这里插入图片描述

allocator 分配未构造的内存

allocator分配的内存是未构造的。按需要在此内存中构造对象。
在新标准库中,construct成员函数接受一个指针和零个或多个额外参数,在给定位置构造一个元素。额外参数用来初始化构造的对象。类似make_shared的参数,这些额外参数必须是与构造的对象的类型相匹配的合法的初始化器:

auto q = p; //q指向最后构造的元素之后的位置
alloc.construct(q++);//*q为空字符串
alloc.construct(q++,10,'c');//*q为ccccccccc
calloc.construct(q++, "hi");//*q为hi!

为了使用allocate返回的内存,必须用 construct构造对。使用未构造的内存,其行为是未定义的

用完对象后,必须对每个构造的元素调用destroy来销毁它们:

while (q != p)
	alloc.destroy(--q);//释放真正构造的string

只能对真正构造了的元素进行destroy操作。
元素被销毁后可以重新在这块内存构造对象也可以释放掉内存。
constructdestory 一次都只能构造或销毁一个对象,要想完成对所有元素的操作,需要通过指针来遍历对每个元素进行操作。

拷贝和填充未初始化内存的算法

定义在头文件 memory 中,两个伴随算法:uninitialized_copyuninitialized_fill,可以在未初始化内存中创建对象。
在这里插入图片描述将分配一块比vector中元素所占用空间大一倍的动态内存,然后将原vector中的元素拷贝到前一半空间,对后一半空间用一个给定值进行填充:

//分配比vi中元素所占用空间大一倍的动态内存
auto p = alloc.allocate(vi.size()*2);
//通过拷贝vi中的元素来构造从p开始的元素
auto q = uninitialized_copy(vi.begin(), vi.end(),p);
//将剩余元素初始化为42
uninitialized_fill_n(g, vi.size(),42);

重要术语

allocator :标准库类,用来分配未构造的内存。
空悬指针:一个指针,指向曾经保存一个对象但现在已释放的内存。众所周知,空悬指针引起的程序错误非常难以调试。
释放器:传递给智能指针的函数,用来代替delete释放指针绑定的对象。
动态分配的:在自由空间中分配的对象。在自由空间中分配的对象直到被显式释放或程序结束才会销毁。
自由空间:程序可用的内存池,保存动态分配的对象。
:自由空间的同义词。
定位new:一种new表达式形式,接受一些额外的参数,在 new关键字后面的括号中给出。例如,new(nothrow) int告诉new不要抛出异常。
引用计数:一个计数器,记录有多少用户共享一个对象。智能指针用它来判断什么时候释放所指向的对象是安全的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/760151.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql_to_clickhouse同步方案调研

调研时间&#xff1a;2021年08月&#xff0c;之后是否出现优化方案未知 方式1&#xff1a;网上开源python脚本-----mysql-clickhouse-replication 安装参考&#xff1a;https://www.cnblogs.com/gomysql/p/11199856.html 软件路径&#xff1a;https://github.com/yymysql/my…

【C++STL】模拟实现vector容器

文章目录 前言一、vector的成员函数二、增删查改工作说明size()和capapcity()2.1reserve()2.2 resize&#xff08;&#xff09;2.3 insert()2.4 erase&#xff08;&#xff09;2.5 push_back&#xff08;&#xff09;和pop_back&#xff08;&#xff09; 三、[]重载和迭代器3.1…

ahut 周赛3

A.gzm判试卷 AhutOj 线段树(注意,一定要开到4*N,不然会RE) 单点更新(求区间最值) 单点更新不需要懒标记,区间修改是大量的点,需要懒标记 AC代码: #include<iostream> #include<algorithm> #include<cstring> #include<cmath> using namespace st…

Jmeter接口测试从0到1打通,从安装到接口测试实例(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Jmeter简介 J…

安装adobe系列产品,提示错误代码81解决办法

安装adobe系列软件&#xff0c;如Photoshop、Premiere Pro、Illustrator等时&#xff0c;出现如下图提示错误代码81&#xff0c;如何解决呢&#xff1f;一起来看看。 解决方法一 (重启电脑等待5分钟再安装&#xff01;) 解决方法二 应用程序中打开Adobe Creative Cloud 点击…

分布式文件系统与HDFS的shell操作及查看元数据

启动hadoop和hive的metastore查看sbin的目录下的文件 执行./start-all.sh 查看相关的进程

node.js重装问题

目录 问题一&#xff1a; 问题二&#xff1a; node.js安装分享&#xff1a; node.js重装参考&#xff1a; 问题一&#xff1a; 使用&#xff08;npm install express -g&#xff09;报错&#xff1a; config global --global -local are deprecated. Use --locationglobal …

【软件测试】Window与Linux系统下-初始化Git环境(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Window 初始化 Gi…

python开发ESP32之环境配置(一)

1、开发编辑器 Thonny 2、串口驱动安装 通过type-c线将芯片链接电脑&#xff0c; 右击我的点电脑——管理——设备管理器&#xff0c;查看驱动安装是否正常。 异常情况下需要安装驱动&#xff08;CP2102USBQD),根据个人电脑配置选择相应的驱动程序 安装失败的话&…

AcWing1536. 均分纸牌 AcWing122. 糖果传递—数学推导、贪心

均分纸牌 && 糖果传递 均分纸牌糖果传递 均分纸牌 题目链接 AcWing1536. 均分纸牌 问题描述 分析 这道题有个特殊的地方就是A1只能从A2获取纸牌&#xff0c;或者A1只能将多余的纸牌给A2&#xff0c;此操作后A1的纸牌数应该为avg。 A2的纸牌只能从A3获取&#xff0c;…

PS VR2头显有望将与PC兼容,并实现破解6DOF跟踪功能

Sony在今年早些时候推出了专门为PlayStation 5打造的高级虚拟现实头显PS VR2&#xff0c;这款头显赢得了行业的赞誉&#xff0c;为用户提供了独特的体验。 然而&#xff0c;由于其高达599美元&#xff08;约合4283元人民币&#xff09;的售价&#xff08;相比于售价为399美元的…

MySQL学生表和分数表的多表查询

目录 一、创建学生表 二、创建分数表 1.查询student表的所有记录 2.查询student表的第2条到4条记录 3.从student表查询所有学生的学号&#xff08;id&#xff09;、姓名&#xff08;name&#xff09;和院系&#xff08;department&#xff09;的信息 4.从student表中查询计…

【Hello mysql】 mysql的索引

Mysql专栏&#xff1a;Mysql 本篇博客简介&#xff1a;介绍mysql的索引 mysql索引 索引索引是什么索引的分类索引作用查看 磁盘mysql的工作过程认识磁盘定位扇区磁盘随机访问(Random Access)与连续访问(Sequential Access)mysql和磁盘交互的基本单位 索引的理解建立测试表为何I…

Python(十四)数据类型——浮点型

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

Redis(分布式缓存详解)

Redis 一、Redis简介1.1. 适用场景1.2. 常用数据类型1.3. 单点Redis缺陷 二、持久化机制&#xff08;解决数据丢失&#xff09;2.1. RDB2.1.1. RDB优缺点 2.2. AOF2.2.1. AOF配置2.2.2. AOF优缺点 三、Redis集群3.1. 主从&#xff08;解决并发读&#xff09;3.1.1. 主从数据同步…

HBase v2.2 高可用多节点搭建

最近刚刚完成了HBase相关的一个项目,作为项目的技术负责人,完成了大部分的项目部署,特性调研工作,以此系列文章作为上一阶段工作的总结. 前言 其实目前就大多数做应用的情况来讲,我们并不需要去自己搭建一套HBase的集群,现有的很多云厂商提供的服务已经极大的方便日常的应用使…

GEE:计算每个对象的面积、标准差、周长、宽度、高度

作者:CSDN @ _养乐多_ 本文记录了面对对对象分割,以及计算每个对象的面积、标准差、周长、宽度、高度的代码。 文章目录 一、代码一、代码 // 设置种子 var seeds = ee.Algorithms.Image.Segmentation.seedGrid(20)

node.js 第一天

目录 使用readFile()方法读取文件内容 判断文件是否读取成功 判断文件是否写入成功 案例 考试成绩整理 path path.join&#xff08;&#xff09; path.basename() 使用readFile()方法读取文件内容 // 1. 导入 fs 模块&#xff0c;来操作文件 const fs require(fs)// 2.…

Redis(二)网络协议和异步方式(乐观锁悲观锁)

Redis系列文章 Redis&#xff08;一&#xff09;原理及基本命令&#xff08;柔性数组&#xff09; Redis&#xff08;二&#xff09;网络协议和异步方式&#xff08;乐观锁&悲观锁&#xff09; Redis&#xff08;三&#xff09;存储原理与数据模型&#xff08;hash冲突、渐…

2023年NOC决赛-加码未来编程赛项决赛模拟题-Python模拟题--卷2

第一题: 题目:打印出所有的“水仙花数”,所谓“水仙花数”是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个“水仙花数”,因为153=1的三次方+5的三次方+3的三次方。 第二题: 题目:企业发放的奖金根据利润提成。利润(I)低于或等于10万元时,奖金可提10…