一、构造函数与析构函数
C++利用了了构造函数和析构函数解决了上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作
对象的初始化和清理是编译器强制做的事情,不提供构造和析构函数,编译器会提供空实现的构造函数与析构函数。
1.1构造函数作用:
主要作用在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
语法:
类名(){}
1.构造函数,没有返回值,也不写void
2.函数名称与类名相同
3.构造函数可以有参数,可以发生重载
4.程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次
例子:
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "构造函数的调用" << endl;
}
};
void test01()
{
Person p;
}
int main()
{
test01();
return 0;
}
<<构造函数的调用
1.2析构函数作用:
主要作用在于对象销毁前系统自动调用,执行一些清理工作
语法:
~类名(){}
1.析构函数,没有返回值,也不写void
2.函数名称与类名相同,在名称前加上符号~
3.析构函数不可以有参数,因此不能发生重载
4.程序在对象销毁前会自动调用析构函数,无须手动调用,而且只会1调用一次
#include <iostream>
using namespace std;
class Person
{
public:
~Person()
{
cout << "析构函数的调用" << endl;
}
};
void test01()
{
Person p;
}
int main()
{
test01();
return 0;
}
<<析构函数调用
二、构造函数的分类与调用方法
构造函数可以按不同的类型分类
按参数可以分为有参构造与无参构造
还可以按类型分为拷贝构造与普通构造
拷贝构造就是把一个对象属性完全赋值给另外一个对象,但是原对象不能改变,因此要加const
拷贝构造函数语法:
类名(const类名 &对象名){}
2.1括号法
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
void test01()
{
Person p1;
Person p2(10);
Person p3(p2);
}
int main()
{
test01();
return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用
析构函数的调用
输出p3的年龄
cout << "p3的年龄:" << p3.age << endl;
<<p3的年龄:10
Ps:构造默认函数使用括号法不能加括号,因为这样会被认为是函数声明
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
void test01()
{
Person p1();
Person p2(10);
Person p3(p2);
cout << "p3的年龄:" << p3.age << endl;
}
int main()
{
test01();
return 0;
}
<<
有参构造函数的调用
拷贝构造函数的调用
p3的年龄:10
析构函数的调用
析构函数的调用
2.2显示法
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
void test01()
{
Person p1;
Person p2 = Person(10);
Person p3 = Person(p2);
cout << "p3的年龄:" << p3.age << endl;
}
int main()
{
test01();
return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
拷贝构造函数的调用
p3的年龄:10
析构函数的调用
析构函数的调用
析构函数的调用
PS1:匿名对象:直接写Person(10);称为匿名对象,特点:当前行结束,系统会立即回收掉匿名对象,也即立即调用析构函数
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
void test01()
{
Person p1;
Person(10);
cout << "--------------------------" << endl;
}
int main()
{
test01();
return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
析构函数的调用
--------------------------
析构函数的调用
Ps2:不要使用拷贝函数初始化匿名对象,编译器会认为Person (p3) 相当于 Person p3调用的是默认构造也即无参构造
2.3隐式转换法
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
void test01()
{
Person p1;
Person p2 = 10;
Person p3 = p2;
}
int main()
{
test01();
return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用
析构函数的调用
三、拷贝构造函数使用时机
3.1使用一个已经创建完毕的对象来初始化一个新对象
这点前面已经有了,就是直接调用拷贝构造函数
3.2值传递的方式给函数参数传值
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
void do_work(Person p)
{
}
void test01()
{
Person p;
do_work(p);
}
int main()
{
test01();
return 0;
}
<<
无参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用
3.3以值方式返回局部对象
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
Person do_work()
{
Person p1;
return p1;
}
void test01()
{
Person p = do_work();
}
int main()
{
test01();
return 0;
}
<<
无参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用
PS:不用的编译器可能结果不一样,在gcc中就不会出现拷贝函数的调用,打印地址也会发现地址一样
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
Person do_work()
{
Person p1(10);
cout << (int*)&p1 << endl;
return p1;
}
void test01()
{
Person p = do_work();
cout << (int*)&p << endl;
cout << p.age << endl;
}
int main()
{
test01();
return 0;
}
<<
有参构造函数的调用
0x7ffebf3bf96c
0x7ffebf3bf96c
10
析构函数的调用
四、构造函数调用规则
默认情况下:c++编译器会至少给一个类构造函数添加3个函数
1.默认构造函数(无参,函数体为空)
2.析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
1.用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
2.用户定义拷贝构造,c++不会再提供其他构造函数
可以记为:拷贝构造>有参构造>无参构造
4.1注释掉拷贝构造,提供自定义的有参和无参构造
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝构造
//Person(const Person &p)
//{
// age = p.age;
// cout << "拷贝构造函数的调用" << endl;
//}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
void test01()
{
Person p1(10);
Person p2(p1);
cout << "p2的年龄:" << p2.age << endl;
}
int main()
{
test01();
return 0;
}
<<
有参构造函数的调用
p2的年龄:10
析构函数的调用
析构函数的调用
与前面有自定义的拷贝构造相比,可以看到它提供了默认的拷贝构造,只不过少了自己写的输出“拷贝构造函数调用”,析构函数照样调用了
可以理解为每一个对象用完都会调用析构函数
4.2注释掉拷贝构造,无参构造,提供自定义的有参构造
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
//Person()
//{
// cout << "无参构造函数的调用" << endl;
//}
//有参构造函数
Person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝构造
//Person(const Person &p)
//{
// age = p.age;
// cout << "拷贝构造函数的调用" << endl;
//}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
void test01()
{
Person p;
}
int main()
{
test01();
return 0;
}
<<会报错,提示你没有默认的无参构造
综合4.1和4.2可以明白,提供自定义有参构造,默认拷贝还会有,但是默认无参不会再有了
4.3注释无参构造与有参构造,提供自定义拷贝构造
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
// Person()
// {
// cout << "无参构造函数的调用" << endl;
// }
//有参构造函数
// Person(int a)
// {
// age = a;
// cout << "有参构造函数的调用" << endl;
// }
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
void test01()
{
Person p;
person p1(10);
}
int main()
{
test01();
return 0;
}
<<35,36行都报错
这种情况下提供了自定义拷贝函数,那么默认无参构造与有参构造都不在提供
五、深拷贝与浅拷贝
5.1什么是深拷贝与浅拷贝?
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
5.2为什么需要深拷贝?
在堆区申请完空间后,需要程序员手动释放分配的空间,这是如果不使用深拷贝会带来内存重复释放问题
例子:
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a, int height)
{
age = a;
m_height = new int(height);
cout << "有参构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
int *m_height;
};
void test01()
{
Person p1(18, 170);
cout << "p1的年龄:" << p1.age << " p1的身高:" << *p1.m_height << endl;
Person p2(p1);
cout << "p2的年龄:" << p2.age << " p2的身高:" << *p2.m_height << endl;
}
int main()
{
test01();
return 0;
}
<<
有参构造函数的调用
p1的年龄:18 p1的身高:170
p2的年龄:18 p2的身高:170
析构函数的调用
析构函数的调用
如果不手动释放,没有报错
但是为了规范我们需要手动释放,则会报错,手动释放过程写在析构函数里面,遵循先进后出,先释放p2,再释放p1
原因分析:在使用Person p2(p1)相当于利用编译器自动提供的拷贝函数,做浅拷贝操作,最终会先释放p2的m_height内存再释放p1的m_height,这样就导致了重复释放问题。
5.3解决方法:使用深拷贝
具体自定义拷贝构造函数
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a, int height)
{
age = a;
m_height = new int(height);
cout << "有参构造函数的调用" << endl;
}
//自定义拷贝构造函数解决浅拷贝重复释放内存空间问题
Person(const Person &p)
{
cout << "拷贝构造函数调用" << endl;
age = p.age;
//m_height = p.m_height;这行其实就是默认拷贝构造函数的身高浅拷贝
m_height = new int(*p.m_height);
}
//析构函数
~Person()
{
if(m_height != NULL)
{
delete m_height;
m_height = NULL;
}
cout << "析构函数的调用" << endl;
}
int age;
int *m_height;
};
void test01()
{
Person p1(18, 170);
cout << "p1的年龄:" << p1.age << " p1的身高:" << *p1.m_height << endl;
Person p2(p1);
cout << "p2的年龄:" << p2.age << " p2的身高:" << *p2.m_height << endl;
}
int main()
{
test01();
return 0;
}
<<
有参构造函数的调用
p1的年龄:18 p1的身高:170
拷贝构造函数调用
p2的年龄:18 p2的身高:170
析构函数的调用
析构函数的调用
Ps:有属性在堆区开辟,一定自己提供拷贝构造函数,防止浅拷贝带来的问题。