1、构造函数分类
//1、构造函数分类
//按照参数分类:有参构造函数;无参构造函数(默认构造)
//按照类型分类:普通构造函数;拷贝构造函数
class Person
{
public:
//构造函数--无参构造函数(普通构造函数)
Person()
{
cout << "Person的无参构造函数调用" << endl;
}
//构造函数--有参构造函数(普通构造函数)
Person(int age)
{
m_Age = age;
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数
Person(const Person &p) //const防止拷贝过来的 &p被修改
{
m_Age = p.m_Age;
cout << "Person的拷贝构造函数调用" << endl;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}
public:
//属性
int m_Age;
};
2、函数调用方法
void test01()
{
//**********1、括号法**********
//Person p1; //默认构造函数调用
//Person p2(10); //有参构造函数调用
//Person p3(p2); //拷贝构造函数调用
//cout << "P2的年龄:"<<p2.m_Age << endl;
//cout << "P2的年龄:" <<p3.m_Age<< endl;
//①结果:
//p2的年龄为10,p3也为10
//②解释:
//拷贝构造函数的作用就是把一个类上面的所有属性拷贝赋值给另外一个类的属性
//③注意事项1:
//在调用默认构造函数时,不能写成 Person p1();
//这样写编译器会误认为函数的声明
//简单的函数声明格式 void test();
//**********2、显示法**********
Person p1;
Person p2 = Person(10); //有参构造
Person p3 = Person(p2); //拷贝构造
//①匿名对象:
//Person(10);为匿名对象
//匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
//②注意事项2:
//不要利用拷贝构造函数 初始化匿名对象
//Person(p3); 编译器会认为对象声明:Person(p3)==Person p3;
//与Person p3 = Person(p2); 形成冲突,会报错函数重定义
//**********3、隐式转换法**********
Person p4 = 10; //相当于Person p4=Person(10); 有参构造
Person p5 = p4; //拷贝构造
}
3、拷贝构造调用时机
C++中拷贝构造函数调用时机通常有三种情况:
1、使用一个已经创建完毕的对象来初始化一个新对象
2、值传递的方式给函数参数传值
3、以值方式返回局部对象
1、使用一个已经创建完毕的对象来初始化一个新对象
class Person
{
public:
//构造函数--无参构造函数(普通构造函数)
Person()
{
cout << "Person的无参构造函数调用" << endl;
}
//构造函数--有参构造函数(普通构造函数)
Person(int age)
{
m_Age = age;
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数
Person(const Person& p) //const防止拷贝过来的 &p被修改
{
m_Age = p.m_Age;
cout << "Person的拷贝构造函数调用" << endl;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}
public:
//属性
int m_Age;
};
//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(20); //有参
Person p2(p1); //拷贝
cout << "P2的年龄:"<<p2.m_Age << endl;
//结果:
/* Person的有参构造函数调用
Person的拷贝构造函数调用
P2的年龄:20
Person的析构函数调用
Person的析构函数调用*/
}
结果:
2、值传递的方式给函数参数传值
//2、值传递的方式给函数参数传值
void doWork(Person p)
{
}
void test02()
{
Person p3;
doWork(p3);
//实参在调用形参时,会调拷贝构造函数
//结果:
/* Person的无参构造函数调用
Person的拷贝构造函数调用
Person的析构函数调用
Person的析构函数调用*/
}
结果:
会调用拷贝函数
**3、以值方式返回局部对象 **
Person doWork2()
{
Person p5;
cout << (int*)&p5 << endl;
return p5;
}
void test03()
{
Person p4 = doWork2();
cout << (int*)&p4 << endl;
//结果:
/* Person的无参构造函数调用
00000038E8CFF7B4
Person的拷贝构造函数调用
Person的析构函数调用
00000038E8CFF8F4
Person的析构函数调用 */
//00000038E8CFF7B4为p5地址
//00000038E8CFF8F4为p4地址
}
结果:
//00000038E8CFF7B4为p5地址
//00000038E8CFF8F4为p4地址
4、构造函数的调用规则
//默认情况下,C++编译器至少给一个类添加三个函数:
//1、默认构造函数(无参、函数体为空)
//2、默认析构函数(无参、函数体为空)
//3、默认拷贝函数构造函数,对函数内的所有属性值拷贝
//构造函数调用规则如下:
//1、如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
//2、如果用户定义拷贝构造函数,C++不会再提供其他构造函数
//总结:
//1、用户提供了有参,编译器不会提供无参,但会提供拷贝;
//2、用户提供了拷贝,编译器什么构造函数都不会提供。
5、深拷贝与浅拷贝
深浅拷贝是面试的一个经典的问题,也是常见的一个坑。
①浅拷贝:简单的赋值拷贝操作。
②深拷贝:在堆区中重新申请空间,进行拷贝操作。
浅拷贝带来的问题——内存重复释放。
#include<iostream>
using namespace std;
//深拷贝与浅拷贝问题
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age,int height)
{
//利用new创建的数据,会返回该数据对应类型的指针
m_Height = new int(height);
m_Age = age;
cout << "Person的有参构造函数调用" << endl;
}
Person(const Person& p)
{
cout << "Person的拷贝构造函数调用" << endl;
m_Age = p.m_Age;
m_Height = p.m_Height;//编译器默认实现的就是这两行代码
}
~Person()
{
//将堆区开辟的数据进行释放
if (m_Height !=NULL)
{
delete m_Height;
m_Height = NULL;
}
cout << "Person的析构构造函数调用" << endl;
}
int m_Age;
int* m_Height;//为什么要用指针——要把身高开辟到堆区
};
void test()
{
Person p1(18,166);
cout << p1.m_Age<<"\t" << *p1.m_Height << endl;
Person p2(p1);
cout << p2.m_Age<<"\t" <<*p2.m_Height<< endl;
}
int main(void)
{
test();
system("pause");
return 0;
}
报错:
原因:指针m_Height会被释放两次
浅拷贝的这个问题需要用深拷贝来解决
深拷贝——手动创建拷贝构造函数。
Person(const Person& p)
{
cout << "Person的拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height;编译器默认实现的就是这行代码
//重写拷贝--深拷贝操作
//*p.m_Height为160
m_Height = new int(*p.m_Height);
}
总结:
如果有属性在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。