实现移动构造函数时要将动态数组的控制权由传入对象转移给当前对象
这句话是正确的,C++中的移动构造函数可以用于将所有权从一个对象转移到另一个对象,从而提高程序的性能,特别是在处理动态分配的资源时更为重要。当使用指向堆中资源的指针时,移动构造函数可以实现资源的转移而不需要进行复制。在移动构造函数中,我们要做的就是将动态数组或指针的所有权向新对象转移,从而在原对象中释放这些资源,并避免不必要的复制。下面是一个示例:
#include <iostream>
#include <memory>
class Foo {
public:
Foo() : data(nullptr), size(0) {}
Foo(int n) : data(new int[n]), size(n) {}
~Foo() { if (data) delete[] data; }
// Move constructor
Foo(Foo&& other) {
data = other.data; // 移动指针
size = other.size; // 移动数据
other.data = nullptr; // 将原对象指针置为空
other.size = 0;
}
// Move assignment operator
Foo& operator=(Foo&& other) {
if (this != &other) { // 避免自我赋值
if (data) delete[] data; // 释放当前对象资源
data = other.data; // 移动指针
size = other.size; // 移动数据
other.data = nullptr; // 将原对象指针置为空
other.size = 0;
}
return *this;
}
private:
int* data; // 动态数组
int size; // 数组大小
};
int main() {
Foo f1(10); // 创建一个对象
Foo f2(std::move(f1)); // 将资源从f1移动到f2
Foo f3(20);
f3 = std::move(f2); // 将资源从f2移动到f3
return 0;
}
在这个例子中,Foo
类包含一个动态数组,当一个Foo
对象通过移动构造函数或移动赋值运算符移动到另一个对象时,我们需要将指针转移给接收对象。 然后,原始对象的指针必须被设置为NULL,以保证它指向的内存被认为是无效的。
这是对的。转移控制权的过程可以通过移动语义来实现
题目:
有一个带头结点的单链表head,其中可能出值域重复的结点,设计一个算法删除值域重复的结点。要求在主函数中调用设计的算法,给出结果。
思路:删除某个结点值的重复结点,只需要遍历单链表找到与之相同的结点删除即可。删除多个不同结点值的重复结点,可以按链表顺序一个一个调用删除函数即可。因此可以使用递归实现。
测试数据:
样例1:1、2、3、4、5、6、7、8、9、0
输出1:1、2、3、4、5、6、7、8、9、0
样例2:1、2、3、4、3、5、5、6、7、8
输出2:1、2、3、4、5、6、7、8
样例3:1、1、1、1、5、5、7、8、8、8
输出3:1、5、7、8
细节:
定位到重复结点s的前一个结点q,方便修改指针,进行建链操作:q->next=s->next;递归调用删除重载私有函数时,需要判断指针不为空,在此处犯过错。
#include<iostream>
using namespace std;
template<typename DataType>
struct Node
{
DataType data;//数据域
Node<DataType>*next;//指针域
};
template<class DataType>
class LinkList
{
public:
LinkList();//建立只有头结点一个空链表
LinkList(DataType a[], int n);//建立n个元素的单链表
~LinkList();//析构函数
void Delete();//删除操作
void PrintList();//输出
private:
Node<DataType>*head;
void Delete(Node<DataType>*p);//递归删除
};
template<class DataType>
LinkList<DataType>::LinkList()
{
head = new Node<DataType>;//生成头结点
head->next = nullptr;//判空条件
}
template<class DataType>
LinkList<DataType>::LinkList(DataType a[], int n)//头插法
{
head = new Node<DataType>;//生成头结点
head->next = nullptr;//判空条件
for (int i = 0; i < n; i++)
{
Node<DataType>*s=nullptr;
s = new Node<DataType>;
s->data = a[n - i - 1];
s->next = head->next;
head->next = s;
}
}
template<class DataType>
LinkList<DataType>::~LinkList()
{
while (head)
{
Node<DataType>*p = head;
head= head->next;
delete p;
}
}
template<class DataType>
void LinkList<DataType>::Delete()
{
Delete(head->next);
}
template<class DataType>
void LinkList<DataType>::Delete(Node<DataType>*p)
{
Node<DataType>*q = p;
while (q&&q->next)//遍历链表,查看是否有与p值域相同的结点
{
if (p->data == q->next->data)//有,删除s
{
Node<DataType>*s = q->next; //生成新结点s,指向重复结点
q->next = s->next;//修改指针,此时q->next是s的下一个结点,下一轮继续判断
delete s;
}
else//不同,判断下一个结点
q = q->next;
}
if(p->next)//查找下一个不同的结点
Delete(p->next);//递归,不能用空指针调用
}
template<class DataType>
void LinkList<DataType>::PrintList()
{
Node<DataType>*p = head->next;
while (p)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
int main()
{
int r[10] = { 1,2,3,4,3,5,5,6,7,8 };
LinkList<int>L(r, 10);
L.PrintList();
L.Delete();
L.PrintList();
return 0;
}
以上是我的做法
今天我的书到了,粗略看了一下剑指offer的目录居然瞥到上面的类似题目。
以下为书上题目的做法
删除链表结点两种思路:剑指offer上面限制了时间复杂度为O(1)
1.要想删除结点i,可以从链表的头结点a开始顺序遍历,发现结点h的m_pNext指向要删除的结点i,于是我们可以把结点h的的m_pNext指向i的下一个结点,即结点j。指针调整后,我们就可以安全地删除结点i并保证链表没有断开,由于是顺序查找,时间复杂度为O(n)。——简言之就是我上面删除一个相同结点的部分。
是不是一定要定位到被删除结点的前一个结点呢?答案是否定的。我们可以很方便地得到要删除结点的下一个结点。如果我们把下一个结点的内容复制到要删除的结点上覆盖原有的内容,再把下一个结点删除,那是不是就相当于把当前需要删除的结点删除了?
2.基于上述想法:我们要删除结点i,先把i的下一个结点j复制到i,然后把i的指针指向结点j的下一个结点。此时在删除结点j,其效果刚好是把结点i删除了。
细节:如果链表中只有一个结点,而我们又要删除链表的头结点,那么,此时我们在删除结点之后,还需要把链表的头结点设置为nullptr。
书上代码:
void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted)
{
if(!pListHead || !pToBeDeleted)
return;
// 要删除的结点不是尾结点
if(pToBeDeleted->m_pNext != nullptr)
{
ListNode* pNext = pToBeDeleted->m_pNext;
pToBeDeleted->m_nValue = pNext->m_nValue;
pToBeDeleted->m_pNext = pNext->m_pNext;
delete pNext;
pNext = nullptr;
}
// 链表只有一个结点,删除头结点(也是尾结点)
else if(*pListHead == pToBeDeleted)
{
delete pToBeDeleted;
pToBeDeleted = nullptr;
*pListHead = nullptr;
}
// 链表中有多个结点,删除尾结点
else
{
ListNode* pNode = *pListHead;
while(pNode->m_pNext != pToBeDeleted)
{
pNode = pNode->m_pNext;
}
pNode->m_pNext = nullptr;
delete pToBeDeleted;
pToBeDeleted = nullptr;
}
}
额,我又翻了一页书,找到类似的另一题,上面打的有点多,不想删了,而且也是基础。
那么在排序的链表中删除重复结点呢?
刚看图,书上配图竟然有瑕疵,一个重复结点没有,额。。。七个结点画了5个
根据它的意思,图应该是这样
a:1->2->3->3->4->4->5
b:1->2->5
怎么感觉跟我的题目意思不大一样。。。
看看它的思路吧【我已经有点不想打了】
解决这个问题的第一步是确定删除函数的参数。当然,这个函数需要输出入待删除结点的头结点。头结点可能与后面的结点重复,也就是说头结点也可能被删除,因此删除函数应该声明为void deleteDuplication(ListNode**pHead),而不是void deleteDuplication(ListNode*pHead)。
接下来我们从头遍历整个链表。如果当前结点(代码中的pNode)的值与下一个结点的值相同,那么它们就是重复的结点,都可以被删除。为了保证删除之后的链表仍然是相连的,我们要把当前结点的前一个结点(代码中的pPreNode)和后面值比当前结点的值大的结点相连。我们要确保pPreNode始终与下一个没有重复的结点连接在一起。
书上代码:
void DeleteDuplication(ListNode** pHead)
{
if(pHead == nullptr || *pHead == nullptr)
return;
ListNode* pPreNode = nullptr;
ListNode* pNode = *pHead;
while(pNode != nullptr)
{
ListNode *pNext = pNode->m_pNext;
bool needDelete = false;
if(pNext != nullptr && pNext->m_nValue == pNode->m_nValue)
needDelete = true;
if(!needDelete)
{
pPreNode = pNode;
pNode = pNode->m_pNext;
}
else
{
int value = pNode->m_nValue;
ListNode* pToBeDel = pNode;
while(pToBeDel != nullptr && pToBeDel->m_nValue == value)
{
pNext = pToBeDel->m_pNext;
delete pToBeDel;
pToBeDel = nullptr;
pToBeDel = pNext;
}
if(pPreNode == nullptr)
*pHead = pNext;
else
pPreNode->m_pNext = pNext;
pNode = pNext;
}
}
}
欢迎批评指正!