类的初始化
运行以下代码
class Class
{
private:
int a;
public:
Class()
{
a=0;
cout<<"call constructor "<<endl;
};
~Class()
{
cout<<"call destructor "<<endl;
}
};
void donothing(Class b)
{
;
}
int main()
{
Class A;
Class B=A;
donothing(A);
}
结果
说明析构函数调用3次,而我们编写的构造函数只执行了1次,析构函数的格式是固定的(没有参数),调用也是固定的(类在生存期结束,释放内存前调用)所以问题不在析构函数上。
问题出在构造函数上,donothing函数为了拿到参数的值新建了一个形参Class b执行Class=输入的对象 ,也就是说Class B=A;和donothing中的Class b=A这两个类的初始化赋值时没有执行我们定义的构造函数。事实上类的初始化赋值是进入到了由编译器隐式生成的复制构造函数上,由于是隐式的所以表面上没有任何warning。
复制构造函数格式如下
classname(const & object)
那么试试看在里面添加我们的复制构造函数
class Class
{
private:
int a;
public:
Class()
{
a=0;
cout<<"call constructor "<<endl;
};
Class(const Class &a)//添加自定义的复制构造函数
{
cout<<"call copy constructor"<<endl;
}
~Class()
{
cout<<"call destructor "<<endl;
}
};
void donothing(Class b)
{
;
}
int main()
{
Class A;
Class B=A;
donothing(A);
}
运行
类的赋值符重载
学c的时候就有过这个困惑,为什么数组不能相互赋值而结构体可以相互赋值,甚至可以通过用结构体把数组包进去来达到数组相互赋值的目的
struct T
{
int arr[3];
};
int main()
{
cout<<"a=";
T a={.arr={1,2,3}};
for(int i=0;i<3;i++)
cout<<a.arr[i];
cout<<endl;
cout<<"b=";
T b=a;
for(int i=0;i<3;i++)
cout<<b.arr[i];
cout<<endl;
cout<<"change a"<<endl<<"a=";
a.arr[0]=3,a.arr[1]=2,a.arr[2]=1;
for(int i=0;i<3;i++)
cout<<a.arr[i];
cout<<endl;
cout<<"b=";
for(int i=0;i<3;i++)
cout<<b.arr[i];
cout<<endl;
}
新建结构体a,通过赋值赋值给结构体b,再改变a的值,然后打印,嫌代码多可以直接看执行结果
在c++中,类和构造体可以相互赋值其实是编译器隐式的生成了一个赋值符=的重载函数,原型如下
classname &classname::operator=(const classname &object)
试着编写一下显示的赋值符重载函数
class T
{
private:
int a;
public:
T()
{
a=1;
}
T &operator=(const T& para);
};
T &T::operator=(const T¶)//赋值符号重载函数
{
a=para.a+1;
cout<<"= overload "<<a<<endl;
return *this;
}
int main()
{
T a;
T b;
b=a;
return 0;
}
运行结果
为什么这里设置了返回值引用呢?
其实是为了适配连等
代码中A=B=C=D这样的连等在返回引用的机制下也能保证正常执行
编译器自动生成的赋值符重载函数是按变量成员一个一个赋值过去(忽略static变量,因为static变量事实上是在类外定义的)这种弱复制在大部分情况下都是没问题的,但是需要注意一个情况:包含动态内存操作的类
试想一下,一个类里面有一个指针,在操作时使用动态内存,new内存到指针,但是一旦进行了赋值操作,那么这个指针成员的值,也就是new返回的地址就会被附在另一个指针上
class T
{
private:
int *p;
public:
T(int len)
{
p=new int[len];
}
int *show_add()
{
return p;
}
};
int main()
{
T a(5);
T b(5);
cout<<"pa="<<a.show_add()<<endl;
cout<<"pb="<<b.show_add()<<endl;
cout<<"a=b"<<endl;
a=b;
cout<<"pa="<<a.show_add()<<endl;
cout<<"pb="<<b.show_add()<<endl;
return 0;
}
执行结果
我们的本意应该是将a申请内存里的数据放到b申请的内存里,但是默认生成的赋值重载函数只是将指针赋予了过去,这不是我们希望的,将会造成两个严重后果
1.pa和pb变成共用一段内存,相互影响,且如果在delete了其中一个后再delete另一个的时候可能会出现程序异常(因为第一次delete已经释放过了)
2.pa申请的内存丢失,无法再delete,内存泄露
解决方法就是重构赋值符,改进一下
class T
{
private:
int *p;
int size;
public:
T(int len)
{
p=new int[len];
for(int i=0;i<len;i++)
p[i]=0;
size=len;
}
~T()
{
delete[] p;
}
int *show_add()
{
return p;
}
void show_data()
{
for(int i=0;i<size;i++)
cout<<p[i]<<' ';
cout<<endl;
}
T &operator=(const T& a);
void load_something()
{
for(int i=0;i<size;i++)
p[i]=i;
}
};
T & T::operator=(const T& a)
{
for(int i=0;i<size;i++)
p[i]=a.p[i];
return *this;
}
int main()
{
T a(5);
T b(5);
cout<<"pa="<<a.show_add()<<endl;
cout<<"a=";
a.show_data();
cout<<"pb="<<b.show_add()<<endl;
b.load_something();
cout<<"b=";
b.show_data();
cout<<"a=b"<<endl;
a=b;
cout<<"pa="<<a.show_add()<<endl;
cout<<"a=";
a.show_data();
cout<<"pb="<<b.show_add()<<endl;
cout<<"b=";
b.show_data();
return 0;
}
运行
成功
注意一个奇怪的地方,在显式定义了构造函数后,默认构造函数不会生成,但是代码中如果有使用的化复制构造函数还是会自动生成,赋值符重载也一样,所以编写类的时候一定要小心复制构造函数和赋值符重载的问题