对象的初始化和清理是两个非常重要的安全问题,一个对象或者变量没有初始状态,对其使用后果是未知,同样的使用完一个对象或变量,没有及时清理,也会造成—定的安全问题。
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
构造函数:主腰作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供的构造函数和析构函数是空实现。
构造函数和析构函数的语法
构造函数:
1、没有返回值,也不用也void;
2、函数名与类名一致;
3、构造函数可以有参数,也可以重载;
4、程序在调用对象时候会自动调用构造函数,无须手动调用,而且只会调用一次。
析构函数:
1、没有返回值,也不用也void;
2、函数名与类名一致,但需要在函数名前加上波浪号:~;
3、析构函数不能带参数,所以不能发生函数重载;
4、程序在对象销毁前时候会自动调用析构函数,无须手动调用,而且只会调用一次。
构造函数的分类
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
class A { public: A(){}//无参构造函数 A(int a){}//有参构造函数 A(const A &p)//拷贝构造函数,将对象p的属性传入该构造函数,在其实现过程中可以使用p的属性 { a=p.a; b=p.b; } private: int a; int b };
构造函数的调用
1、括号法
如下图所示,其调用比较简单,也是最常用的方法:
class A { public: A()//无参构造函数 { cout<<"无参构造函数调用"<<endl; } A(int x,int y)//有参构造函数 { a=x; b=y; } A(const A &p)//拷贝构造函数 { a=p.a; b=p.b; } private: int a; int b }; int main() { 括号法: A p1;//调用A()无参构造函数 A p2(10,20);//调用A(int x,int y)有参构造函数 A p3(p2);调用A(const A &p)拷贝构造函数,即p3这个对象将p2的所有属性都拷贝了一份 }
注意
无参构造的调用不能带括号,不能A p1(),这样写编译器会认为是一个函数声明。
2、显示法
A p1;//无参构造还是老样子
A p2 = A(10,20);//调用有参构造函数
A p3 = A(p2);//调用拷贝构造函数
注意
上图中等号右侧的A(10,20)和A(p2)属于匿名对象,其特点是当前执行结束后,会被系统会立即回收掉。
3、隐式转换法
A p1;//无参构造还是老样子
A p2 = {10,20};//调用有参构造函数,这是多参数情况,如果有参构造只有一个参数,就不用大括号了。
A p3 = p2;//调用拷贝构造函数
构造函数的调用规则
- 如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造;
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数。
深拷贝与浅拷贝
- 浅拷贝:简单的赋值拷贝操作,浅拷贝带来的问题就是堆区的内存重夏释放
- 深拷贝:在堆区重新申请空间,进行拷贝操
在调用拷贝构造函数的时候,即会有2个不同构造函数,如果部分内存开辟到了堆区,然后需要手动释放该部分内存,但是这2个不同的构造函数都要释放相应的堆区,第一个释放了,那么第二个怎么办呢?那就报错了,这就是浅拷贝带来的问题。这就需要深拷贝来解决。
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "无参构造函数的调用" << endl;
}
A(int age,string name)
{
m_Age = age;
m_Name = name;
cout << "有参构造函数的调用" << endl;
}
A(const A &p)
{
m_Age = p.m_Age;
m_Name = p.m_Name;
cout << "拷贝构造函数的调用" << endl;
}
~A()
{
cout << "析构函数的调用" << endl;
}
private:
int m_Age;
string m_Name;
};
int main()
{
A p1;//调用无参构造函数
A p2 = p1;//调用拷贝构造函数
}
上面是简单的构造函数调用,没有开辟堆区的内存,编译器正常执行:
接下来我们要在堆区申请内存空间,如下:
对有参构造、拷贝构造和析构函数进行了修改:
class A
{
public:
A()
{
cout << "无参构造函数的调用" << endl;
}
A(int age,string name)
{
m_Age = age;
//m_Name = name;
m_Name = new string(name);
cout << "有参构造函数的调用" << endl;
}
A(const A &p)
{
m_Age = p.m_Age;
m_Name = p.m_Name;
cout << "拷贝构造函数的调用" << endl;
}
~A()
{
if (m_Name != NULL)
{
delete m_Name;
m_Name = NULL;
}
cout << "析构函数的调用" << endl;
}
private:
int m_Age;
string *m_Name;
};
int main()
{
A p1(18, "小明");//调用无参构造函数
A p2 = p1;//调用拷贝构造函数
}
出现如下问题:
即有参构造和拷贝构造这两个函数中的m_Name变量都开辟到同一内存空间,释放变量的时候先先执行拷贝构造函数的析构代码,即把m_Name变量释放了;然后需要执行有参构造的析构函数的析构代码,编译器就访问不到m_Name变量的地址了,即使访问到了,也是非法访问,所以报错了。
解决方法-----------使用深拷贝
A(const A &p)
{
m_Age = p.m_Age;
//m_Name = p.m_Name;浅拷贝
m_Name = new string(*p.m_Name);//深拷贝
cout << "拷贝构造函数的调用" << endl;
}
在拷贝构造函数里面我们对传入的对象重新开辟新的空间,那么就有2个内存空间了,2个函数分别指向2个不同的空间,这样就解决了上面的问题了。