C++学习:拷贝构造函数

C++学习:拷贝构造函数


摘要:
拷贝构造函数是一种特殊的构造函数,具有单个形参,此形参是对该类型的引用,当定义一个新对象并用一个同类型的对象对其进行初始化的时候,将显示的使用拷贝构造函数.并且在将对象作为形式参数或者是返回值的时候,这个时候就会去隐式的调用拷贝构造函数(Copy Constructor),拷贝构造函数在C++中具有这及其重要的意义,它对于对象的引用.效率.避免内存溢出等都具有这重要的作用.


提示

本文作者:章飞_906285288
博客地址:http://blog.csdn.net/qq_29924041


拷贝构造函数

在C++中,一个类在声明的时候,往往会为我们默认定义四个默认的成员函数:默认的构造函数,默认的析构函数,默认的拷贝构造函数,默认的赋值运算符.这四个默认的定义在C++类的使用的时候都具有这及其重要的低位,而拷贝构造函数就是其中的一个.它的作用主要体现在赋值拷贝的时候去使用.
首先讲解一下什么是拷贝:

/*
 * ===========================================================================
 *
 *       Filename:  copyTest1.cpp
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年06月06日 22时05分30秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace::std;


int main(int argc,char* argv[]){
  //一般的赋值拷贝
  int num1 = 10;
  int num2 = num1;
  printf("num1 value is %d address:%p\n",num1,&num1);
  printf("num2 value is %d address:%p\n",num2,&num2);

  return 0;
}

结果为:

 num1 value is 10 address:0x7ffcb4f65808
 num2 value is 10 address:0x7ffcb4f6580c

从上面的案例中可以看出来,num1初始化为10,这个时候将num1赋值给num2,此时对应的只是将数值拷贝到了num2指向的栈空间中,并没有改变num2的指向

针对上面的案例我们可以衍生到一般的函数传参(非指针类型的形参)

/*
 * ===========================================================================
 *
 *       Filename:  copyTest1.cpp
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年06月06日 22时05分30秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<memory.h>
using namespace::std;

struct student{
  char name[16];
  int age;
};

void printf_stu(student stu){
  cout<<"stu age"<<stu.age<<endl;
  cout<<"stu name"<<stu.name<<endl;
  printf("stu address:%p\n",&stu);
}



int main(int argc,char* argv[]){
  //一般的赋值拷贝

  student stu;
  memset(&stu,0,sizeof(stu));
  stu.age = 16;
  strcpy(stu.name,"zhangsan");
  printf("stu address:%p\n",&stu);
  printf_stu(stu);

  return 0;
}

输出结果为:

stu address:0x7ffc7fc14930
stu age16
stu namezhangsan
stu address:0x7ffc7fc14900

由上面可以看出来,对于结构体类型的形式参数的话,结构体作为形式参数传递进函数体后,采用的是一种拷贝形式,新的结构体分配的结构体与原来的结构体指向的内存区域是不一样的.
而面对C++中的一些复杂的对象的时候,作为参数进行传递的时候,同样也是.它需要将原来的对象中的具体成员拷贝一份后,然后赋值给另一个对象.而这两个对象所对应的参数与函数都是一样的,这也就是copy构造函数的来源


拷贝构造函数的定义:

拷贝构造函数具有单个的形式参数,并且其形式参数就是该类型本身的引用
如以下形式:

  class Student{
    public:
        Student();
        ~Student();
        Student(const Student &stu);//拷贝构造函数
    private:
        int age;
        int num;
  };

如上述所显示:拷贝构造函数的定义就是有一个默认形式参数是该类型引用的构造函数
当然拷贝构造函数也有其他类型的声明或者定义:

对于一个类Stu, 如果一个构造函数的第一个参数是下列之一:
1:) Stu&
2:) const Stu&
3:) volatile Stu&
4:) const volatile Stu&
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数.
如:
    Stu::Stu(const Stu &stu);
    Stu::Stu(const Stu &stu,int age = 0);

一个类是可以出现多个拷贝构造函数的,就类似与函数重载一样


显示的调用拷贝构造函数:

/*
 * ===========================================================================
 *
 *       Filename:  copyTest1.cpp
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年06月06日 22时05分30秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<memory.h>
using namespace::std;

class Student{
  private:
     //char m_name[40];
     string m_name;
     int m_age;
  public:
      /*采用char[]类型变量
        Student(const char *name = "NA",int age):m_age(age){
            memset(m_name,0,sizeof(m_name));
            strncpy(m_name,name,strlen(name));
        }
      */
      //构造函数
     Student(string name = "zhangsan",int age = 15){
        m_name = name;
        m_age = age;
        cout<<"Student contrutor" <<endl;
     }

     //析构函数
     ~Student(){
       printf("this address:%p\n",this);
       cout<<"Student descontrutor"<<endl;
     }
     //拷贝构造函数
     Student(const Student &stu){
       cout<<"call copy construcor"<<endl;
     }
};




