1.构造和析构函数的概述
构造函数和析构函数,这两个函数将会被编译器自动调用,构造函数完成对象的初始化动作, 析构函数在对象结束的时候完成清理工作。
注意:对象的初始化和清理工作是编译器强制我们要做的事情,即使你不提供初始化操作 和 清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事。
构造函数:实例化对象的时候系统自动调用
析构函数:对象释放的时候系统自动调用
构造函数语法:
构造函数函数名和类名相同,没有返回类型,连void都不可以,但可以有参数,可以重载
析构函数语法:
析构函数函数名是在类名前面加”~”组成,没有返回类型,连void都不可以,不能有参数, 不能重载
示例代码:
#include <iostream>
using namespace std;
class Data
{
public:
int num;
public:
//构造函数(无参的构造)
Data()
{
num = 0;
cout<<"无参的构造函数"<<endl;
}
//构造函数(有参的构造)
Data(int n)
{
num = n;
cout<<"有参的构造函数"<<endl;
}
//析构函数
~Data()
{
cout<<"析构函数"<<endl;
}
};
void test01()
{
//类实例化对象 系统自动调用构造函数
Data ob;
//它是一个局部变量 函数调用结束后ob被释放 系统自动调用析构函数
}
int main(int argc, char *argv[])
{
cout<<"‐‐‐‐‐001‐‐‐‐‐"<<endl;
test01();
cout<<"‐‐‐‐‐002‐‐‐‐‐"<<endl;
return 0;
}
打印结果
2.构造函数的分类以及调用
1、构造函数分类:
按参数类型:分为无参构造函数和有参构造函数
按类型分类:普通构造函数和拷贝构造函数(复制构造函数)
示例代码
#include <iostream>
using namespace std;
class Data
{
public:
int num;
public:
//构造函数(无参的构造)
Data()
{
num = 0;
cout<<"无参的构造函数"<<endl;
}
//构造函数(无参的构造)
Data(int n)
{
num = n;
cout<<"有参的构造函数"<<endl;
}
//析构函数
~Data()
{
cout<<"析构函数"<<endl;
}
};
void test02()
{
Data ob1; //调用无参 或 默认构造 (隐式调用)
Data ob2 = Data(); //调用无参构造 (显示调用)
Data ob3(10); //调用有参构造(隐式调用)
Data ob4 = Data(20); //调用有参构造(显示调用)
//隐式转换的方式 调用有参构造(针对于 只有一个数据成员)(尽量别用)
Data ob5 = 30;//转化成Data ob5(30)
//匿名对象(当前语句结束 匿名对象立即释放)
Data(40);
cout<<"‐‐‐‐‐‐"<<endl;
}
int main(int argc, char *argv[])
{
test02();
return 0;
}
以上就介绍了无参 有参构造函数分别如何调用
打印结果
由于出入栈的原因,先构造的会最后释放,所以从打印结果来看 构造顺序和析构顺序是相反的。
3.拷贝构造函数
#include <iostream>
using namespace std;
class Data
{
public:
int num;
public:
//构造函数(无参的构造)
Data()
{
num = 0;
cout<<"无参的构造函数num = "<<num<<endl;
}
//构造函数(有参的构造)
Data(int n)
{
num = n;
cout<<"有参的构造函数num = "<<num<<endl;
}
//析构函数
~Data()
{
cout<<"析构函数num = "<<num<<endl;
}
};
void test03()
{
Data ob1(10);
cout<<"ob1.num = "<<ob1.num<<endl;
//调用拷贝构造函数
Data ob2 = ob1;
cout<<"ob2.num = "<<ob2.num<<endl;
}
int main(int argc, char *argv[])
{
test03();
return 0;
}
上面代码中想要把ob1赋值给ob2,这时系统会去调用默认的拷贝构造函数,单纯的整体赋值(浅拷贝)
打印结果
如果用户自己实现了拷贝构造函数,系统将会调用 用户自己实现的拷贝构造函数,示例如下:
#include <iostream>
using namespace std;
class Data
{
public:
int num;
public:
//构造函数(无参的构造)
Data()
{
num = 0;
cout<<"无参的构造函数num = "<<num<<endl;
}
//构造函数(有参的构造)
Data(int n)
{
num = n;
cout<<"有参的构造函数num = "<<num<<endl;
}
//拷贝构造函数
Data(const Data &ob) //const Data &ob = ob1
{
//ob2.num = ob1.num
num = ob.num; //拷贝构造函数 是ob2调用 num就是ob2的num
cout<<"拷贝构造函数"<<endl;
}
//析构函数
~Data()
{
cout<<"析构函数num = "<<num<<endl;
}
};
void test03()
{
Data ob1(10);
cout<<"ob1.num = "<<ob1.num<<endl;
//调用拷贝构造函数(如果用户 不实现拷贝构造 系统将调用默认的拷贝构造)
//默认的拷贝构造:单纯的整体赋值(浅拷贝)
//如果用户实现了 拷贝构造 系统将调用用户实现的拷贝构造
Data ob2 = ob1;
cout<<"ob2.num = "<<ob2.num<<endl;
}
int main(int argc, char *argv[])
{
test03();
return 0;
}
上面代码,在类中实现了一个拷贝构造函数,系统就会调用它,打印结果如下:
总结:旧对象初始化新对象时 系统才会调用拷贝构造函数
//拷贝构造函数
Data(const Data &ob) //自身对象的常引用
{
num = ob.num; //拷贝构造函数 是ob2调用 num就是ob2的num
//相当于ob2.num = ob1.num;
cout<<"拷贝构造函数"<<endl;
}
Data ob1;
Data ob2 = ob1; //旧对象 初始化 新对象
//如下形式也会调用拷贝构造函数
Data ob2(ob1);
Data ob3 = Data(ob1);
注意:下方的就不会调用拷贝构造
Data ob1(10);
Data ob2;
ob2 = ob1;//不会调用拷贝构造 单纯对象 赋值操作
4.构造函数的调用规则
系统会对任何一个类提供3个函数成员函数:
默认构造函数(空) 默认析构函数(空) 默认拷贝构造函数(浅拷贝)
1、如果用户提供了有参构造 将屏蔽 系统的默认构造函数。
2、如果用户提供了有参构造 不会屏蔽 系统的默认拷贝构造函数。
3、如果用户提供了拷贝构造函数 将屏蔽 系统的默认构造函数、默认拷贝构造函数。
所以 对于构造函数:用户一般要实现:无参构造、有参构造、拷贝构造、析构。
5.浅拷贝与深拷贝
#include <iostream>
#include<string.h>
#include<stdlib.h>
using namespace std;
class Person
{
private:
char *m_name;
int m_num;
public:
Person()
{
m_name = NULL;
m_num = 0;
cout<<"无参构造"<<endl;
}
Person(char *name, int num)
{
//为m_name开辟空间
m_name = (char *)calloc(1, strlen(name)+1);
if(m_name == NULL){
cout<<"构造失败"<<endl;
}
cout<<"已申请好空间"<<endl;
strcpy(m_name, name);
m_num = num;
cout<<"有参构造"<<endl;
}
Person(const Person &ob)//拷贝构造 ob==>lucy
{
cout<<"拷贝构造函数"<<endl;
m_name = (char *)calloc(1, strlen(ob.m_name)+1);
cout<<"已申请好空间"<<endl;
strcpy(m_name, ob.m_name);
m_num = ob.m_num;
}
~Person()
{
if(m_name != NULL){
cout<<"空间已被释放"<<endl;
free(m_name);
m_name = NULL;
}
cout<<"析构函数"<<endl;
}
void showPerson(void)
{
cout<<"m_name = "<<m_name<<", m_num = "<<m_num<<endl;
}
};
void test01()
{
Person lucy("lucy", 100);
lucy.showPerson();
//浅拷贝的问题(多次释放同一块空间)
//通过自定义 拷贝构造函数 完成深拷贝动作
Person bob = lucy;//调用系统的默认拷贝函数(单纯的值拷贝)
}
int main(int argc, char *argv[])
{
test01();
return 0;
}
上面代码中如果没有自己写拷贝构造函数,那么系统就会调用默认的拷贝构造函数(仅仅是值的传递),会导致两个指针指向同一块空间,析构函数中这块空间会被释放两次。
而在我们自己写的拷贝构造函数中,我们为bob的m_name也开辟了自己的空间,这样两个指针就不会指向同一块空间,在析构函数中会分别释放这两块空间。
写该类代码的要点:
如果类中的成员 指向了堆区空间 一定要记得在析构函数中 释放该空间
如果用户 不实现 拷贝构造 系统就会提供默认拷贝构造
而默认拷贝构造 只是单纯的赋值 容易造成浅拷贝问题
用户记得 要实现:无参构造(初始化数据)、有参构造(赋参数)、拷贝构造(深拷贝) 、析构函数(释放空间)