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

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

C#文件复制异常:"未能找到文件"的解决方案与预防措施

《C#文件复制异常:未能找到文件的解决方案与预防措施》在C#开发中,文件操作是基础中的基础,但有时最基础的File.Copy()方法也会抛出令人困惑的异常,当targetFilePath设置为D:2... 目录一个看似简单的文件操作问题问题重现与错误分析错误代码示例错误信息根本原因分析全面解决方案1. 确保

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

使用Java读取本地文件并转换为MultipartFile对象的方法

《使用Java读取本地文件并转换为MultipartFile对象的方法》在许多JavaWeb应用中,我们经常会遇到将本地文件上传至服务器或其他系统的需求,在这种场景下,MultipartFile对象非... 目录1. 基本需求2. 自定义 MultipartFile 类3. 实现代码4. 代码解析5. 自定

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

MySQL 临时表与复制表操作全流程案例

《MySQL临时表与复制表操作全流程案例》本文介绍MySQL临时表与复制表的区别与使用,涵盖生命周期、存储机制、操作限制、创建方法及常见问题,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小... 目录一、mysql 临时表(一)核心特性拓展(二)操作全流程案例1. 复杂查询中的临时表应用2. 临时

MySQL实现多源复制的示例代码

《MySQL实现多源复制的示例代码》MySQL的多源复制允许一个从服务器从多个主服务器复制数据,这在需要将多个数据源汇聚到一个数据库实例时非常有用,下面就来详细的介绍一下,感兴趣的可以了解一下... 目录一、多源复制原理二、多源复制配置步骤2.1 主服务器配置Master1配置Master2配置2.2 从服

MySQL配置多主复制的实现步骤

《MySQL配置多主复制的实现步骤》多主复制是一种允许多个MySQL服务器同时接受写操作的复制方式,本文就来介绍一下MySQL配置多主复制的实现步骤,具有一定的参考价值,感兴趣的可以了解一下... 目录1. 环境准备2. 配置每台服务器2.1 修改每台服务器的配置文件3. 安装和配置插件4. 启动组复制4.