int main(int argc,char* argv[]){
  //一般的赋值拷贝
  Student stu_zhangsan;
  printf("stu_zhangsan address:%p\n",&stu_zhangsan);
  Student stu_copy = stu_zhangsan;
  printf("stu_copy address:%p\n",&stu_copy);

  return 0;
}

结果是:

Student contrutor
stu_zhangsan address:0x7ffd2ab8bb70
call copy construcor
stu_copy address:0x7ffd2ab8bb80
this address:0x7ffd2ab8bb80
Student descontrutor
this address:0x7ffd2ab8bb70
Student descontrutor

从上面的结果中可以看到stu_zhangsan 与stu_copy的地址不是一样的,而在进行赋值的时候,首先调用的是拷贝构造函数,将原来对象中的数值拷贝到stu_copy对象中去,在析构的时候也是一样,先去析构拷贝出来的对象.然后再去析构自身,这种赋值类型的,是最典型的显示的去调用拷贝构造函数的过程,它会调用原来对象的副本(由于编译器进行的优化,所以在拷贝过程中只去做了一个构造,但是却做了两次析构的过程)


隐式的去调用拷贝构造函数:

隐式的去调用拷贝构造函数,主要是两种,一种是作为函数的形式参数传递进去
如:

    void print_student_info(Student student){
        //执行自己的相关代码
    }

还有一种就是作为参数返回出来:
如:

    Student getStudent(){
        Student stu("zhangsan",20);
        return stu;
    }

在这两种情况之下才会调用拷贝构造函数

首先来构造我们的类:

/*
 * ===========================================================================
 *
 *       Filename:  student.h
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年06月06日 23时08分47秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#ifndef __STUDENT_H__
#define __STDUENT_H__


#ifdef _cpluscplus
extern "C"{
#endif

class Student{
  private:
    char m_name[40];
    int m_sid;
    int m_age;
    int m_classNum;
  public:
    Student(const char* name,int sid,int age,int classnum);


    ~Student();
    Student(const Student &stu);
    int get_sid();
    int get_age();
    int get_class_num();
    void set_sid(int sid);
    void set_age(int age);
    void set_class_num(int classnum);
    void printall();
};

#ifdef _cpluscplus
}
#endif


#endif

类中函数的实现:

/*
 * ===========================================================================
 *
 *       Filename:  student.cpp
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年06月06日 23时14分27秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
#include<student.h>
#include<string.h>

using namespace::std;

Student::Student(const char* name = "DEFAULT",int sid = 0,int age = 0,int classnum = 0):m_sid(sid),m_age(age),m_classNum(classnum)
{
 memset(m_name,0,40);
 strncpy(m_name,name,strlen(name));
 cout<<"student constructor" << endl;

}

Student::~Student(){
  cout<<"student destructor"<<endl;
}


Student::Student(const Student & stu){
  cout <<"student copy constructor"<<endl;
  strcpy(m_name,"copy");
  strcat(m_name,stu.m_name);
  m_sid = stu.m_sid;
  m_age = stu.m_age;
  m_classNum = stu.m_classNum;
}

int Student::get_sid(){
  return m_sid;
}

int Student::get_age(){
  return m_age;
}

int Student::get_class_num(){
  return m_classNum;
}

void Student::set_sid(int sid){
  this->m_sid = sid;
}

void Student::set_age(int age){
  this->m_age = age;
}

void Student::set_class_num(int class_num){
  this->m_classNum = class_num;
}

void Student::printall(){
  cout<<"student name:"<<this->m_name<<endl;
  cout<<"student:id:"<<this->m_sid<<endl;
  cout<<"student age:"<<this->m_age<<endl;
  cout<<"student classNum:"<<this->m_classNum<<endl;
}

作为形式参数去调用拷贝构造函数:

在上面已经定义好了后续作为使用的实体类,然后将其对象作为形式参数传递到函数中去:

/*
 * ===========================================================================
 *
 *       Filename:  studentTest.cpp
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年06月08日 21时39分55秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<iostream>
using namespace::std;
#include<student.h>

/* **
 *将对象作为形式参数传递进去(非指针形式与引用形式)
 * */
