「C++之STL」关于在模拟实现STL容器中的深浅拷贝问题

简介: 「C++之STL」关于在模拟实现STL容器中的深浅拷贝问题

前言

在学习STL容器中,不仅需要学会容器的使用,同时也需要了解容器的大体框架以及各个函数的模拟实现才能更好的去了解这个容器;

杨辉三角

在LeetCode中有一道这样的题目,给定一个非负整数 numRow ,生成「杨辉三角」的前 numRows 行;

[题目链接]

从图中可知杨辉三角的概念,即每一个数都是它上方左右两数的和,且整个三角形呈对称关系;

这题若是使用c语言的话可以直接用二维数组的思路进行作答;

//c
int** generate(int numRows, int* returnSize, int** returnColumnSizes){
    int** ret = malloc(sizeof(int*)*numRows); //申请返回的指针空间
    *returnSize = numRows; //返回行数
    *returnColumnSizes = malloc(sizeof(int*)*numRows); //为每一列分配空间
    for(int i=0;i<numRows;i++)
    {
        ret[i] = malloc(sizeof(int)*(i+1));
        //分配每一行的个数
        (*returnColumnSizes)[i] = i + 1;
        //为第一个以及最后一个赋值
        ret[i][0] = 1;
        ret[i][i] = 1;
        for(int j=1;j<i;j++)
        {
            ret[i][j] = ret[i-1][j-1] + ret[i-1][j];
        }
    }
    return ret;
}

若是使用C++的话则可以使用STL容器中的vector来模仿二维数组;

//c++
class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>>triangle(numRows);
        for(int i = 0;i<numRows;i++){
            triangle[i].resize(i+1,0);
            triangle[i].front() = triangle[i].back() = 1;
        }
        for(int i = 0;i<numRows;i++){
            for(int j = 0;j<triangle[i].size();j++){
                if(triangle[i][j]!=1){
                    triangle[i][j] = triangle[i-1][j-1]+triangle[i-1][j];
                }
            }
        }
        return triangle;
    }
};

且其思路与C语言中的二维数组法相同;

深浅拷贝问题

[【C++】STL之String模拟实现 ]

在该篇文章中,我写出了对于类似拷贝构造或者扩容时应该进行的深浅拷贝;

在学过一段时间的C++后就能知道,在默认成员函数中存在拷贝构造函数以及赋值重载等函数;

这些默认成员函数在没有显式定义时,编译器会自动生成一个默认的对应函数;

而对于编译器自动生成的拷贝构造以及赋值重载函数,对于内置类型将会进行值拷贝,而对于自定义类型来说将会调用它的默认构造函数;

在这篇文章中对于类似扩容,拷贝构造以及赋值重载函数,采用的方法都是:

开辟一块新的空间,再将原先的数据使用memcpy函数或者在string中使用的strcpy函数拷贝到新的空间;

拷贝构造
//拷贝
My_string::string::string(const string& str)
  :_str(new char[str._size+1])
  ,_size(str._size)
  ,_capacity(str._size)
{
  memcpy(_str, str._str,str._size);
  _str[_size] = '\0';
}
扩容
//扩容
 void My_string::string::reserve(size_t n)
 {
   if (n > _capacity) {
     char* temp = new char[n+1];//多开1的空间确保n为有效数据的空间而+1为给'\0'的无效空间
     strcpy(temp, _str);
     _capacity = n;
     delete[]_str;
     _str = temp;
   }
 }
赋值重载
My_string::string& My_string::string::operator=(const string& str)//赋值操作符重载
 {
  if (this != &str) {
     char* temp = new char[str._capacity+1];
     strcpy(temp, str._str);
     delete[]_str;
     _str = temp;
     _size = str._size;
     _capacity = str._capacity;
   }
   return *this;
 }

模拟实现的vector对题目杨辉三角引发的程序崩溃

同时,我模拟实现了一个vector容器,这个容器的完成程度暂且不提,在这里的拷贝构造函数,扩容以及赋值重载依旧按照模拟实现string那样使用memcpy进行原数据拷贝到新空间的方法;

//拷贝构造
  vector(const vector<T>&v){
    T* tmp = new T[v.size()];
    _start = tmp;
    memcpy(tmp,v._start,sizeof(T)*v.size());
    _finish = _end_of_storage = _start + v.size();
  }
  /*扩容reserve*/
  void reserve(size_t n){
    if(n>capacity()){
      size_t count = size();
      T* tmp = new T[n];
      memcpy(tmp,_start,sizeof(T)*count);
      delete[]_start;
      _start = tmp;
      _finish = _start+count;
      _end_of_storage = _start+n;
    }
   }

而使用这种方法对杨辉三角题目进行测试时,程序将崩溃;

而程序崩溃的原因为析构函数;

原因

当程序在析构函数处崩溃且析构函数并没有其他问题的时候,应该及时将问题的思路转变到对象的深浅拷贝问题;

而这里出的错误正是使用memcpy函数进行拷贝从而造成的浅拷贝;

那为什么在这里使用memcpy时将会造成浅拷贝;

在模拟这些成员函数中,一般优先会想到的类型为内置类型,即语言标准指定,编译器自带的类型;

如果为vector< int >,vector< char >将可以使用memcpy进行拷贝;

但是vector这类的容器为一种类模板,对于类模板来说,其给的模板参数不一定一定就是内置类型,也有可能出现自定义类型;

就如使用vector< vector< int > >来说,最外层的模板参数为一个自定义类型,而内层的模板参数为一个内置类型int;

而如果在这里使用了memcpy进行原数据到新空间的拷贝将会怎么做;

表面上这里进行了拷贝,但实际上,进行了深拷贝的只有最外层;

对于内层而言只是使用了memcpy将对应的对象进行拷贝;

也就是说,这里出现了指针指向同一块空间的问题;

而若是在这种情况下则会出现重复析构的问题;


解决办法

在这种情况下只能摒弃以往的使用memcpy进行数据拷贝,并使用赋值进行拷贝;

以拷贝构造为例:

vector(const vector<T>& v){
  _start = new T[v.size()];
  for(size_t i = 0;i<v.size();++i){
    _start[i] = v._start[i];//若是自定义类型将会去调用它的赋值重载
  }
  _finish = _start + v.size();
  _end_of_storage = _start + v.capacity();
}
相关文章
|
9天前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
15 1
|
22天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
39 7
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
72 4
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
85 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
65 2
|
25天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
32 0
|
2月前
|
存储 设计模式 C++
【C++】优先级队列(容器适配器)
本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。
42 0
|
28天前
|
监控 NoSQL 时序数据库
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
201 77
|
1月前
|
监控 Docker 容器
在Docker容器中运行打包好的应用程序
在Docker容器中运行打包好的应用程序
|
9天前
|
Ubuntu Linux 开发工具
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖项打包成标准化单元(容器),确保在任何支持 Docker 的操作系统上一致运行。容器共享主机内核,提供轻量级、高效的执行环境。本文介绍如何在 Ubuntu 上安装 Docker,并通过简单步骤验证安装成功。后续文章将探讨使用 Docker 部署开源项目。优雅草央千澈 源、安装 Docker 包、验证安装 - 适用场景:开发、测试、生产环境 通过以上步骤,您可以在 Ubuntu 系统上成功安装并运行 Docker,为后续的应用部署打下基础。
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈

热门文章

最新文章