写在前面
上一节解决了类与对象封装的问题,这一节就是对象的初始化和清理的构造函数与析构函数的内容了;对象的初始化和清理也是两个非常重要的安全问题:一个对象或者变量没有初始状态,对其使用后果是未知,同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题;c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器提供编译器提供的构造函数和析构函数是空实现。下面开始正文:
构造函数和析构函数
语法
构造函数语法: 类名(){}
1、没有返回值也不写void
2、函数名称与类名相同
3、构造函数可以有参数,因此可以发生重载
4、程序在调用对象时会自动调用,无需手动调用且只会调用一次
析造函数语法: ~类名(){}
1、没有返回值也不写void
2、函数名称与类名相同,在名称前加上符号~
3、构造函数不可以有参数,因此不可以发生重载
4、程序在对象销毁前会自动调用析构,无需手动调用且只会调用一次
作用
构造函数 主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
析构函数 主要作用于对象销毁前系统自动调用,执行一些清理工作
代码实现
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person构造函数的调用" << endl;
}
~Person()
{
cout << "~Person析构函数的调用" << endl;
}
};
void test01()
{
Person p;//栈上的对象运行完毕后,编译器自动释放
}
int main()
{
test01();
}
test01中创建了Person类p,主函数只是调用了一下创建的Person类p,编译器就自动调用了类的构造函数和析构函数,析构函数是程序运行完毕后,编译器自动清理内存空间的时候调用的。
两大分类方式
按参数分为 有参构造 和 无参构造
按类型分为 普通构造 和 拷贝构造
无参和有参构造很好理解,就是有无参数的区别,这里讲一下拷贝构造函数:
//拷贝构造函数
Person(const Person &p) //格式: const 类名 引用(&)变量名
{
//讲传入的人身上的所有属性,拷贝到我身上
age = p.age;
cout << "Person的拷贝构造函数调用" << endl;
}
Person()的括号中是const Person &p,这是拷贝构造的函数格式,他需要传入相同类的对象,会产生一个具有相同属性的类,比如p1的年龄为20,经过拷贝构造p2的年龄也会是20,但是两个类对象的地址并不相同,这个到后面会具体解释
三种调用方式
class Person
{
public:
//构造函数
Person()
{
cout << "Person的无参构造函数调用" << endl;
}
Person(int a)
{
age = a;
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数
Person(const Person &p) //格式: const 类名 引用(&)变量名
{
//讲传入的人身上的所有属性,拷贝到我身上
age = p.age;
cout << "Person的拷贝构造函数调用" << endl;
}
~Person()
{
cout << "~Person的析构函数调用" << endl;
}
int age;
};
括号法
Person p;//默认构造函数调用
Person p2(10);//有参构造函数
Person p3(p2);//拷贝构造函数
cout << "p2 age=" << p2.age << endl;
cout << "p3 age=" << p3.age << endl;
注意事项:调用默认构造函数的时候,不要加();Person p1() 编译器会认为是函数的声明,不认为在创建对象,等同于 void func()
显示法
Person p;
Person p2=Person(10);//有参构造函数
Person p3=Person(p2);//拷贝构造函数
Person(100);//匿名对象,特点:当前执行完毕后,系统会立即回收掉匿名对象
cout << "AAAAA" << endl;
注意事项2:拷贝构造初始化匿名对象等同于去掉括号,导致重定义,不要用拷贝构造初始化匿名对象,如果利用匿名对象的话,会和Peron p2=Person(10),重复,出现重定义错误;也不要用拷贝构造初始化匿名对象。
隐式转换法
Person p2 = 10;// 有参构造函数
Person p3 = p2;// 拷贝构造函数
这个方法不推荐使用,调用的很不明显,建议使用前面两个方法调用构造函数。
正确调用拷贝构造函数
class Person
{
public:
Person()
{
cout << "Person的无参构造函数调用" << endl;
}
Person(int a)
{
m_age = a;
cout << "Person的有参构造函数调用" << endl;
}
Person(const Person& p)
{
m_age = p.m_age;
cout << "Person的拷贝构造函数调用" << endl;
}
~Person()
{
cout << "Person 的析构函数调用" << endl;
}
int m_age;
};
正常调用
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年龄为:" << p2.m_age << endl;
}
主函数中直接调用test01,这时候会显示 p2的年龄为20,并且打印:拷贝构造函数的调用。所以说,使用一个已经创建完毕的对象来初始化一个新对象的时候会调用拷贝构造函数
值传递的方式给函数参数传值
void doWork(Person p)
{ }
void test02()
{
Person p;
doWork(p);
}
大家可以猜一下,在主函数调用,会运行出什么结果,答案是:无参构造函数调用和拷贝构造函数调用,最后是两个析构函数调用;浅析一下过程,调用test02时创建了对象P,所以自动调用无参构造函数,当运行到doWork(p)时,调用拷贝构造函数,随后拷贝构造函数被清理,调用析构函数,程序结束前,p被清理,再次调用析构函数,程序结束。
值传递方式返回局部对象
Person doWork2()
{
Person p1;
cout << (int)&p1<<" 1 " << endl;
return p1;//返回就拷贝构造函数,随后释放掉,调用析构
}
void test03()
{
Person p = doWork2();//重新创建局部对象,并不是上面返回的对象p1
cout << (int)&p<<" 2 " << endl;
}
这里doWork2返回值时Person类型,也就是说return p1后会拷贝构造其属性给test03调用的p,但是p1和p2并不是同一个对象,我们可以输出他们的地址来验证。
这里的调用顺序是:Person P1 的无参构造,随后输出p1地址,然后返回值的时候先调用拷贝构造函数,把值赋给p,随后清理p1调用析构;然后回到test03中,输出p的地址,程序结束前调用析构,程序结束。
构造函数的调用规则
编译器提供:
1、创建一个类,c++编译器会给每个类都至少添加三个函数
- 默认构造(空实现)
- 析构函数(空实现)
- 值拷贝构造(值拷贝)
2、如果我们写了有参构造,编译器不再提供默认构造,但是提供值拷贝构造
如果我们写了拷贝构造函数,编译器不再提供其他普通构造函数
void test01()
{
Person p1;
p1.m_age = 19;
Person p2(p1);//即使没写拷贝构造仍然能得到结果p2.m_age =19
cout << "p2的年龄为:" << p2.m_age << endl;
}
也就是说,就算我们不写无参和拷贝构造,调用test03也会得到值拷贝后的p2年龄,这是编译器默认提供的三个函数。但是如果写了有参构造,Person p1这行代码就会报错,提示找不到默认构造函数;同样的如果自己写了拷贝构造,Person p1也会显示同样的错误。
总结
这篇博文讲了一部分对象的初始化和清理的内容,着重讲了构造函数的调用方法、规则,以及拷贝构造函数的概念,调用方法和细节。下一篇直接准备深浅拷贝的内容和初始化列表,静态成员等的问题,彻底结束对象的初始化和清理内容,期待下篇与你们见面!
到此这篇关于C++超详细讲解构造函数与析构函数的用法及实现的文章就介绍到这了,更多相关C++构造函数与析构函数内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!