void printf_student_info(Student stu){
  stu.printall();
}

void printf_student_info_reference(Student &stu){
  stu.printall();
}

int main(int agrc,char *argv[]){
   Student stu("zhangsan",1,15,3);
   stu.printall();
   cout<<"-----------------"<<endl;
   printf_student_info(stu);

   cout<<"-----------------"<<endl;
   printf_student_info_reference(stu);
  return 0;
}

此时的输出结果是:

student constructor
student name:zhangsan
student:id:1
student age:15
student classNum:3
-----------------
student copy constructor
student name:copyzhangsan
student:id:1
student age:15
student classNum:3
student destructor
-----------------
student name:zhangsan
student:id:1
student age:15
student classNum:3
student destructor

从上面的结果中可以看到:
如果函数参数为Student stu即非引用或者指针形式进行传参的时候,首先会调用拷贝构造函数
而如果采用引用的形式去进行传参的时候,这个时候是不去调用拷贝构造函数的


作为函数返回值去调用拷贝构造函数:

继续通过上面的Student实体类来看看:如果作为函数返回值的时候,是否会调用拷贝构造函数

/*
 * ===========================================================================
 *
 *       Filename:  studentTest2.cpp
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年06月08日 22时09分45秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<iostream>
using namespace::std;
#include<string.h>
#include<student.h>


Student getStudent(){
  Student stu("lisi",10,17,2);
  return stu;
}


int main(int argc,char *agrv[]){  
  Student stu2 = getStudent();
  cout<<"--------------"<<endl;
  stu2.printall();
  cout<<"========="<<endl;

  //通过直接调用拷贝构造函数的形式
  Student stu(stu2);
  stu.printall();

  return 0;
}

输出的结果

 student constructor
--------------
student name:lisi
student:id:10
student age:17
student classNum:2
=========
student copy constructor
student name:copylisi
student:id:10
student age:17
student classNum:2
student destructor
student destructor

从上面的结果中可能会有点疑问,就是明明说在作为返回值返回的时候,是需要调用拷贝构造函数的,但是事实上并没有调用.这个就是由于有些编译器在编译的时候,做了很大的优化.我目前的编译器在编译的时候,这个时候就优化了,它不会去调用拷贝构造函数.当然有时候我们也是可以去直接调用拷贝构造函数的,这就跟一般的构造类似的形式


拷贝构造函数的类型

编译器会提供提供默认的构造函数的行为:
这个行为会执行逐个成员初始化,讲新对象初始化为原来对象的副本
逐个成员:指的是编译器将现有对象的每个非static成员,依次复制到正在创建的对象中

浅拷贝

浅拷贝??什么是浅拷贝??
浅拷贝是只是将对象的赋值给了另外一个副本对象,但是他们指向的资源都是同一个资源
如图所示:
这里写图片描述
从上面可以看出:副本与原对象指向的是同一块内存资源.这个时候就叫做浅拷贝;
但是在p1进行析构的时候,这个时候其资源也会被析构,但是p2如果这个时候还在使用状态,就有可能出现一些问题;

/*
 * ===========================================================================
 *
 *       Filename:  shadowcopy.cpp
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年06月11日 11时06分27秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<iostream>
using namespace::std;
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

class Student{
  private:
    char *m_name;
    int m_sid;
    int m_age;
  public:
    Student(const char *name = "NULL",int sid = 0,int age = 0):m_sid(sid),m_age(age){
      cout<<"student constructor"<<endl;
       m_name = (char *)malloc(sizeof(char)*strlen(name));      
       strncpy(m_name,name,sizeof(char)*strlen(name));
       printf("m_name address:%p\n",m_name);
    }

    ~Student(){
       cout<<"student destructor"<<endl;
       /*if(m_name != NULL){
          free(m_name);
          m_name = NULL;
       }*/
    }


    void printf_info(){
      printf("m_name: %s and address:%p\n",m_name,m_name);
      printf("m_age:%d and address:%p\n",m_age,&m_age);
      printf("m_sid:%d and address:%p\n",m_sid,&m_sid);
    }


};



int main(int argc,char *agrv[]){
   Student stu("zhangsan",1,15);
   stu.printf_info();
   printf("-------------------\n");
   Student copy_stu = stu;
   copy_stu.printf_info();


  return 0;
}

输出结果为:

 student constructor
m_name address:0x19d7010
m_name: zhangsan and address:0x19d7010
m_age:15 and address:0x7ffdf942bbfc
m_sid:1 and address:0x7ffdf942bbf8
-------------------
m_name: zhangsan and address:0x19d7010
m_age:15 and address:0x7ffdf942bc0c
m_sid:1 and address:0x7ffdf942bc08
student destructor
student destructor

从上面的输出结果可以看到,学生名字的指针变量中存储的地址都是一模一样的.如果其中有一个对地址进行过修改的话.那么这个时候另外一个也就随之会改变.这样会使安全行降低很多.在这里还有一点需要注意的,就是free函数的使用.如果在析构函数中去调用free的话,这个时候会出现两次free的错误

深拷贝

什么是深拷贝?由浅拷贝相关的概念我们可以大概猜到.深拷贝大概的意思就是不仅仅只是创建一个副本.在创建副本的 时候也会创建副本资源.将堆中对应的资源同样会拷贝一份

如下图所示:这里写图片描述


/*
 * ===========================================================================
 *
 *       Filename:  deepcopy.cpp
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年06月11日 11时06分27秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<iostream>
using namespace::std;
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

class Student{
  private:
    char *m_name;
    int m_sid;
    int m_age;
  public:
    Student(const char *name = "NULL",int sid = 0,int age = 0):m_sid(sid),m_age(age){
      cout<<"student constructor"<<endl;
       m_name = (char *)malloc(sizeof(char)*strlen(name));      
       strncpy(m_name,name,sizeof(char)*strlen(name));
       printf("m_name address:%p\n",m_name);
    }

    ~Student(){
       cout<<"student destructor"<<endl;
       if(m_name != NULL){
          free(m_name);
          m_name = NULL;
       }
    }

    Student(const Student &stu){
      m_name = (char*)malloc(sizeof(char)*strlen(stu.m_name));
      m_sid = stu.m_sid;
      m_age = stu.m_age;
    }

    void printf_info(){
      printf("m_name: %s and address:%p\n",m_name,m_name);
      printf("m_age:%d and address:%p\n",m_age,&m_age);
      printf("m_sid:%d and address:%p\n",m_sid,&m_sid);
    }


};



int main(int argc,char *agrv[]){
   Student stu("zhangsan",1,15);
   stu.printf_info();
   printf("-------------------\n");
   Student copy_stu = stu;
   copy_stu.printf_info();


  return 0;
}

这份代码的输出结果为:

 student constructor
m_name address:0xf85010
m_name: zhangsan and address:0xf85010
m_age:15 and address:0x7ffd93f37dbc
m_sid:1 and address:0x7ffd93f37db8
-------------------
m_name:  and address:0xf85030
m_age:15 and address:0x7ffd93f37dcc
m_sid:1 and address:0x7ffd93f37dc8
student destructor
student destructor

从上面的代码中可以看到,在拷贝构造函数中,同时也去开辟了一块堆内存,所以在调用拷贝构造函数的时候,也同时会去开辟一块内存区域,这个时候副本对象与原来的对象之间的以来关系就会降低很多.

注意:
如果不主动去定义构造函数的时候,这个时候,系统会为类提供一个默认的构造函数
默认的拷贝构造函数是浅拷贝类型的,但是由于浅拷贝是有一定局限性的
为了防止默认拷贝构造函数的发生,可以通过提供一个私有的拷贝构造函数,系统会在直接编译的时候直接就会报出一个错误,这样就防止了默认拷贝构造函数的发生

     如:private:
         Student(const Student &stu){}

浅谈静态变量在拷贝构造函数中发生的情况

在之前我们已经提过static成员变量,它是属于类的,并不是属于对象的,而在拷贝构造函数中,它拷贝的是对象的具体的值,而静态成员变量在拷贝函数调用的时候,会出现什么情况 ???
在原来浅拷贝的基础上面加以修改:

/*
 * ===========================================================================
 *
 *       Filename:  staticcopy.cpp
 *    Description:  
 *        Version:  1.0
 *        Created:  2017年06月11日 11时06分27秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:   (), 
 *        Company:  
 *
 * ===========================================================================
 */

