C++类对象的复制-拷贝构造函数——The c + + class object replication - copy constructor

本文主要是介绍C++类对象的复制-拷贝构造函数——The c + + class object replication - copy constructor,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我们已经学习过了类的构造函数和析构函数的相关知识,对于普通类型的对象来说,他们之间的复制是很简单的,例如:

int a = 10;
int b =a;

  自己定义的类的对象同样是对象,谁也不能阻止我们用以下的方式进行复制,例如:

#include <iostream
using namespace
 std; 
 
class
 Test 

public

    Test(
int
 temp) 
    { 
        p1=temp; 
    } 
protected

    
int
 p1; 
 
}; 
 
void main
() 

    Test a(99); 
    Test b=a; 
}

  普通对象和类对象同为对象,他们之间的特性有相似之处也有不同之处,类对象内部存在成员变量,而普通对象是没有的,当同样的复制方法发生在不同的对象上的时候,那么系统对他们进行的操作也是不一样的,就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的,在上面的代码中,我们并没有看到拷贝构造函数,同样完成了复制工作,这又是为什么呢?因为当一个类没有自定义的拷贝构造函数的时候系统会自动提供一个默认的拷贝构造函数,来完成复制工作。

  下面,我们为了说明情况,就普通情况而言(以上面的代码为例),我们来自己定义一个与系统默认拷贝构造函数一样的拷贝构造函数,看看它的内部是如何工作的!


  代码如下:

#include <iostream
using namespace
 std; 
 
class
 Test 

public

    Test(
int
 temp) 
    { 
        p1=temp; 
    } 
    Test(Test &c_t)
//这里就是自定义的拷贝构造函数 

    { 
        
cout<<"进入copy构造函数
"<<endl; 
        p1=c_t.p1;
//这句如果去掉就不能完成复制工作了,此句复制过程的核心语句 

    } 
public

    
int
 p1; 
}; 
 
void main
() 

    Test a(99); 
    Test b=a; 
    
cout
<<b.p1; 
    
cin
.get(); 
}

  上面代码中的Test(Test &c_t)就是我们自定义的拷贝构造函数,拷贝构造函数的名称必须与类名称一致,函数的形式参数是本类型的一个引用变量,必须是引用

  当用一个已经初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用,如果你没有自定义拷贝构造函数的时候系统将会提供给一个默认的拷贝构造函数来完成这个过程,上面代码的复制核心语句就是通过Test(Test &c_t)拷贝构造函数内的p1=c_t.p1;语句完成的。如果取掉这句代码,那么b对象的p1属性将得到一个未知的随机值;

 

  下面我们来讨论一下关于浅拷贝和深拷贝的问题。

  就上面的代码情况而言,很多人会问到,既然系统会自动提供一个默认的拷贝构造函数来处理复制,那么我们没有意义要去自定义拷贝构造函数呀,对,就普通情况而言这的确是没有必要的,但在某写状况下,类体内的成员是需要开辟动态开辟堆内存,如果我们不自定义拷贝构造函数而让系统自己处理,那么就会导致堆内存的所属权产生混乱,试想一下,已经开辟的一端堆地址原来是属于对象a的,由于复制过程发生,b对象取得是a已经开辟的堆地址,一旦程序产生析构,释放堆的时候,计算机是不可能清楚这段地址是真正属于谁的,当连续发生两次析构的时候就出现了运行错误。

为了更详细的说明问题,请看如下的代码。

#include <iostream
using namespace
 std; 
 
class
 Internet 

public

    Internet(
char *name,char
 *address) 
    { 
        
cout<<"载入构造函数
"<<endl; 
        strcpy(Internet::name,name); 
        strcpy(Internet::address,address); 
        cname=
new char
[strlen(name)+1]; 
        
if
(cname!=NULL) 
        { 
            strcpy(Internet::cname,name); 
        } 
    } 
    Internet(Internet &temp) 
    { 
        
cout<<"载入COPY构造函数
"<<endl; 
        strcpy(Internet::name,temp.name); 
        strcpy(Internet::address,temp.address); 
        cname=
new char[strlen(name)+1];//这里注意,深拷贝的体现

        
if
(cname!=NULL) 
        { 
            strcpy(Internet::cname,name); 
        } 
    } 
    ~Internet() 
    { 
        
cout<<"载入析构函数
!"; 
        
delete
[] cname; 
        
cin
.get(); 
    } 
    
void
 show(); 
protected

    
char
 name[20]; 
    
char
 address[30]; 
    
char
 *cname; 
}; 
void
 Internet::show() 

    
cout
<<name<<":"<<address<<cname<<endl; 

void
 test(Internet ts) 

    
cout<<"载入test函数
"<<endl; 

void main
() 

    Internet a("
中国软件开发实验室
","www.cndev-lab.com"); 
    Internet b 
=
 a; 
    b.show(); 
    test(b); 
}

  上面代码就演示了深拷贝的问题,对对象bcname属性采取了新开辟内存的方式避免了内存归属不清所导致析构释放空间时候的错误,最后我必须提一下,对于上面的程序我的解释并不多,就是希望读者本身运行程序观察变化,进而深刻理解。

拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源但复制过程并未复制资源的情况视为浅拷贝


  浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错,这点尤其需要注意!

  以前我们的教程中讨论过函数返回对象产生临时变量的问题,接下来我们来看一下在函数中返回自定义类型对象是否也遵循此规则产生临时对象


  先运行下列代码:

#include <iostream
using namespace std; 
 
class Internet 

public
    Internet() 
    { 
         
    }; 
    Internet(
char *name,char *address) 
    { 
        
cout<<"载入构造函数"<<endl; 
        strcpy(Internet::name,name); 
    } 
    Internet(Internet &temp) 
    { 
        
cout<<"载入COPY构造函数"<<endl; 
        strcpy(Internet::name,temp.name); 
        
cin.get(); 
    } 
    ~Internet() 
    { 
        
cout<<"载入析构函数!"; 
        
cin.get(); 
    } 
protected
    
char name[20]; 
    
char address[20]; 
}; 
Internet tp() 

    Internet b("
中国软件开发实验室","www.cndev-lab.com"); 
    
return b; 

void main() 

    Internet a; 
    a=tp(); 
}

  从上面的代码运行结果可以看出,程序一共载入过析构函数三次,证明了由函数返回自定义类型对象同样会产生临时变量,事实上对象a得到的就是这个临时Internet类类型对象temp的值。

  这一下节的内容我们来说一下无名对象

  利用无名对象初始化对象系统不会不调用拷贝构造函数。

  那么什么又是无名对象呢?

  很简单,如果在上面程序的main函数中有:

  Internet ("中国软件开发实验室","www.cndev-lab.com");

  这样的一句语句就会产生一个无名对象,无名对象会调用构造函数但利用无名对象初始化对象系统不会不调用拷贝构造函数!

  下面三段代码是很见到的三种利用无名对象初始化对象的例子。

#include <iostream
using namespace std; 
 
class Internet 

public
    Internet(
char *name,char *address) 
    { 
        
cout<<"载入构造函数"<<endl; 

 strcpy(Internet::name,name); 
    } 
    Internet(Internet &temp) 
    { 
        
cout<<"载入COPY构造函数
"<<endl; 
        strcpy(Internet::name,temp.name); 
        
cin
.get(); 
    } 
    ~Internet() 
    { 
        
cout<<"载入析构函数
!"; 
    } 
public

    
char
 name[20]; 
    
char
 address[20]; 
}; 
 
void main
() 

    Internet a=Internet("
中国软件开发实验室
","www.cndev-lab.com"); 
    
cout
<<a.name; 
    
cin
.get(); 
}

  上面代码的运行结果有点出人意料,从思维逻辑上说,当无名对象创建了后,是应该调用自定义拷贝构造函数,或者是默认拷贝构造函数来完成复制过程的,但事实上系统并没有这么做,因为无名对象使用过后在整个程序中就失去了作用,对于这种情况c++会把代码看成是:

Internet a("中国软件开发实验室",www.cndev-lab.com);

  省略了创建无名对象这一过程,所以说不会调用拷贝构造函数。

 

  最后让我们来看看引用无名对象的情况。

#include <iostream>   
using namespace
 std;   
   
class
 Internet   
{   
public
:   
    Internet(
char *name,char
 *address)   
    {   
        
cout<<"载入构造函数
"<<endl;   
        strcpy(Internet::name,name);   
    }   
    Internet(Internet &temp)   
    {   
        
cout<<"载入COPY构造函数
"<<endl;   
        strcpy(Internet::name,temp.name);   
        
cin
.get();   
    }   
    ~Internet()   
    {   
        
cout<<"载入析构函数
!";   
    }   
public
:   
    
char
 name[20];   
    
char
 address[20];   
};   
   
void main
()   
{   
    Internet &a=Internet("
中国软件开发实验室","www.cndev-lab.com");   

 cout<<a.name; 
    
cin
.get();   
}

  引用本身是对象的别名,和复制并没有关系,所以不会调用拷贝构造函数,但要注意的是,在c++看来:

Internet &a=Internet("
中国软件开发实验室
","www.cndev-lab.com");

  是等价与
:

Internet a("
中国软件开发实验室
","www.cndev-lab.com");

  的,注意观察调用析构函数的位置(这种情况是在main()外调用,而无名对象本身是在main()内析构的)。

 

这篇关于C++类对象的复制-拷贝构造函数——The c + + class object replication - copy constructor的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1034652

相关文章

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

JavaScript对象转数组的三种方法实现

《JavaScript对象转数组的三种方法实现》本文介绍了在JavaScript中将对象转换为数组的三种实用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录方法1:使用Object.keys()和Array.map()方法2:使用Object.entr

C#利用Free Spire.XLS for .NET复制Excel工作表

《C#利用FreeSpire.XLSfor.NET复制Excel工作表》在日常的.NET开发中,我们经常需要操作Excel文件,本文将详细介绍C#如何使用FreeSpire.XLSfor.NET... 目录1. 环境准备2. 核心功能3. android示例代码3.1 在同一工作簿内复制工作表3.2 在不同

C++读写word文档(.docx)DuckX库的使用详解

《C++读写word文档(.docx)DuckX库的使用详解》DuckX是C++库,用于创建/编辑.docx文件,支持读取文档、添加段落/片段、编辑表格,解决中文乱码需更改编码方案,进阶功能含文本替换... 目录一、基本用法1. 读取文档3. 添加段落4. 添加片段3. 编辑表格二、进阶用法1. 文本替换2

使用MapStruct实现Java对象映射的示例代码

《使用MapStruct实现Java对象映射的示例代码》本文主要介绍了使用MapStruct实现Java对象映射的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、什么是 MapStruct?二、实战演练:三步集成 MapStruct第一步:添加 Mave

Java抽象类Abstract Class示例代码详解

《Java抽象类AbstractClass示例代码详解》Java中的抽象类(AbstractClass)是面向对象编程中的重要概念,它通过abstract关键字声明,用于定义一组相关类的公共行为和属... 目录一、抽象类的定义1. 语法格式2. 核心特征二、抽象类的核心用途1. 定义公共接口2. 提供默认实

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

Java中实现对象的拷贝案例讲解

《Java中实现对象的拷贝案例讲解》Java对象拷贝分为浅拷贝(复制值及引用地址)和深拷贝(递归复制所有引用对象),常用方法包括Object.clone()、序列化及JSON转换,需处理循环引用问题,... 目录对象的拷贝简介浅拷贝和深拷贝浅拷贝深拷贝深拷贝和循环引用总结对象的拷贝简介对象的拷贝,把一个

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned