构造函数,析构函数,复制构造函数
(一)概念
1.构造函数:构造函数是用来给类中的成员变量赋予初值的函数,当类的对象被创建时就会调用构造函数。
2.析构函数:当一个对象被破坏或者结束时就会调用析构函数,析构函数没有返回类型,析构函数不能带有任何参数,析构函数只能有一个,析构函数必须是public(公有的)。
3.复制构造函数:当用户没有定义自己的复制构造函数时,系统将生成一个默认的复制构造函数,当按值传递对象时,就会创建一个形参的临时对象,然后调用复制构造函数把临时对象的值复制给实参。
(二)详细说明
(1)构造函数
默认构造函数:默认构造函数就是你泡杯咖啡,啥也别干,一边歇歇,在程序运行的时候系统会自动给你做好。
用户自定义构造函数:默认构造函数虽然让你节省劳动力,但是系统毕竟不是人脑,它一般情况下只能将数据初始化为0,而且遇到const类型的变量还不能使用默认构造函数。所以还是少喝杯咖啡,自己写吧~~
class Person
{
public:
Person();
void Show();
private:
int Age;
int Height;
};
Person::Person()
{
Age=21;
Height=175;
}
void Person::Show()
{
cout<<Age<<" "<<Height<<endl;
}
除了上面一种方法,还可以利用初始化列表对成员变量进行初始化
class Person
{
public:
Person();
void Show();
private:
int Age;
int Height;
};
Person::Person():Age(21),Height(175)
{
}
void Person::Show()
{
cout<<Age<<" "<<Height<<endl;
}
利用初始化列表对成员变量进行初始化当然也有一定的注意点。在利用初始化列表的时候对成员变量进行初始化的顺序是按照声明的时候顺序进行的,比如下面这种写法就会发生错误
//这种方法就是错误的,根据声明顺序,先是初始化Age,然后Height,而下面在Height没有初始化就将其值给Age,所以这
//种做法是不对的
Person::Person():Age(Height),Height(175)
{}
//下面这种做法就是正确的
Person::Person():Height(Age),Age(21)
{}
还有一点就是在类中有const类型变量的时候,构造函数必须使用初始化列表的形式来初始化const类型的变量。
class Person
{
public:
Person();
void Show();
private:
const int Age;
const int Height;
};
Person::Person():Age(21),Height(175)
{
}
void Person::Show()
{
cout<<Age<<" "<<Height<<endl;
}
最后注意一点,如果要对数组变量在构造函数的时候初始化,应该在大括号里面初始化,不能运用初始化列表的形式初始化
.
(2)析构函数
析构函数与构造函数的执行顺序一般相反,当有多个构造函数内嵌生成的时候,他们消失时候调用析构函数的顺序和生成构造函数的顺序是相反的。
//析构函数就是在构造函数前面添加了一个‘~’符号,记住析构函数没有返回值
class Person
{
public:
Person();
~Person();
};
Person::Person()
{}
Person::~Person()
{}
(3)复制构造函数
在以下常见的三种情况会调用复制构造函数
1,按值传递对象
2,函数返回对象
3,用一个对象初始化另一个对象
class Person { public: Person(); ~Person(); Person(const Person &people); void Show(); private: }; Person f() { Person m; return m; } Person::Person() { cout<<"调用构造函数"<<endl; } Person::~Person() { cout<<"调用析构函数"<<endl; } Person::Person(const Person &people) { cout<<"调用复制构造函数"<<endl; } int main() { Person p1; Person p2(p1); //按值传递对象 Person p3=f(); //函数返回对象 Person p4=p1; //用一个对象初始化另一个对象 system("pause"); }
在复制构造函数的时候,如果我们使用默认的复制构造函数不能够对类内指针变量进行合理的处理,所以当遇到类内有指针成员变量的时候,我们最好自己来写复制构造函数。其实使用系统默认的复制构造函数是采用浅复制的方法来进行的,下面我们来看看深复制和浅复制的区别。
浅复制:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。(如下图)
对象(地址0x200000B0)被浅拷贝到另外一个对象(地址0x200000C0),由于其中含有一个字符串对象,其地址为0x200000D0,在浅拷贝的时候,只是简单地将地址拷贝到新对象,因此原对象和新对象所指向的内容是一样的,都是“Hello, World”,如果新对象将该字符串进行了修改,那么原对象中对应的字符串也会发生同样的变化。而这在大部分情况下是不能接受的。
#include<iostream>
using namespace std;
class Animal
{
public:
int Age,Height;
public:
Animal(int age,int height);
~Animal();
};
Animal::Animal(int age,int height)
{
Age=age;
Height=height;
}
Animal::~Animal()
{
}
class Person
{
public:
Animal *animal;
public:
Person(Animal* ani);
~Person(){}
void Show()
{
cout<<animal->Age<<" "<<animal->Height<<endl;
}
void ReSet(int age,int height)
{
animal->Age=age;
animal->Height=height;
}
void getPersonAddress()
{
cout<<animal<<endl;
}
};
Person::Person(Animal* ani)
{
this->animal=new Animal(ani->Age,ani->Height);
}
int main()
{
Animal A1(21,175);
Person P1(&A1);
P1.Show();
Person P2(P1); //默认复制构造函数
P2.Show();
P2.ReSet(19,162);
P1.Show();
P2.Show();
P1.getPersonAddress();
P2.getPersonAddress();
delete P1.animal;
delete P2.animal;
system("pause");
}
运行结果:
上面出错的原因就是由于浅复制造成的,P1,P2中的animal指针指向同一段内存区域,当delete P1.animal的时候,就相当于把这段内存区域给释放了,当再次执行delete P2.animal的时候,就会引起错误。
.
深复制:深复制其实就是浅复制的一种改进,他重新开辟出一段空间用来重新存放对象,然后将指针指向新的对象地址
为了解决上述问题只要在增加一个复制构造函数,如下
Person(const Person &people)
{
animal=new Animal(people.animal->Age,people.animal->Height);
}
运行结果: