只要创建了一个类编译器会默认创建三个构造,
1.无参构造
2.有参构造
3.拷贝构造
这三个函数你不写编译器就自动用编译器默认的,你写了就用你的。
// 构造函数和析构函数
// 类实例化一个对象时强制调用构造函数,对象销毁后自动调用析构函数进行清理工作。
// 如果没有手动写出构造和析构函数,编译器会自动补上,只不过是空实现。
#include <iostream>
using namespace std;
class fun {
public: // 如果私有化 则该类将不能在外部实例化对象
fun() { // 没有返回值也不写void
// 构造函数一般进行初始化的工作。
// 可以有形参,可以重载,有参数的是有参构造,该构造为无参构造
// 创建对象时会自动调用构造且只会调用一次。
cout << "构造函数被调用!" << endl;
}
fun(int a) {
cout << "有参构造 " << a << endl;
}
// 析构函数 语法 " ~类名(){} "
~fun() {// 没有返回值也不写void
// 析构函数进行清理工作。
// 析构函数没有形参,不能发生重载。
// 析构函数在对象死亡的时候被自动调用且只调用一次。
cout << "析构函数被调用!" << endl;
}
};
class Person {
int age;
public:
// 普通构造函数
Person(){
}
Person(int a) {
}
// 拷贝构造函数
// 不写拷贝构造函数时,系统默认提供,拷贝p的所有属性到新的对象中
Person(const Person& p) {
// 拷贝时不能修改本体所以要写const
age = p.age;
}
};
// 调用
void test(){
Person p1; // 默认构造的调用
// 1.括号法
Person p2(10); // 调用有参构造函数
Person p3(p2); // 括号法调用拷贝构造函数
// 注意:1、调用默认构造时 不要加括号。
// Person pp(); // 编译器认为这是函数的声明,而不是创建对象。
// 2.显示法
Person p4 = Person(10); // 调用有参构造
Person p5 = Person(p4); // 显示法调用拷贝构造
Person(10); // 创建匿名对象,特点:当前行运行结束后会立即调用它的析构函数结束对象的生命
// Person(p3);
// 注意:2、不要利用匿名实例化对象调用拷贝构造。
// 编译器会认为这是个对象的声明 Person(p3) === Person p3;
// 编译器就会报语法错误,p3重定义
// 3.隐式转换法
Person p6 = 10; // 相当于 Person p6 = Person(10);
Person p7 = p6; // 隐式法 调用 拷贝构造
}
class ccopy {
public:
int age;
public:
ccopy() {
}
ccopy(int age) {
this->age = age;
}
ccopy(const ccopy & c) {
this->age = c.age + 1; // 这样写测试更明显
}
~ccopy() {
}
};
void dowork(ccopy p) {
// 这里实参传入时会触发拷贝构造函数
// 我们将拷贝构造函数修改为 新对象时旧对象的age+1;
cout << "拷贝打印 " << p.age << endl; // 这里打印2 因为拷贝构造中 cc1.age+1 == 2
}
ccopy dowork2() {
ccopy c(1);
return c; // 这里函数结束后栈被释放,
//但是释放前拷贝了c对象,所以返回 通过触发拷贝构造 产生的对象
}
void text01() {
// 拷贝函数的调用时机
// 1.使用一个已经初始化完毕的对象来初始化一个新对象。
ccopy c1(10);
ccopy c2 = c1;
// 2.值传递的方式给函数参数传值
ccopy cc1(1);
dowork(cc1); // 调用形参为ccopy对象的函数 传入的cc1 age==1;
// 3.值方式返回局部对象
ccopy cc2 = dowork2(); // 这里cc2.age == 2;
// 因为调用函数dowork2 时触发拷贝
// dowork2中实例化了c对象然后栈销毁前触发拷贝构造c.age+1 == 2
cout << "cc2 == " << cc2.age << endl; // cc2 == 2
}
int main()
{
text01();
//fun f;
return 0;
}
如果你写了拷贝构造函数,编译器将不会提供其他的构造函数
class demo {
public:
demo(const demo& d) { // 只写拷贝构造
}
};
void t_demo() {
demo d; // 这里实例化不了demo对象。因为没有构造函数
}
深拷贝与浅拷贝
所谓浅拷贝就是拷贝的时候考虑的少,不管被拷贝的对象属性是什么全拿过来赋给新的对象,而深拷贝则是对被拷贝对象的属性值多考虑了一步。当然考虑不周就会付出代价,下面列举出浅拷贝的问题以及深拷贝的优化。
// 深拷贝与浅拷贝
#include <iostream>
using namespace std;
// 浅拷贝:逐字节拷贝
// 编译器默认提供的就是浅拷贝
// 浅拷贝会出现的问题
// 1.堆区的内存重复释放
class student {
public:
student(int age) {
this->age = age;
}
~student() { // 析构函数
delete(height); // 释放对象通过new开辟的堆区空间
// 当触发浅拷贝时将原来对象的地址拷贝了一份,
// 但是原来的对象以及释放了该地址空间
// 拷贝来的这个对象再次释放这个地址就会出现段错误
}
// 深拷贝 解决上述问题
student(const student& s1) {
this->age = s1.age;
this->height = new int(*s1.height);// 像这种重新申请空间拷贝有效值称为深拷贝
}
int age;
int* height;
};
int main()
{
student s1(10);
s1.height = new int(10);
student s2 = s1; // 触发拷贝构造
system("pause");
return 0;
}