对于拷贝构造函数和赋值构造函数的理解

来源:互联网 发布:孔子知天命 编辑:程序博客网 时间:2024/06/08 13:19


对于拷贝构造函数和赋值构造函数的理解

昨天晚上在看智能指针的时候,我发现自己连一个拷贝构造函数和赋值构造函数都写不出来,自己就尝试写了一个版本,结果发现错误百出,对于拷贝构造函数和赋值构造函数的理解仅仅停留在理论的方面,而不知其中太多的内涵。

比如我们都知道拷贝构造函数和赋值构造函数最大的不同在于:

 

拷贝构造是确确实实构造一个新的对象,并给新对象的私有成员赋上参数对象的私有成员的值,新构造的对象和参数对象地址是不一样的,所以如果该类中有一个私有成员是指向堆中某一块内存,如果仅仅对该私有成员进行浅拷贝,那么会出现多个指针指向堆中同一块内存,这是会出现问题,如果那块内存被释放了,就会出现其他指针指向一块被释放的内存,出现未定义的值的问题,如果深拷贝,就不会出现问题,因为深拷贝,不会出现指向堆中同一块内存的问题,因为每一次拷贝,都会开辟新的内存供对象存放其值。

下面是浅拷贝构造函数的代码:

复制代码
#include <iostream>using namespace std;class A{private:    int* n;public:    A()    {        n = new int[10];        n[0] = 1;        cout<<"constructor is called\n";    }    A(const A& a)    {        n = a.n;        cout<<"copy constructor is called\n";    }    ~A()    {        cout<<"destructor is called\n";        delete n;    }    void get()    {        cout<<"n[0]: "<<n[0]<<endl;    }};int main(){    A* a = new A();    A b = *a;    delete a;    b.get();    return 0;}
复制代码

运行结果如下:

 

下面是深拷贝构造函数的代码:

复制代码
#include <iostream>#include <string.h>using namespace std;class A{private:    int* n;public:    A()    {        n = new int[10];        n[0] = 1;        cout<<"constructor is called\n";    }    A(const A& a)    {        n = new int[10];        memcpy(n, a.n, 10);  //通过按字节拷贝,将堆中一块内存存储到另一块内存        cout<<"copy constructor is called\n";    }    ~A()    {        cout<<"destructor is called\n";        delete n;    }    void get()    {        cout<<"n[0]: "<<n[0]<<endl;    }};int main(){    A* a = new A();    A b = *a;    delete a;    b.get();    return 0;}
复制代码

运行截图如下:

 

 

但是赋值构造函数是将一个参数对象中私有成员赋给一个已经在内存中占据内存的对象的私有成员,赋值构造函数被赋值的对象必须已经在内存中,否则调用的将是拷贝构造函数,当然赋值构造函数也有深拷贝和浅拷贝的问题。当然赋值构造函数必须能够处理自我赋值的问题,因为自我赋值会出现指针指向一个已经释放的内存。还有赋值构造函数必须注意它的函数原型,参数必须是引用类型,返回值也必须是引用类型,否则在传参和返回的时候都会再次调用一次拷贝构造函数。

 

复制代码
#include <iostream>#include <string.h>using namespace std;class A{private:    int* n;public:    A()    {        n = new int[10];        n[0] = 1;        cout<<"constructor is called\n";    }    A(const A& a) //拷贝构造函数的参数一定是引用,不能不是引用,不然会出现无限递归    {        n = new int[10];        memcpy(n, a.n, 10);  //通过按字节拷贝,将堆中一块内存存储到另一块内存        cout<<"copy constructor is called\n";    }    A& operator=(const A& a)  //记住形参和返回值一定要是引用类型,否则传参和返回时会自动调用拷贝构造函数    {        if(this == &a)     //为什么需要进行自我赋值判断呢?因为下面要进行释放n的操作,如果是自我赋值,而没有进行判断的话,那么就会出现讲一个释放了的内存赋给一个指针            return *this;        if(n != NULL)        {            delete n;            n == NULL;   //记住释放完内存将指针赋为NULL        }        n = new int[10];        memcpy(n, a.n, 10);        cout<<"assign constructor is called\n";        return *this;    }    ~A()    {        cout<<"destructor is called\n";        delete n;
     n = NULL; //记住释放完内存将指针赋为NULL }
void get() { cout<<"n[0]: "<<n[0]<<endl; }};int main(){ A* a = new A(); A* b = new A(); *b = *a; delete a; b->get();return 0;}
复制代码

运行截图如下:

如果我们在赋值构造函数的形参和返回值不用引用类型,代码如下:

复制代码
#include <iostream>#include <string.h>using namespace std;class A{private:    int* n;public:    A()    {        n = new int[10];        n[0] = 1;        cout<<"constructor is called\n";    }    A(const A& a) //拷贝构造函数的参数一定是引用,不能不是引用,不然会出现无限递归    {        n = new int[10];        memcpy(n, a.n, 10);  //通过按字节拷贝,将堆中一块内存存储到另一块内存        cout<<"copy constructor is called\n";    }    A operator=(const A a)   //传参和返回值设置错误    {        if(this == &a)            return *this;        if(n != NULL)        {            delete n;            n == NULL;        }        n = new int[10];        memcpy(n, a.n, 10);        cout<<"assign constructor is called\n";        return *this;    }    ~A()    {        cout<<"destructor is called\n";        delete n;    }    void get()    {        cout<<"n[0]: "<<n[0]<<endl;    }};int main(){    A* a = new A();    A* b =  new A();    *b = *a;    delete a;    b->get();    while(1)    {}    return 0;}
复制代码

运行截图如下:

 

多了两次的拷贝构造函数的调用和两次析构函数的调用。

 

好文要顶 关注我 收藏该文  
GOD_YCA
关注 - 6
粉丝 - 44
+加关注
7
0
« 上一篇:递归和数学归纳法
» 下一篇:再论智能指针
posted @ 2013-01-16 16:20 GOD_YCA 阅读(15337) 评论(2) 编辑 收藏
  
#1楼 2013-01-16 16:37 john23.net  
感谢分享
支持(0)反对(0)
  
#2楼 2016-10-15 21:26 yuanww  
引用写的很好
支持(0)反对(0)
刷新评论刷新页面返回顶部
【推荐】50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
【推荐】中铁、中石油等大型企业的复杂报表解决方案
【活动】阿里云海外云服务全面降价助力企业全球布局
【实用】40+篇云服务器操作及运维基础知识!
优达学城0509
最新IT新闻:
· WannaCry结束了? 安专家注册域名掐断病毒传播
· Edge浏览器曝新漏洞:可被攻击者窃取cookie与密码数据
· 链家APP被苹果下架 因为购房者大面积投诉?
· 谷歌发力!Android 8.0解决安卓最大顽疾:完美
· 超级福利!支付宝奖励金升级:最高奖励999元 全国支持
» 更多新闻...
阿里云E4
最新知识库文章:
· 软件开发为什么很难
· 唱吧DevOps的落地,微服务CI/CD的范本技术解读
· 程序员,如何从平庸走向理想?
· 我为什么鼓励工程师写blog
· 怎么轻松学习JavaScript
» 更多知识库文章...

对于拷贝构造函数和赋值构造函数的理解

昨天晚上在看智能指针的时候,我发现自己连一个拷贝构造函数和赋值构造函数都写不出来,自己就尝试写了一个版本,结果发现错误百出,对于拷贝构造函数和赋值构造函数的理解仅仅停留在理论的方面,而不知其中太多的内涵。

比如我们都知道拷贝构造函数和赋值构造函数最大的不同在于:

 

拷贝构造是确确实实构造一个新的对象,并给新对象的私有成员赋上参数对象的私有成员的值,新构造的对象和参数对象地址是不一样的,所以如果该类中有一个私有成员是指向堆中某一块内存,如果仅仅对该私有成员进行浅拷贝,那么会出现多个指针指向堆中同一块内存,这是会出现问题,如果那块内存被释放了,就会出现其他指针指向一块被释放的内存,出现未定义的值的问题,如果深拷贝,就不会出现问题,因为深拷贝,不会出现指向堆中同一块内存的问题,因为每一次拷贝,都会开辟新的内存供对象存放其值。

下面是浅拷贝构造函数的代码:

复制代码
#include <iostream>using namespace std;class A{private:    int* n;public:    A()    {        n = new int[10];        n[0] = 1;        cout<<"constructor is called\n";    }    A(const A& a)    {        n = a.n;        cout<<"copy constructor is called\n";    }    ~A()    {        cout<<"destructor is called\n";        delete n;    }    void get()    {        cout<<"n[0]: "<<n[0]<<endl;    }};int main(){    A* a = new A();    A b = *a;    delete a;    b.get();    return 0;}
复制代码

运行结果如下:

 

下面是深拷贝构造函数的代码:

复制代码
#include <iostream>#include <string.h>using namespace std;class A{private:    int* n;public:    A()    {        n = new int[10];        n[0] = 1;        cout<<"constructor is called\n";    }    A(const A& a)    {        n = new int[10];        memcpy(n, a.n, 10);  //通过按字节拷贝,将堆中一块内存存储到另一块内存        cout<<"copy constructor is called\n";    }    ~A()    {        cout<<"destructor is called\n";        delete n;    }    void get()    {        cout<<"n[0]: "<<n[0]<<endl;    }};int main(){    A* a = new A();    A b = *a;    delete a;    b.get();    return 0;}
复制代码

