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

相关文章

在java中如何将inputStream对象转换为File对象(不生成本地文件)

《在java中如何将inputStream对象转换为File对象(不生成本地文件)》:本文主要介绍在java中如何将inputStream对象转换为File对象(不生成本地文件),具有很好的参考价... 目录需求说明问题解决总结需求说明在后端中通过POI生成Excel文件流,将输出流(outputStre

C++ 中的 if-constexpr语法和作用

《C++中的if-constexpr语法和作用》if-constexpr语法是C++17引入的新语法特性,也被称为常量if表达式或静态if(staticif),:本文主要介绍C++中的if-c... 目录1 if-constexpr 语法1.1 基本语法1.2 扩展说明1.2.1 条件表达式1.2.2 fa

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++从序列容器中删除元素的四种方法

《C++从序列容器中删除元素的四种方法》删除元素的方法在序列容器和关联容器之间是非常不同的,在序列容器中,vector和string是最常用的,但这里也会介绍deque和list以供全面了解,尽管在一... 目录一、简介二、移除给定位置的元素三、移除与某个值相等的元素3.1、序列容器vector、deque

C++常见容器获取头元素的方法大全

《C++常见容器获取头元素的方法大全》在C++编程中,容器是存储和管理数据集合的重要工具,不同的容器提供了不同的接口来访问和操作其中的元素,获取容器的头元素(即第一个元素)是常见的操作之一,本文将详细... 目录一、std::vector二、std::list三、std::deque四、std::forwa

C++字符串提取和分割的多种方法

《C++字符串提取和分割的多种方法》在C++编程中,字符串处理是一个常见的任务,尤其是在需要从字符串中提取特定数据时,本文将详细探讨如何使用C++标准库中的工具来提取和分割字符串,并分析不同方法的适用... 目录1. 字符串提取的基本方法1.1 使用 std::istringstream 和 >> 操作符示

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

利用Python和C++解析gltf文件的示例详解

《利用Python和C++解析gltf文件的示例详解》gltf,全称是GLTransmissionFormat,是一种开放的3D文件格式,Python和C++是两个非常强大的工具,下面我们就来看看如何... 目录什么是gltf文件选择语言的原因安装必要的库解析gltf文件的步骤1. 读取gltf文件2. 提