1.构造函数和析构函数
当你创建一个类时,如果你不自己定义,编译器会默认创建这两个函数,并且会自动调用,但他俩就是空实现,也就是说函数里木有内容。
构造函数是在创建一个对象时自动调用对成员属性进行初始化,析构函数则是将其在对象销毁前自动调用来进行一些清理工作。
语法
构造函数:类名(){ }
没有返回值,跟void差不多,可以有参数,因此可以重载,会自动调用,而且只会调用一次。
析构函数:~类名(){ }
没有返回值,跟void差不多,不可以有参数,因此不可以重载,会自动调用,而且只会调用一次。
所以说啥叫自动调用呢,比如你定义了一个名叫 student的类,然后你是不是实例化也就是定义一个对象 像这样:student s;创建了一个student类的对象s,这个时候构造函数就已经被调用了,但没啥反应,因为这时候因为你木有在student类内定义这个函数,系统默认创建了构造函数student(){这里面啥也木有},它是一个空函数。
2.构造函数的分类和调用
按类型分为普通和拷贝,按参数分为有参和无参
class student{
public:
student() //无参构造又叫默认函数 因为如果你不写,系统会默认生成这个函数
{
}
student(int age) //有参构造函,数普通构造
{
m_Age = age;
}
student(const student &s)有参构造函数,拷贝函数 加const修饰,表示不能改变传进来的引用
{
m_Age = s.m_Age;
}
int m_Age;
};
int main()
{
//调用
//括号法
student s1; //调用了默认构造函数
student s2(18); //调用了有参构造函数
student s3(s2); //调用了拷贝构造函数
//显示法
student s4 = student(19);
student s5 = student(s4); //调用了拷贝构造函数
// student(10); 这种叫匿名对象,当前行结束后系统会立即回收匿名对象。
//隐式转换法
student s6 = 10; //调用了有参构造函数,相当于 student s6 = student(10);
student s7 = s6; //调用了拷贝构造函数
return 0;
}
目前先明白代码是啥意思,能看懂代码,等到时候才能拿来就用,学习后面的知识的时候也更轻松。
构造函数就是一个给类的属性初始化的函数,也只有类可以这样调用 用来初始化变量的函数。
析构函数有点像free,delete 的用法,主要是用来释放你定义的类时所申请只占用的空间。
3.拷贝构造函数的调用时机,这里我不再具体写代码了,借用上面写的。
1.当我们已经定义并初始化了一个对象,要初始化另一个新对象时,会调用拷贝构造函数
比如 : student s2(18); //调用了有参构造函数
student s3(s2); //调用了拷贝构造函数让s1,s2属性值完全一样。
2.将类作为一个函数的值传递的时候,也会调用到拷贝构造函数,
比如 :函数 void test(student s){ s.m_Age = 0 }; sudent s1(10); test(s1);
这是一个正常的函数的值传递调用,只不过传递的值得类型是studen类,此时s是形参,相当于,实参s1将他的值了拷贝一份传递给了形参s。所以调用到了拷贝函数,编译器自动调用的。众所周知值传递形参不会改变实参,所以调用函数后,s1.m_Age的值还是10。
正常情况下,c++编译器会默认给一个类至少提供三个函数,默认构造函数(空实现),析构函数(空实现),拷贝构造函数(提供简单的赋值操作)这样保证了你用类作为值传递给函数的时候可以实现。
如果你自己定义了拷贝构造函数,系统就不会再给你提供默认构造函数。先晓得有这种情况就行,知道了好像也没啥用这会儿。
浅拷贝&深拷贝,析构用途
简单的进行赋值操作的叫浅拷贝,编译器默认提供的拷贝构造函数就是浅拷贝,
深拷贝则需要用到new关键字,向堆区申请空间,进行赋值操作,这就叫深拷贝,这得你自己个写,系统提供得是最简单的,有时候无法提供你想要的一些功能。
class student{
public:
int m_Age;
int *m_Height; //这是个指针变量
//浅拷贝函数,如果你不写,编译器会提供的就是这种,稍微带点智能,你有几个属性他就会有几个赋值操作。
student(const student &s)
{
m_Age = s.m_Age;
m_Height = s.m_Height;
//这里因为是赋值操作,俩指针会指向同一块内存,也就是存的地址一样
//这样在析构函数到用时,会delete两次同一块地址,不合法,也是浅拷贝的弊端
}
//深拷贝构造函数,当然如果是你自己定义的函数,你还可以加一些cout啥的,当成void来用,但最好不要偏离实现初始化的功能。
//这跟上面的函数重名,他俩又没构成重载,运行会报错,所以正常只能有一个拷贝构造函数
student(const student &s)
{
m_Age = s.m_Age;
m_Height = new int(*s.m_Height); //这相当于重新申请了一块新空间给指针,
//所以不会出现浅拷贝出现的问题
}
//析构函数,没定义之前系统会默认提供的是个空的,在对象定义后,运行结束前会自动调用。
~student()
{
delete(m_Height); //因为你如果要给指针变量赋值就要申请一块空间;m_Height=new int(180)
//这时候用完,也就是在代码结束前要释放申请的空间。
}
};