运行截图如下:

 

 

但是赋值构造函数是将一个参数对象中私有成员赋给一个已经在内存中占据内存的对象的私有成员,赋值构造函数被赋值的对象必须已经在内存中,否则调用的将是拷贝构造函数,当然赋值构造函数也有深拷贝和浅拷贝的问题。当然赋值构造函数必须能够处理自我赋值的问题,因为自我赋值会出现指针指向一个已经释放的内存。还有赋值构造函数必须注意它的函数原型,参数必须是引用类型,返回值也必须是引用类型,否则在传参和返回的时候都会再次调用一次拷贝构造函数。

 

复制代码
#include <iostream>#include <string.h>using namespace std;class A{private:    int* n;public:    A()    {        n = new int[10];        n[0] = 1;        cout<<"constructor is called\n";    }    A(const A& a) //拷贝构造函数的参数一定是引用,不能不是引用,不然会出现无限递归    {        n = new int[10];        memcpy(n, a.n, 10);  //通过按字节拷贝,将堆中一块内存存储到另一块内存        cout<<"copy constructor is called\n";    }    A& operator=(const A& a)  //记住形参和返回值一定要是引用类型,否则传参和返回时会自动调用拷贝构造函数    {        if(this == &a)     //为什么需要进行自我赋值判断呢?因为下面要进行释放n的操作,如果是自我赋值,而没有进行判断的话,那么就会出现讲一个释放了的内存赋给一个指针            return *this;        if(n != NULL)        {            delete n;            n == NULL;   //记住释放完内存将指针赋为NULL        }        n = new int[10];        memcpy(n, a.n, 10);        cout<<"assign constructor is called\n";        return *this;    }    ~A()    {        cout<<"destructor is called\n";        delete n;
     n = NULL; //记住释放完内存将指针赋为NULL }
void get() { cout<<"n[0]: "<<n[0]<<endl; }};int main(){ A* a = new A(); A* b = new A(); *b = *a; delete a; b->get();return 0;}
复制代码

运行截图如下:

如果我们在赋值构造函数的形参和返回值不用引用类型,代码如下:

复制代码
#include <iostream>#include <string.h>using namespace std;class A{private:    int* n;public:    A()    {        n = new int[10];        n[0] = 1;        cout<<"constructor is called\n";    }    A(const A& a) //拷贝构造函数的参数一定是引用,不能不是引用,不然会出现无限递归    {        n = new int[10];        memcpy(n, a.n, 10);  //通过按字节拷贝,将堆中一块内存存储到另一块内存        cout<<"copy constructor is called\n";    }    A operator=(const A a)   //传参和返回值设置错误    {        if(this == &a)            return *this;        if(n != NULL)        {            delete n;            n == NULL;        }        n = new int[10];        memcpy(n, a.n, 10);        cout<<"assign constructor is called\n";        return *this;    }    ~A()    {        cout<<"destructor is called\n";        delete n;    }    void get()    {        cout<<"n[0]: "<<n[0]<<endl;    }};int main(){    A* a = new A();    A* b =  new A();    *b = *a;    delete a;    b->get();    while(1)    {}    return 0;}
复制代码

运行截图如下:

 

多了两次的拷贝构造函数的调用和两次析构函数的调用。

 

好文要顶 关注我 收藏该文  
GOD_YCA
关注 - 6
粉丝 - 44
+加关注
7
0
« 上一篇:递归和数学归纳法
» 下一篇:再论智能指针
posted @ 2013-01-16 16:20 GOD_YCA 阅读(15337) 评论(2) 编辑 收藏
  
#1楼 2013-01-16 16:37 john23.net  
感谢分享
支持(0)反对(0)
  
#2楼 2016-10-15 21:26 yuanww  
引用写的很好
支持(0)反对(0)
刷新评论刷新页面返回顶部
【推荐】50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
【推荐】中铁、中石油等大型企业的复杂报表解决方案
【活动】阿里云海外云服务全面降价助力企业全球布局
【实用】40+篇云服务器操作及运维基础知识!
优达学城0509
最新IT新闻:
· WannaCry结束了? 安专家注册域名掐断病毒传播
· Edge浏览器曝新漏洞:可被攻击者窃取cookie与密码数据
· 链家APP被苹果下架 因为购房者大面积投诉?
· 谷歌发力!Android 8.0解决安卓最大顽疾:完美
· 超级福利!支付宝奖励金升级:最高奖励999元 全国支持
» 更多新闻...
阿里云E4
最新知识库文章:
· 软件开发为什么很难
· 唱吧DevOps的落地,微服务CI/CD的范本技术解读
· 程序员,如何从平庸走向理想?
· 我为什么鼓励工程师写blog
· 怎么轻松学习JavaScript
» 更多知识库文章...
0 0
原创粉丝点击