面经八股文

红黑树和AVL树有什么区别?

红黑树和AVL树都是常用的自平衡二叉搜索树,它们的主要区别在于平衡的实现方式和平衡因子的定义。

平衡实现方式:
AVL树:AVL树通过限制每个节点的左右子树的高度差(平衡因子)不超过1来保持平衡。这意味着AVL树的高度非常稳定,但是由于每个插入或删除操作可能需要旋转来保持平衡,因此AVL树的插入和删除操作相对较慢。

红黑树:红黑树通过定义五个规则(红黑规则)来保持平衡。这些规则包括:节点是红色或黑色、根节点是黑色、所有叶子节点都是黑色、每个红色节点的两个子节点都是黑色、从任何一个节点到其每个叶子节点的所有路径都包含相同数量的黑色节点。这些规则保证了红黑树的高度大致平衡,插入和删除操作的平均时间复杂度为O(logn)。
平衡因子的定义:
AVL树:AVL树的平衡因子定义为左子树高度减去右子树高度的值,取值范围为-1、0、1。因此,AVL树的平衡调整比较容易,只需要通过旋转操作保证平衡因子不

HashMap底层实现

底层都是以红黑树的结构实现,因此插入删除等操作都在O(logn)时间内完成,因此可以完成高效的插入删除。
红黑树是一种平衡二叉树,它的每个节点满足根节点大于左儿子,小于右儿子,且每一层的高度差不小于1

HashMap怎么判断该不该扩容?扩容到多少?

在C++的STL中,map是一种关联式容器,使用红黑树实现,其内部会自动调整树结构以保持平衡,因此不需要自己手动调整容器大小。

map在内部维护一个红黑树,每个节点代表一个键值对。当插入一个新键值对时,map会自动将其插入到红黑树中的正确位置,并对红黑树进行必要的平衡操作。当插入操作完成后,如果红黑树的节点数量超过了当前容量,则会自动进行扩容操作。

具体来说,map的扩容策略是:当红黑树节点数量达到当前容量时,会将容量扩大为原来的两倍。在进行扩容操作时,map会重新分配内存空间,并将原有的键值对按照新的哈希函数重新插入到新的桶中。

需要注意的是,由于map的扩容操作会导致节点的重新插入,因此在插入大量数据时,建议在插入前先预留足够的空间,以避免频繁扩容操作导致的性能损失。可以通过调用reserve()方法来预留容量,例如:

std::map<int, int> myMap;
myMap.reserve(1000); // 预留1000个键值对的容量

hashcode怎么计算?

在计算哈希码时,通常需要将对象的数据转换成一个整数值,这个整数值就是哈希码。不同的哈希函数有不同的计算方法,下面介绍一些常见的计算方法。

直接取地址法
直接将对象的地址作为哈希码,这种方法实现简单,但会产生哈希冲突。

数字分析法
将对象的数据按照一定的规律分析,得到一个哈希码。例如,对于一个字符串,可以将其每个字符的ASCII码相加,然后取余得到哈希码。

平方取中法
将对象的数据平方后,取中间的一部分作为哈希码。例如,对于一个整数,可以将其平方后取中间几位作为哈希码。

除留余数法
将对象的数据除以一个不大于哈希表大小的数,然后取余得到哈希码。例如,对于一个整数,可以将其除以一个质数,然后取余得到哈希码。

折叠法
将对象的数据分成几个部分,每部分都转换成一个整数值,然后将这些整数值相加或异或得到哈希码。例如,对于一个长字符串,可以将其分成若干段,每段转换成一个整数值,然后相加或异或得到哈希码。

需要注意的是,不同的哈希函数有不同的性质和适用场景,选择合适的哈希函数可以减少哈希冲突,提高哈希表的效率。在实际应用中,可以根据对象的特点和哈希表的大小选择合适的哈希函数。

出现哈希冲突该怎么办

哈希冲突是指不同的键值对象被哈希函数映射到了同一个哈希桶中,这会导致哈希表的效率降低。下面介绍一些常见的解决哈希冲突的方法。

开放地址法
开放地址法是指当发生哈希冲突时,继续在哈希表中寻找下一个空闲位置,并将键值对象插入到该位置中。常见的开放地址法包括线性探测法、二次探测法、双重哈希法等。

链地址法
链地址法是指将哈希桶中的每个位置都设置为一个链表的头节点,当发生哈希冲突时,将键值对象插入到对应位置的链表中。链地址法可以保证每个键值对象都能被存储在哈希表中,但需要额外的空间来存储链表节点。

再哈希法
再哈希法是指使用多个不同的哈希函数,当发生哈希冲突时,使用另一个哈希函数重新计算哈希码,并将键值对象插入到对应的哈希桶中。再哈希法可以减少哈希冲突的概率,但需要额外的计算时间和空间。

公共溢出区法
公共溢出区法是指将哈希表中所有的哈希桶共用一个溢出区,当发生哈希冲突时,将键值对象插入到溢出区中。公共溢出区法可以避免链地址法中的空间浪费问题,但可能会导致溢出区的效率降低。

需要注意的是,不同的解决哈希冲突的方法有各自的优缺点,选择合适的方法需要根据实际情况综合考虑。在实际应用中,常见的哈希表实现如C++的std::unordered_map和Java的HashMap等已经内置了哈希冲突解决的方法,可以直接使用。

进程的内存布局堆和栈分别是干什么用的?

进程的内存布局包括代码段、数据段、堆、栈等部分,其中堆和栈是两个重要的内存区域。

堆是用于动态内存分配的区域,它的大小不是固定的,可以在程序运行过程中动态地扩展或缩小。当程序需要申请一块动态内存时,就会从堆中分配一块合适大小的空间,这个过程称为“动态内存分配”。堆的内存空间是由程序员控制的,它的生命周期可能超过函数的作用域。

栈是用于函数调用的区域,它的大小是固定的。每当一个函数被调用时,就会在栈中为这个函数分配一块空间,用于保存函数的局部变量、函数参数、返回地址等信息。当函数调用结束时,这块空间就会被释放。栈的内存空间是由编译器控制的,它的生命周期通常与函数的调用周期相同。

总的来说,堆和栈都是用于存储数据的内存区域,但是它们的使用方式和作用不同。堆用于动态内存分配,可以在程序运行过程中动态地分配和释放内存,而栈则用于函数调用,用于存储函数的局部变量和其他相关信息。

进程间的通信方式?

管道

无名管道

无名管道特点:

无名管道是一种特殊的文件,这种文件只存在于内存中。

无名管道只能用于父子进程或兄弟进程之间,必须用于具有亲缘关系的进程间的通信。

无名管道只能由一端向另一端发送数据,是半双工方式,如果双方需要同时收发数据需要两个管道。

有名管道:

有名管道特点:

有名管道是FIFO文件,存在于文件系统中,可以通过文件路径名来指出。

有名管道可以在不具有亲缘关系的进程间进行通信。

消息队列

相比于 FIFO,消息队列具有以下优点:

消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。

共享内存

进程可以将同一段共享内存连接到它们自己的地址空间,所有进程都可以访问共享内存中的地址,如果某个进程向共享内存内写入数据,所做的改动将立即影响到可以访问该共享内存的其他所有进程。

共享内存的方式像极了多线程中线程对全局变量的访问,大家都对等地有权去修改这块内存的值,这就导致在多进程并发下,最终结果是不可预期的。所以对这块临界区的访问需要通过信号量来进行进程同步。

但共享内存的优势也很明显,首先可以通过共享内存进行通信的进程不需要像无名管道一样需要通信的进程间有亲缘关系。其次内存共享的速度也比较快,不存在读取文件、消息传递等过程,只需要到相应映射到的内存地址直接读写数据即可。

信号量

在提到共享内存方式时也提到,进程共享内存和多线程共享全局变量非常相似。所以在使用内存共享的方式是也需要通过信号量来完成进程间同步。多线程同步的信号量是POSIX信号量,而在进程里使用SYSTEM V信号量。

套接字

与其它通信机制不同的是,它可用于不同机器间的进程通信。

说说TCP的连接?

初始状态:客户端处于 closed(关闭)状态,服务器处于 listen(监听) 状态。
第一次握手:客户端发送请求报文将 SYN = 1同步序列号和初始化序列号seq = x发送给服务端,发送完之后客户端处于SYN_Send状态。(验证了客户端的发送能力和服务端的接收能力)
第二次握手:服务端受到 SYN 请求报文之后,如果同意连接,会以自己的同步序列号SYN(服务端) = 1、初始化序列号 seq = y和确认序列号(期望下次收到的数据包)ack = x+ 1 以及确认号ACK = 1报文作为应答,服务器为SYN_Receive状态。(问题来了,两次握手之后,站在客户端角度上思考:我发送和接收都ok,服务端的发送和接收也都ok。但是站在服务端的角度思考:哎呀,我服务端接收ok,但是我不清楚我的发送ok不ok呀,而且我还不知道你接受能力如何呢?所以老哥,你需要给我三次握手来传个话告诉我一声。你要是不告诉我,万一我认为你跑了,然后我可能出于安全性的考虑继续给你发一次,看看你回不回我。)
第三次握手: 客户端接收到服务端的 SYN + ACK之后,知道可以下次可以发送了下一序列的数据包了,然后发送同步序列号 ack = y + 1和数据包的序列号 seq = x + 1以及确认号ACK = 1确认包作为应答,客户端转为established状态。(分别站在双方的角度上思考,各自ok)

TCP如何判断是不是一个有效的包?

CRC即循环冗余校验码(Cyclic Redundancy Check):
是数据通信领域中最常用的一种查错校验码
,其特征是信息字段和校验字段的长度可以任意选定。
循环冗余检查(CRC)是一种数据传输检错功能
,对数据进行多项式计算,并将得到的结果附在帧的后面,
接收设备也执行类似的算法,以保证数据传输的正确性和完整性。
相当于大家都是用相同的加密方式,但加密结果是唯一的,所以必须值没有丢失或者篡改
才能算出相同的数据。

设计模式有了解吗,手写一下单例模式?

答案链接

#include
using namespace std;
class Singleton {
public:
	static Singleton* getIntance()
	{
		if (m_singer == NULL)
		{
		  std::lock_guard<std::mutex> lock(mtx);
		  if (m_singer == nullptr) {
	    		 m_singer = new Singleton();
	    	}
		 
		}
		return m_singer;
	}
private:
	Singleton()
	{
		m_singer = NULL;

	}
private:
	static Singleton* m_singer;
	 static std::mutex mtx;
};
Singleton* Singleton::m_singer = NULL;
int main()
{
	Singleton* p1 = Singleton::getIntance();
	Singleton* p2 = Singleton::getIntance();
	printf("%x %x", p1, p2);

}

虚函数

epoll的实现原理

epoll和select、poll的区别

水平触发与边缘触发的区别

链接

可能导致内存泄漏的原因

1.产生内存泄漏最主要的原因是利用mallco或者new 等分配内存的方式申请内存后,由于主观或者客观的原因没有进行释放,导致内存区域没有得到及时释放导致的。

  1. 关于指向对象指针数组释放

指针是C/C++中的一个核心概念,程序员可以直接对内存进行操作的一种工具,这样的工具也是一把双刃剑,既可以对程序进行优化,又可以导致一些难以调试的错误。

指向对象的指针数组是指数组中存放的是指向对象的指针,不仅并没有释放每个对象的空间,还要释放每个指针的空间,delete[]p只是释放了每个指针,但是并没有释放对象的空间,正确的做法,是通过一个循环,将每个对象释放,然后再把指针释放了。

3.拷贝构造函数和运算符重载的问题

当类缺少拷贝构造函数,可能造成内存泄漏问题。在C/C++中,如果没有定义拷贝构造函数,那么编译器就会调用默认的拷贝构造函数。这种隐式传递的方式容易造成两个对象同时具有指向同一个地址的指针成员。因为在释放对象的时候,第一个对象能够正常释放,而第二个对象的释放将会释放相同的内存,这是一种错误的做法,可能会导致堆的崩溃。

4.没有将基类的析构函数定义为虚函数

当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确释放,继而造成内存泄漏。

其他原因:

析构的时候void*,导致析构函数没有调用

野指针:指向被释放的或者访问受限内存的指针

构造的时候浅拷贝,释放的时候调用了两侧delete。

如何避免内存泄漏

内存泄漏

有哪些智能指针

智能指针

多进程和多线程运用在哪?

多线程:io密集型 (读取网络,读取文件)

多进程:计算 cpu耗用的多(一个程序就可以理解为一个进程 )

多线程逃不开的抢占锁的话题,牵扯到一个安全问题。

安全问题的优化又伴随效率问题。如果他们太复杂,你懒得优化。你就可以想办法去复制出来一个进程去执行。

这样虽然消耗了cpu但是计算结果快而准确。

堆排序了解吗?

堆是一种特殊的树,它满足需要满足两个条件:

(1)堆是一种完全二叉树,也就是除了最后一层,其他层的节点个数都是满的,最后一个节点都靠左排列。

(2)堆中每一个节点的值都必须大于等于(或小于等于)其左右子节点的值。

对于每个节点的值都大于等于子树中每个节点值的堆,我们叫作“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,我们叫作“小顶堆”。

2、堆的实现:

用数组来存储完全二叉树是非常节省内存空间的,因为我们不需要存储左右子节点的指针,单纯通过数组的下标,就可以找到一个节点的子节点和父节点。
从堆中删除堆顶元素:
往堆中插入一个元素:
我们新插入一个元素之后,堆可能就不满足堆的特性了,就需要进行调整,让其重新满足堆的特性,即堆化(heapify),堆化非常简单,就是顺着节点所在的路径,向上或者向下,对比,然后交换。所以分为两种,从下往上 和 从上往下。
删除步骤:把最后一个节点放到堆顶,然后利用同样的父子节点对比方法。对于不满足父子节点大小关系的,互换两个节点,并且重复进行这个过程,直到父子节点之间满足大小关系为止。这就是从上往下的堆化方法

建堆的时间复杂度,为什么?

面经八股文_第1张图片

快排解释和空间复杂度

扑克牌抽任意张,至少要抽几张才能6个相同花色?

考虑最坏情况,前20张是各5张不同花色,2张大小王,最后一张任意花色就好,所以23张

多态如何实现、内存布局

多态

IPC通信

共享内存

介绍最小生成树、如何实现

prim算法

将点分为两个集合,一个集合为已经在最小生成树中的节点,另外一个不在,从一个节点作为初始的最小生成树节点,每次选择两个集合之中最近的一条边,然后将不在最小生成树的节点加入最小生成树集合中,直到所有点都加入集合,加入的边长之和为最小生成树

kruskal算法

将所有边长从小到大进行排序,然后用并查集维护,两个点是否为同一个集合,若不为则加入最小生成树集合,直到并查集大小为点的个数,即为最小生成树。

如何删除大文件中重复的行

将文件分为多个文件,每个文件按照字典序排序,并且遍历将字符串hash,然后同时遍历这几个文件,每次取出最小的字典序,加入答案文件中,判断是否出现过,若出现则跳过。

Delete和delete[]

1、 动态数组管理new一个数组时,[]中必须是一个整数,但是不一定是常量整数,普通数组必须是一个常量整数;

2、 new动态数组返回的并不是数组类型,而是一个元素类型的指针;

3、 delete[]时,数组中的元素按逆序的顺序进行销毁;

const、static关键词

static
不考虑类的情况
隐藏。所有不加static的全局变量和函数具有全局可见性,可以在其他文件中使用,加了之后只能在该文件所在的编译模块中使用
默认初始化为0,包括未初始化的全局静态变量与局部静态变量,都存在全局未初始化区
静态变量在函数内定义,始终存在,且只进行一次初始化,具有记忆性,其作用范围与局部变量相同,函数退出后仍然存在,但不能使用
考虑类的情况
static成员变量:只与类关联,不与类的对象关联。定义时要分配空间,不能在类声明中初始化,必须在类定义体外部初始化,初始化时不需要标示为static;可以被非static成员函数任意访问。
static成员函数:不具有this指针,无法访问类对象的非static成员变量和非static成员函数;不能被声明为const、虚函数和volatile;可以被非static成员函数任意访问
const
不考虑类的情况
const常量在定义时必须初始化,之后无法更改
const形参可以接收const和非const类型的实参,例如// i 可以是 int 型或者 const int 型void fun(const int& i){ //…}
考虑类的情况
const成员变量:不能在类定义外部初始化,只能通过构造函数初始化列表进行初始化,并且必须有构造函数;不同类对其const数据成员的值可以不同,所以不能在类中声明时初始化
const成员函数:const对象不可以调用非const成员函数;非const对象都可以调用;不可以改变非mutable(用该关键字声明的变量可以在const成员函数中被修改)数据的值
补充一点const相关:const修饰变量是也与static有一样的隐藏作用。只能在该文件中使用,其他文件不可以引用声明使用。 因此在头文件中声明const变量是没问题的,因为即使被多个文件包含,链接性都是内部的,不会出现符号冲突。

手撕快排

#include
using namespace std;
int n;
int a[1000005];
void q_sort(int l,int r)
{
    if(l>=r)return;
    int i=l-1,j=r+1;
    int x=a[(l+r)>>1];
    while(i<j)
    {
        do i++;while(a[i]<x);
        do j--;while(a[j]>x);
        if(i<j)swap(a[i],a[j]);
    }
    q_sort(l,j);
    q_sort(j+1,r);
    
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    q_sort(1,n);
    for(int i=1;i<=n;i++)
    cout<<a[i]<<" ";
}

手撕质数

朴素筛法

#include 
#include 

using namespace std;

const int N= 1000010;

int primes[N], cnt;
bool st[N];

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i]) continue;
        primes[cnt ++ ] = i;
        for (int j = i + i; j <= n; j += i)
            st[j] = true;
    }
}

int main()
{
    int n;
    cin >> n;

    get_primes(n);

    cout << cnt << endl;

    return 0;
}


线性筛法

#include 
#include 

using namespace std;

const int N= 1000010;

int primes[N], cnt;
bool st[N];

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int main()
{
    int n;
    cin >> n;

    get_primes(n);

    cout << cnt << endl;

    return 0;
}

手撕堆排序

#include
using namespace std;
int n,m,cnt;
const int N=1e5+10;
int h[N];
void down(int u)
{
    int t=u;
    if(u*2<=cnt&&h[u*2]<h[t])t=2*u;
    if(u*2+1<=cnt&&h[u*2+1]<h[t])t=2*u+1;
    if(u!=t)
    {
        swap(h[u],h[t]);
        down(t);
    }
}
int main()
{
    cin>>n>>m;
    cnt=n;
    for(int i=1;i<=n;i++)
    cin>>h[i];
    for(int i=n/2;i>=1;i--)
    {
        down(i);
    }
    while(m--)
    {
        cout<<h[1]<<" ";
        h[1]=h[cnt--];
        down(1);
    }
}

C++11新特性

nullptr替代 NULL
引入了 auto 和 decltype 这两个关键字实现了类型推导
基于范围的 for 循环for(auto& i : res){}
类和结构体的中初始化列表
Lambda 表达式(匿名函数)
std::forward_list(单向链表)
右值引用和move语义

智能指针的原理、常用的智能指针及实现

智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源

常用的智能指针

(1) shared_ptr

实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。

智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针
每次创建类的新对象时,初始化指针并将引用计数置为1
当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数
对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数
调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)
(2) unique_ptr

unique_ptr采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。

(3) weak_ptr

weak_ptr:弱引用。 引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak_ptr打破环形引用。weak_ptr是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。

(4) auto_ptr

主要是为了解决“有异常抛出时发生内存泄漏”的问题 。因为发生异常而无法正常释放内存。

auto_ptr有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而unique_ptr则无拷贝语义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move()进行转移。

auto_ptr不支持拷贝和赋值操作,不能用在STL标准容器中。STL容器中的元素经常要支持拷贝、赋值操作,在这过程中auto_ptr会传递所有权,所以不能在STL中使用。

深拷贝 浅拷贝

class MyString
{
private:
   char *str;
public:
   MyString(const char *p=nullstr)//缺省构造函数
       :str(nullptr)
  {
     if(p!=nullptr)
    {
      int len=strlen(p)+1;
      str=new char[len];
      strcpy_s(str,lrn,p);
     }
  }
 
   MyString(const MyString& ms)//拷贝构造函数,深拷贝
  {
	 int n = strlen(ms.str) + 1;
	 *str = new char[n];
	 strcpy_s = (str, n, ms.str);
     //int *str
     // this->str=new int(*ms.str)
  }
 
   ~MyString()//析构函数
  {
  }
}

手撕多态

#include 
using namespace std;

class Base{
public:
	virtual void fun(){
		cout << " Base::func()" <<endl;
	}
};

class Son1 : public Base{
public:
	virtual void fun() override{
		cout << " Son1::func()" <<endl;
	}
};

class Son2 : public Base{

};

int main()
{
	Base* base = new Son1;
	base->fun();
	base = new Son2;
	base->fun();
	delete base;
	base = NULL;
	return 0;
}
// 运行结果
// Son1::func()
// Base::func()

面经八股文_第2张图片
面经八股文_第3张图片

面经八股文_第4张图片

拷贝构造 移动构造

我们用对象a初始化对象b,后对象a我们就不在使用了,但是对象a的空间还在呀(在析构之前),既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么为什么我们不能直接使用a的空间呢?这样就避免了新的空间的分配,大大降低了构造的成本。这就是移动构造函数设计的初衷;

拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制。浅层复制之所以危险,是因为两个指针共同指向一片内存空间,若第一个指针将其释放,另一个指针的指向就不合法了。

所以我们只要避免第一个指针释放空间就可以了。避免的方法就是将第一个指针(比如a->value)置为NULL,这样在调用析构函数的时候,由于有判断是否为NULL的语句,所以析构a的时候并不会回收a->value指向的空间;

移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用。意味着,移动构造函数的参数是一个右值或者将亡值的引用。也就是说,只用用一个右值,或者将亡值初始化另一个对象的时候,才会调用移动构造函数。而那个move语句,就是将一个左值变成一个将亡值。

你可能感兴趣的:(数据结构,算法,java)