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#如何调用C++库

《C#如何调用C++库》:本文主要介绍C#如何调用C++库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录方法一:使用P/Invoke1. 导出C++函数2. 定义P/Invoke签名3. 调用C++函数方法二:使用C++/CLI作为桥接1. 创建C++/CL

Java对象转换的实现方式汇总

《Java对象转换的实现方式汇总》:本文主要介绍Java对象转换的多种实现方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java对象转换的多种实现方式1. 手动映射(Manual Mapping)2. Builder模式3. 工具类辅助映

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

Python中判断对象是否为空的方法

《Python中判断对象是否为空的方法》在Python开发中,判断对象是否为“空”是高频操作,但看似简单的需求却暗藏玄机,从None到空容器,从零值到自定义对象的“假值”状态,不同场景下的“空”需要精... 目录一、python中的“空”值体系二、精准判定方法对比三、常见误区解析四、进阶处理技巧五、性能优化

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

如何高效移除C++关联容器中的元素

《如何高效移除C++关联容器中的元素》关联容器和顺序容器有着很大不同,关联容器中的元素是按照关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的,本文介绍了如何高效移除C+... 目录一、简介二、移除给定位置的元素三、移除与特定键值等价的元素四、移除满足特android定条件的元

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下: