C++第十一天

1.拷贝构造函数基础

1.1.构造和析构函数调用次数

#include <iostream>
using namespace std;
 
class A{
    int data;
public:
    A(int d=0):data(d){
        cout<<"A(int=0)"<<endl;
    }
    void show(){
        cout<<"data="<<data<<endl;            
    }
    ~A(){
        cout<<"~A()"<<endl;
    }
    //声明友员,表示对其授权,允许其访问A类中的所有成员。友员不是A的成员函数
    friend A add( A a1, A a2);
};

A add( A a1, A a2){
	//访问A中的成员,访问方式:“对象.成员”
	int sum =a1.data+a2.data;
	return A(sum);
}
 
int main()
{
    A obj1(40);
    A obj2(50);
    add(obj1,obj2).show();
}

执行结果:

root@host:/home/LinuxShare/004.c++/day11# g++ 01.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
A(int=0)
A(int=0)
A(int=0)
data=90
~A()
~A()
~A()
~A()
~A()

结果分析:
从输出信息分析,析构调用5次属于正常,但为何只调用3此构造函数呢???
实际编译过程:创建obj1对象时,传递的是int型数据,会调用构造函数A(int d=0);同理创建obj2时,传递的也是int型数据,同样调用构造函数A(int d=0);add函数的return返回类型是A(sum),也是调用构造函数A(int d=0)。但add函数,是使用obj1初始化a1,即a1(obj1),并不会调用构造函数A(int d=0),因为传递参数的类型不是int型,理论上应该调用A(A)形式的构造函数,但我们在代码中没有写,所以看不到输出信息。实际运行中,系统会自动产生一个构造函数A(const A& obj),

1.2.新增拷贝构造函数测试构造函数调用次数

#include <iostream>
using namespace std;
 
class A{
    int data;
public:
    A(int d=0):data(d){
        cout<<"A(int=0)"<<endl;
    }
    void show(){
        cout<<"data="<<data<<endl;            
    }
    ~A(){
        cout<<"~A()"<<endl;
    }
	//新增构造函数
	A(const A& obj){
        cout<<"A(const A&)"<<endl;
    }
    friend A add( A a1, A a2);
};

A add( A a1, A a2){
	int sum =a1.data+a2.data;
	return A(sum);
}
 
int main()
{
    A obj1(40);
    A obj2(50);
    add(obj1,obj2).show();
}

执行结果:

root@host:/home/LinuxShare/004.c++/day11# g++ 02.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
A(int=0)
A(int=0)
A(const A&)
A(const A&)
A(int=0)
data=1028279616
~A()
~A()
~A()
~A()
~A()

2.拷贝构造函数

A(const A& obj)称作拷贝构造函数。除有个名字叫拷贝构造函数外,与普通构造函数相比毫无特殊之处,所有构造函数的调用均由传递的参数类型决定。
在类中没写构造函数时,系统自动产生两个构造函数,一个是无参构造函数,一个是拷贝构造函数。所以在没写上述两种构造函数时,可以被调用。
默认的拷贝构造函数,把旧对象中的内容逐个字节复制到新对象中。

2.1.传递的参数类型决定调用哪个构造函数例1

#include <iostream>
using namespace std;
 
class A{
    int data;
public:
    //添加不同类型的构造函数
    A():data(100){
        cout<<"A()"<<endl;
    }
    A(int d):data(d){
        cout<<"A(int)"<<endl;
    }
    A(bool b):data(b?123:456){
        cout<<"A(bool)"<<endl;
    }
    A(char c):data((int)c){
        cout<<"A(char)"<<endl;
    }
    //添加拷贝构造函数
    A(const A& o):data(o.data){
        cout<<"A(const A&)"<<endl;
    }
    //添加析构函数
    virtual ~A(){
        cout<<"~A()"<<endl;
    }
    void show(){
        cout<<"data="<<data<<endl;
    }
};
 
 
int main()
{
    A a1;
    A a2(200);
    A a3(false);
    A a4('V');
    A a5(a2);
}

执行结果:

root@host:/home/LinuxShare/004.c++/day11# g++ 03.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
A()
A(int)
A(bool)
A(char)
A(const A&)
~A()
~A()
~A()
~A()
~A()

结果分析:所有构造函数的调用均由传递的参数类型决定,拷贝构造函数也不例外。

2.2.传递的参数类型决定调用哪个构造函数例2

#include <iostream>
using namespace std;
 
class A{
    int data;
public:
    //添加不同类型的构造函数
    A():data(100){
        cout<<"A()"<<"---"<<this<<endl;
    }
    A(int d):data(d){
        cout<<"A(int)"<<"---"<<this<<endl;
    }
    A(bool b):data(b?123:456){
        cout<<"A(bool)"<<"---"<<this<<endl;
    }
    A(char c):data((int)c){
        cout<<"A(char)"<<"---"<<this<<endl;
    }
    //添加拷贝构造函数
    A(const A& o):data(o.data){
        cout<<"A(const A&)"<<"---"<<this<<endl;
    }
    //添加析构函数
    virtual ~A(){
        cout<<"~A()"<<"---"<<this<<endl;
    }
    void show(){
        cout<<"data="<<data<<endl;
    }
};
 
void show(A obj){
    obj.show();
}
 
int main()
{
    A a1;
    show(a1);
    show(200);
}

执行结果:

root@host:/home/LinuxShare/004.c++/day11# g++ 04.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
A()---0x7ffe080b20a0
A(const A&)---0x7ffe080b20b0
data=100
~A()---0x7ffe080b20b0
A(int)---0x7ffe080b20b0
data=200
~A()---0x7ffe080b20b0
~A()---0x7ffe080b20a0

自我理解:show函数,传递参数为200时,并不会因为show(A obj)而调用拷贝构造函数,而是根据调用时传递的参数类型选择调用哪个构造函数,上例中传递int型参数200,故调用构造函数A(int d):data(d)。相当于A(200),将200强制转换成A类型,所以调用的是构造函数A(int d):data(d)。

2.3.拷贝构造函数去掉&是否可行

#include <iostream>
using namespace std;
 
class A{
    int data;
public:
    //添加不同类型的构造函数
    A():data(100){
        cout<<"A()"<<endl;
    }
    A(int d):data(d){
        cout<<"A(int)"<<endl;
    }
    A(bool b):data(b?123:456){
        cout<<"A(bool)"<<endl;
    }
    A(char c):data((int)c){
        cout<<"A(char)"<<endl;
    }
    //添加拷贝构造函数
    //A(const A& o):data(o.data){
    //修改拷贝构造函数如下
    A(A o):data(o.data){
        cout<<"A(const A&)"<<endl;
    }
    //添加析构函数
    virtual ~A(){
        cout<<"~A()"<<endl;
    }
    void show()const{
        cout<<"data="<<data<<endl;
    }
 
};
 
void show(A obj){
    obj.show();
}
void func(const A& obj){
    obj.show();
}
 
int main()
{
    A a1;
    show(a1);
}

编译报错:

root@host:/home/LinuxShare/004.c++/day11# g++ 05.cpp
05.cpp:23:10: error: invalid constructor; you probably meant ‘A (const A&)’
     A(A o):data(o.data){
          ^

结果分析:
拷贝构造函数去掉引用时,在show(a1)调用构造函数A(A o)时,会新建一个A类型的对象,而新建一个A类型的对象又会调用A(A o),从而无限循环地调用下去。
PS:形参是一个新变量,在调用时创建,由实参来初始化。

3.默认拷贝构造函数和自定义拷贝构造函数的区别

默认的拷贝构造函数,把旧对象中的内容逐个字节复制到新对象中。而自定义构造函数,旧对象的内容只是传递到了自定义拷贝构造函数的形参,而不会自动复制到新对象中,如果需要对新对象赋值,需要在初始化列表中写,或者在函数体中写语句实现。
自定义拷贝构造函数后,就不再有默认了拷贝构造函数。要在自定义的拷贝构造函数中处理所有需要处理的数据。

3.1.默认拷贝构造函数

旧对象中的内容逐个字节复制到新对象中。

#include <iostream>
using namespace std;
 
class A{
    int data;
public:
    //添加无参构造函数
    A():data(100){
        cout<<"A()"<<endl;
    }
    void show()const{
        cout<<"data="<<data<<endl;
    }
    //添加析构函数
    virtual ~A(){
        cout<<"~A()"<<endl;
    }
};
int main()
{
    A a1;
    a1.show();
    cout<<"======"<<endl;
    A a5(a1);
    a5.show();
}

执行结果:

root@host:/home/LinuxShare/004.c++/day11# g++ 06.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
A()
data=100
======
data=100
~A()
~A()

3.2.自定义拷贝构造函数

#include <iostream>
using namespace std; 
class A
{   
  int data;
  public:    //添加无参构造函数    
  A():data(100)
  {        
    cout<<"A()"<<endl;    
  }    
  //添加自定义拷贝构造函数    
  A(const A& o)
  {        
    cout<<"A(const A&)"<<endl;    
  }    
  void show()const
  {        
    cout<<"data="<<data<<endl;    
  }    
  //添加析构函数    
  virtual ~A()
  {        
    cout<<"~A()"<<endl;    
  }
};

  int main()
  {    
    A a1;    a1.show();    
    cout<<"======"<<endl;    
    A a5(a1);    
    a5.show();
  }

执行结果:

A()
data=100
======
A(const A&)
data=-1217415840
~A()
~A()

结果分析:
a1中的data并没有自动赋值给a5中的data。

4.类成员有指针,调用拷贝构造函数的情形

4.1.基础代码,不调用拷贝构造函数

#include <iostream>
using namespace std;
 
class Array
{
    char *p;//数组指针
    int len;//数组长度
public:
    //构造函数
    Array(int n):len(n),p(new char[n]){}
 
    //获取数组长度
    int size()
    {
        return len;    
    }
    //填充数组内容
    void fill(char start,int skip)
    {
            for(int i=0;i<len;i++)
            {
                p[i]=start+i*skip;  
            }
    }
    //输出数组内容
    void show()
    {
        for(int i=0;i<len;i++)  
        {
            cout<<p[i];   
        }
        cout<<endl;
    }
    //析构函数
    ~Array()
    {
            delete[] p;
    }
};
int main()
{
    Array a1(10);
    a1.fill('a',2);
    a1.show();
}

执行结果:

root@host:/home/LinuxShare/004.c++/day11# g++ 08.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
acegikmoqs

4.2.增加新对象,并用旧对象对其初始化

#include <iostream>
using namespace std;
 
class Array
{
    char *p;//数组指针
    int len;//数组长度
public:
    //构造函数
    Array(int n):len(n),p(new char[n]){}
 
    //获取数组长度
    int size()
    {
        return len;    
    }
    //填充数组内容
    void fill(char start,int skip)
    {
            for(int i=0;i<len;i++)
            {
                p[i]=start+i*skip;  
            }
    }
    //输出数组内容
    void show()
    {
        for(int i=0;i<len;i++)  
        {
            cout<<p[i];   
        }
        cout<<endl;
    }
    //析构函数
    ~Array()
    {
            delete[] p;
    }
};
int main()
{
    Array a1(10);
    a1.fill('a',2);
    a1.show();
    //新增对象a2
    Array a2(a1);
    a2.fill('A',2);
    a2.show();
}

执行结果:

 ./a.out 
acegikmoqs
ACEGIKMOQS
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x09a97008 ***
======= Backtrace: =========
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x6ff41)[0xb74cff41]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x717a8)[0xb74d17a8]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(cfree+0x6d)[0xb74d48ed]
/usr/lib/i386-linux-gnu/libstdc++.so.6(_ZdlPv+0x1f)[0xb76594bf]
./a.out[0x80488e2]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb7476e46]
./a.out[0x8048781]
======= Memory map: ========
08048000-08049000 r-xp 00000000 00:12 496        /home/gongxiang/c++day21/day11/a.out
08049000-0804a000 rw-p 00000000 00:12 496        /home/gongxiang/c++day21/day11/a.out
09a97000-09ab8000 rw-p 00000000 00:00 0          [heap]
b7300000-b7321000 rw-p 00000000 00:00 0 
b7321000-b7400000 ---p 00000000 00:00 0 
b745e000-b7460000 rw-p 00000000 00:00 0 
b7460000-b75c1000 r-xp 00000000 08:01 927283     /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b75c1000-b75c2000 ---p 00161000 08:01 927283     /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b75c2000-b75c4000 r--p 00161000 08:01 927283     /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b75c4000-b75c5000 rw-p 00163000 08:01 927283     /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b75c5000-b75c8000 rw-p 00000000 00:00 0 
b75c8000-b75e4000 r-xp 00000000 08:01 894084     /lib/i386-linux-gnu/libgcc_s.so.1
b75e4000-b75e5000 rw-p 0001b000 08:01 894084     /lib/i386-linux-gnu/libgcc_s.so.1
b75e5000-b7609000 r-xp 00000000 08:01 927454     /lib/i386-linux-gnu/i686/cmov/libm-2.13.so
b7609000-b760a000 r--p 00023000 08:01 927454     /lib/i386-linux-gnu/i686/cmov/libm-2.13.so
b760a000-b760b000 rw-p 00024000 08:01 927454     /lib/i386-linux-gnu/i686/cmov/libm-2.13.so
b760b000-b760c000 rw-p 00000000 00:00 0 
b760c000-b76ec000 r-xp 00000000 08:01 887484     /usr/lib/i386-linux-gnu/libstdc++.so.6.0.17
b76ec000-b76f0000 r--p 000e0000 08:01 887484     /usr/lib/i386-linux-gnu/libstdc++.so.6.0.17
b76f0000-b76f1000 rw-p 000e4000 08:01 887484     /usr/lib/i386-linux-gnu/libstdc++.so.6.0.17
b76f1000-b76f8000 rw-p 00000000 00:00 0 
b76fd000-b7700000 rw-p 00000000 00:00 0 
b7700000-b7701000 r-xp 00000000 00:00 0          [vdso]
b7701000-b771d000 r-xp 00000000 08:01 894040     /lib/i386-linux-gnu/ld-2.13.so
b771d000-b771e000 r--p 0001b000 08:01 894040     /lib/i386-linux-gnu/ld-2.13.so
b771e000-b771f000 rw-p 0001c000 08:01 894040     /lib/i386-linux-gnu/ld-2.13.so
bfa7d000-bfa9e000 rw-p 00000000 00:00 0          [stack]
已放弃 (core dumped)

