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++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方