深浅拷贝、STL迭代器失效

一、深浅拷贝区别

1、浅拷贝

C++中的浅拷贝是通过拷贝构造函数来实现的,如果程序员不主动编写拷贝构造函数和赋值函数,编译器将以浅拷贝的方式自动生成缺省的函数,也就是在拷贝时简单的复制某个对象的指针,这样很容易造成一些问题。

例如,假设String类有两个对象a和b,a.data的内容为"hello",b.data的内容为"world",当将a的值赋给b时,可能会出现以下3个问题:

1)b.data的内存没释放,造成内存泄漏;

2)b.data和a.data指向了同一块内存,a和b任何一方的值改变都会修改另一方的值;

3)在对象被析构时,data被释放了两次。

例:

cpp 复制代码
class ShallowCopyExample {
public:
    int num;
    int* ptr;
    // 这种构造函数使用了成员初始化列表(: 后面的部分)来初始化成员变量,比在构造函数体内赋值更高效。
    ShallowCopyExample(int n, int* p) : num(n), ptr(p) {}
    // 等价的构造函数写法:
    // ShallowCopyExample(int n, int* p) {
    //     num = n;
    //     ptr = p;
};

int main() {
    int value = 10;
    ShallowCopyExample obj1(5, &value);
    ShallowCopyExample obj2 = obj1;
    // 此时obj2.num的值为5,obj2.ptr和obj1.ptr指向相同的内存地址,即&value
    return 0;
}

2、深拷贝

深拷贝必须显示的提供拷贝构造函数和赋值运算符,而且新旧对象不共享内存,也就是说,在编写拷贝构造函数时会开辟一个新的内存空间。什么时候使用深拷贝?

1)一个对象以值传递的方式传入函数体;

2)一个对象以值传递的方式从函数体返回;

3)一个对象需要通过另外一个对象进行初始化。

例:

cpp 复制代码
class DeepCopyExample {
public:
    int num;
    int* ptr;
    DeepCopyExample(const DeepCopyExample& other) {     // 自定义的拷贝构造函数
        num = other.num;
        ptr = new int(*other.ptr); // 深拷贝,创建新的内存副本
    }
    DeepCopyExample(int n, int* p) : num(n), ptr(p) {}  // 普通构造函数
    ~DeepCopyExample() {
        delete ptr;   // 释放资源
    }
};

int main() {
    int value = 10;
    DeepCopyExample obj1(5, &value);
    DeepCopyExample obj2 = obj1;
    // 此时obj2.num的值为5,obj2.ptr指向一个新分配的内存区域,其值与obj1.ptr所指向的值相同
    return 0;
}

3、深浅拷贝区别:

浅拷贝不需要自己实现,编译器会自动生成缺省的拷贝构造函数,浅拷贝新旧对象共享一块内存,任何一方的值改变都会影响另一方;深拷贝需要自己手动编写拷贝构造函数,深拷贝新旧对象不共享内存。

二、STL迭代器失效

在 C++ 中,迭代器失效是指由于容器的操作导致迭代器指向的内存位置变得无效。这通常发生在容器进行插入、删除或扩容操作时。

导致迭代器失效的操作:

  1. 插入操作:在容器中插入元素可能会导致内存重新分配,从而使所有指向旧内存位置的迭代器失效。

  2. 删除操作:删除元素后,所有指向被删除元素及其后的元素的迭代器都会失效。

  3. 扩容操作:当容器需要扩展容量时,会重新分配内存,导致所有旧的迭代器失效。

迭代器失效分三种情况考虑,也是分三种数据结构考虑,分别为数组型,链表型,树型数据结构。

1、序列式容器(数组式容器)

对于序列式容器(如vector,deque),删除当前的iterator会使后面所有元素的iterator都失效。这是因为vetor、deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。

解决办法:

使用erase方法返回下一个有效迭代器的值。 iter = cont.erase(iter);

cpp 复制代码
for (iter = container.begin(); iter != container.end(); )
{
    if (*iter > 3)
      iter = container.erase(iter);    //erase的返回值是删除元素下一个元素的迭代器
    else{
        iter++;
    }
}

这样删除后iter指向的元素,返回的是下一个元素的迭代器,这个迭代器是vector内存调整过后新的有效的迭代器。

2、关联式容器

对于关联容器(如map, set,multimap,multiset),删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响。

解决办法:

erase迭代器只是被删元素的迭代器失效,但是返回值为void,所以要采用erase(iter++)的方式删除迭代器。

cpp 复制代码
for (iter = dataMap.begin(); iter != dataMap.end(); )
{
     int nKey = iter->first;
     string strValue = iter->second;

     if (nKey % 2 == 0)
     {
           map<int, string>::iterator tmpIter = iter;
           iter++;
           dataMap.erase(tmpIter);
           //dataMap.erase(iter++) 这样也行

     }else
     {
           iter++;
     }
}

dataMap.erase(iter++);这句话分三步走,先把iter传值到erase里面,然后iter自增,然后执行erase,所以iter在失效前已经自增了。

3、链表式容器

对于链表式容器(如list),删除当前的iterator,仅仅会使当前的iterator失效,这是因为list之类的容器,使用了链表来实现,插入、删除一个结点不会对其他结点造成影响。只要在erase时,递增当前iterator即可,并且erase方法可以返回下一个有效的iterator。

解决方式1:递增当前iterator

cpp 复制代码
for (iter = cont.begin(); it != cont.end();)
{
   (*iter)->doSomething();
   if (shouldDelete(*iter))
      cont.erase(iter++);
   else
      ++iter;
}

方式2:通过erase获得下一个有效的iterator

cpp 复制代码
for (iter = cont.begin(); iter != cont.end();)
{
   (*it)->doSomething();
   if (shouldDelete(*iter))
      iter = cont.erase(iter);  //erase删除元素,返回下一个迭代器
   else
      ++iter;
}
相关推荐
再卷也是菜2 小时前
C++篇(21)图
数据结构·c++·算法
星轨初途2 小时前
C++入门(算法竞赛类)
c++·经验分享·笔记·算法
Bona Sun2 小时前
单片机手搓掌上游戏机(十三)—pico运行fc模拟器之硬件准备
c语言·c++·单片机·游戏机
Bona Sun2 小时前
单片机手搓掌上游戏机(十八)—pico运行fc模拟器之更大屏幕
c语言·c++·单片机·游戏机
chenyuhao20242 小时前
MySQL索引特性
开发语言·数据库·c++·后端·mysql
小龙报3 小时前
【算法通关指南:数据结构和算法篇 】队列相关算法题:3.海港
数据结构·c++·算法·贪心算法·创业创新·学习方法·visual studio
辞旧 lekkk3 小时前
【c++】封装红黑树实现mymap和myset
c++·学习·算法·萌新
星轨初途3 小时前
C++的输入输出(上)(算法竞赛类)
开发语言·c++·经验分享·笔记·算法
极地星光4 小时前
Qt/C++ 单例模式深度解析:饿汉式与懒汉式实战指南
c++·qt·单例模式