#include<iostream>
using namespace::std;
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

class Student{
  private:
    char *m_name;
    int m_sid;
    int m_age;
    //声明一个静态变量
    static int count;

  public:
    Student(const char *name = "NULL",int sid = 0,int age = 0):m_sid(sid),m_age(age){
      cout<<"student constructor"<<endl;
       m_name = (char *)malloc(sizeof(char)*strlen(name));      
       strncpy(m_name,name,sizeof(char)*strlen(name));
       printf("m_name address:%p\n",m_name);
       count++;
    }

    ~Student(){
       cout<<"student destructor"<<endl;
       /*if(m_name != NULL){
          free(m_name);
          m_name = NULL;
       }*/
       count --;
    }

    static int getcount(){
      return count;
    }

    void printf_info(){
      printf("m_name: %s and address:%p\n",m_name,m_name);
      printf("m_age:%d and address:%p\n",m_age,&m_age);
      printf("m_sid:%d and address:%p\n",m_sid,&m_sid);
      printf("count:%d\n",count);
    }

};

int Student::count = 0; 

int main(int argc,char *agrv[]){
  {
   Student stu("zhangsan",1,15);
   stu.printf_info();
   printf("-------------------\n");
   Student copy_stu = stu;
   copy_stu.printf_info();
  }   
   printf("==================");
   Student stu("lisi",2,20);
    stu.printf_info();
  return 0;
}

得到的结果为:

 student constructor
m_name address:0x1c64010
m_name: zhangsan and address:0x1c64010
m_age:15 and address:0x7ffe7ee9ec3c
m_sid:1 and address:0x7ffe7ee9ec38
count:1
-------------------
m_name: zhangsan and address:0x1c64010
m_age:15 and address:0x7ffe7ee9ec4c
m_sid:1 and address:0x7ffe7ee9ec48
count:1
student destructor
student destructor
==================student constructor
m_name address:0x1c64030
m_name: lisi and address:0x1c64030
m_age:20 and address:0x7ffe7ee9ec4c
m_sid:2 and address:0x7ffe7ee9ec48
count:0
student destructor

从上面可以看到的是,在拷贝构造函数的时候,并没有对静态成员变量进行操作.因为往往在不经意的时候会出现调用这种系统默认拷贝构造函数的时候,这个时候如果不去做特殊处理的话,这种拷贝构造函数是不具有处理静态成员变量的能力的,所以也就出现了上述的,count = 0的现象;

解决方法:
在拷贝构造函数中去count的再去做++操作,

以上就是关于拷贝构造函数相关的一些部分,欢迎斧正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值