C++对象的赋值与复制复制构造函数(指针数据成员)
一、对象的赋值
同类对象之间可以相互赋值,对象赋值的一般形式:
对象名2 = 对象名1;
原理是,赋值运算符的重载。仅赋值,因此赋值前,需要先定义并初始化对象2。
对象的赋值针对指对象中所有数据成员的值;
对象的赋值只对其中的数据成员赋值,不涉及成员函数。
#include <iostream>
#include <sstream>
using namespace std;class Student
{
public:Student(){num = 0;}Student(int n, string na, char s){num = n;name = na;sex = s;}void showinf( ){cout<<"num:"<<num<<"\tname:"<<name<<"\tsex:"<<sex<<endl;}
private:int num;string name;char sex;
};int main()
{Student stud1(101,"LL",'m');//定义并初始化stud1,调用Student(int n, string na, char s);Student stud2; //定义并初始化stud2,调用Student();stud1.showinf();stud2.showinf();stud2=stud1; //赋值运算符重载,仅赋值操作,因此之前应先定义并初始化stud2stud2.showinf();return 0;
}
二、对象的复制
用已有的对象克隆出一个新对象,对象复制的一般形式为:
类名 对象2(对象1);
原理:调用了编译系统提供的默认复制构造函数。
Student∷Student(const Student& obj) //默认构造函数
{num=obj.num;name=obj.name;sex=obj.sex;
}
既定义又复制,定义的同时执行复制对象操作!不需要提前定义和初始化对象2
等价形式:类名 对象2=对象1;(注:注意区分对象赋值操作)
#include <iostream>
#include <sstream>
using namespace std;class Student
{
public:Student(int n, string na, char s){num = n;name = na;sex = s;}void showinf( ){cout<<"num:"<<num<<"\tname:"<<name<<"\tsex:"<<sex<<endl;}
private:int num;string name;char sex;
};int main()
{Student stud1(101,"LL",'m');Student stud2(stud1); //等价于 Student stud2 =stud1; 既是定义,又是复制 == 定义&复制stud2.showinf();return 0;
}
三、复制构造函数
1、什么时候需要通过复制构造函数对对象进行复制?
(1)新建立一个对象时:直接利用复制构造函数进行定义和初始化Box box2(box1);
(2)当函数的参数为类的对象:调用函数时,将实参对象完整地传递给形参,通过调用复制构造函数来建立一个实参的拷贝;
void fun(Box b){ }int main( )
{Box box1(12,15,18);fun(box1);return 0;
}
(3)函数的返回值是类的对象:在函数调用完毕,将函数中的对象复制一个临时对象并传给该函数的调用处。
Box fun( )
{Box box1(12,15,18);return box1;
}
int main( )
{Box box2;box2=fun( );
}
2、复制构造函数的形式
复制构造函数的定义形式如下:
类名(const 类名&对象名)
Time(const Time & object);
3、浅复制
浅复制是指,使用默的复制方式{类名 对象2(对象1);或 类名 对象2 = 对象1}方式,复制数据成员的方式(此种方式无法传递指针数据成员)。
例1 当数据成员不包含指针时,此种复制方式不会出现问题。
#include <iostream>
using namespace std;
class Test
{
private:int x;
public:Test(int n) {x=n; }Test(const Test& obj){x=obj.x; }//复制构造函数void show (){cout<<x<<endl;}
};int main()
{Test test1(100);Test test2(test1); //对象复制形式一,调用复制构造函数Test(const Test& obj)Test test3=test1; //对象复制形式二,调用复制构造函数Test(const Test& obj)test2.show();test3.show();return 0;
}
例2 当数据成员包含指针时,此复制方式会由于指针的传递出现异常
#include <iostream>
using namespace std;
class Test
{
private:int x;char *pcr; //定义字符指针,可指向字符串或字符数组
public:Test(int n, char* c) //初始化构造函数!!!{x=n;strcpy(pcr, c); //错误点:这是一个野指针!!!!(但不是根源,根源是复制的方式不对)}Test(const Test& obj) //复制构造函数!!!{x=obj.x;strcpy(pcr, obj.pcr);}void show (){cout<<"x:"<<x<<"\t pcr:"<<pcr<<endl;}
};int main()
{Test test1(100,'L'); //初始化对象test1Test test2(test1); //对象复制形式一,调用复制构造函数Test(const Test& obj)Test test3=test1; //对象复制形式二,调用复制构造函数Test(const Test& obj)test2.show();test3.show();return 0;
}
编译会报错。
4、深复制
深复制的目的:先给包含指针的数据成员分配空间,然后再去复制(避免了指针数据成员存在野指针情况)。
例3 在复制构造函数中预先为指针数据成员分配空间(在析构函数中释放空间)
#include <iostream>
using namespace std;
class Test
{
private:int x;char *pcr; //定义字符指针,可指向字符串或字符数组
public:Test(int n, char* c) //初始化构造函数{x=n;int m = strlen(c)+1; //C语言中的字符串结尾\0,因此长度+1pcr = new char[m]; //为指针成员分配空间,避免野指针strcpy(pcr, c); }Test(const Test& obj) //复制构造函数{x=obj.x;int m = strlen(obj.pcr)+1;pcr = new char[m]; //为指针成员分配空间,避免野指针strcpy(pcr, obj.pcr);}~Test(){delete pcr;} //由于构造函数中分配了空间,因此必须在析构函数中释放void show (){cout<<"x:"<<x<<"\t pcr:"<<pcr<<endl;}
};int main()
{Test test1(100,"Alibaba"); //初始化对象test1Test test2(test1); //对象复制形式一,调用复制构造函数Test(const Test& obj)Test test3=test1; //对象复制形式二,调用复制构造函数Test(const Test& obj)test2.show();test3.show();return 0;
}
例5 在复制构造函数中对指针数据成员直接传递【非常危险】
#include <iostream>
using namespace std;
class Test
{
private:int x;char *pcr; //定义字符指针,可指向字符串或字符数组
public:Test(int n, char* c) //初始化构造函数{x=n;pcr = c; //!直接传递指针地址(貌似对,一定有机会错!)}Test(const Test& obj) //复制构造函数{x=obj.x;pcr = obj.pcr;}void show (){cout<<"x:"<<x<<"\t pcr:"<<pcr<<endl;}
};int main()
{Test *pt1;pt1 = new Test(100,"GW");Test test2(*pt1);Test test3 = (*pt1);test2.show();test3.show();delete pt1;test2.show();test3.show();return 0;
}
注意:虽然通过传递指针的方式,实现了对象的复制。但是当Test *pt1被释放掉时,由pt1复制产生的test2和test3的数据成员中的指针数据成员,所指向的内存空间可能不变,也可能改变。
上图程序运行的结果是:释放后此段内存空间没变,但实际程序运行中则不一定。
所以,直接传递指针的方式非常危险!