结果分析:
现象解释:由于默认的拷贝构造函数将旧对象的指针赋值给新对象指针,旧对象调用析构函数时,将指针p指向的空间释放,新对象调用析构函数时,要释放同样的地址空间,报错。

4.3.自定义拷贝构造函数,防止重复是否地址空间

#include <iostream>
using namespace std;
 
class Array
{
    char *p;//数组指针
    int len;//数组长度
public:
    //构造函数
    Array(int n):len(n),p(new char[n]){}
    //自定义拷贝构造函数
    Array(const Array& o):len(o.len)
    {
        p = new char[len];
        for(int i=0;i<len;i++)
        {
            p[i]=o.p[i];    
        }   
    }
    //获取数组长度
    int size()
    {
        return len;    
    }
    //填充数组内容
    void fill(char start,int skip)
    {
            for(int i=0;i<len;i++)
            {
                p[i]=start+i*skip;  
            }
    }
    //输出数组内容
    void show()
    {
        for(int i=0;i<len;i++)  
        {
            cout<<p[i];   
        }
        cout<<endl;
    }
    //析构函数
    ~Array()
    {
        cout<<"~A()-1"<<endl;
        delete[] p;
        cout<<"~A()-2"<<endl;
             
    }
};
int main()
{
    Array a1(10);
    a1.fill('a',2);
    a1.show();
    //新增对象a2
    Array a2(a1);
    a2.fill('A',2);
    a2.show();
    a1.show();
}

执行结果:

root@host:/home/LinuxShare/004.c++/day11# g++ 10.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
acegikmoqs
ACEGIKMOQS
acegikmoqs
~A()-1
~A()-2
~A()-1
~A()-2

结果解释:
旧对象没有被改变,并且新对象调用析构函数正常释放空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值