在使用c++编程的时候你总会自不自觉的用到构造函数还有析构函数(只要你用到了类,不管是你写的还是别人写的都会调用到)。
首先看看它们在哪里会被调用:
#include <stdio.h>
class AClass
{
public:
AClass()
{
printf("AClass()\n");
}
~AClass()
{
printf("~AClass()\n");
}
};
int main()
{
AClass C1; //声明一个类实例的时候会调用“构造函数”
AClass* pC2 = new AClass(); //new 一个类实例的时候会调用“构造函数”
delete pC2; //delete 一个类实例的时候会调用“析构函数”
return 0;
//main return 后系统会帮我们释放C1的空间,会调用“析构函数”
}
运行结果:
(因为我是在vc6.0写的控制台程序,他这个编译器在debug模式下有个特点:
就是会一直运行完 main 的return 函数后再自动显示 press any key tocontinue 让你看看运行效果,等你按键盘上的任意键退出黑框框.
就是说现在C1已经是被释放掉了,所以我们才能见到两个析构函数)
然后在讲讲一些关于构造函数和析构函数的特殊小知识:
1、定义一个类的时候如果你没有写构造函数,那系统会帮你自动生成一个默认构造函数。但如果你写了一个构造函数,即使不是无参数的构造函数,那系统就不会再为你生成默认构造函数。
我们来看一个具体的例子:
#include <stdio.h>
class AClass
{
//没有写构造函数
};
int main()
{
AClass c;
}
这样的程序是可以编译通过的系统会帮你自动生成一个默认构造函数。
但如果你写了一个构造函数
#include <stdio.h>
class AClass
{
public:
//你写了一个构造函数,它有一个int类型的参数
AClass(int data)
{
}
};
int main()
{
AClass c;
}
这样你运行程序的时候编译器就会报错:
没有找到默认构造函数。因为现在系统不会帮你自动生成一个默认构造函数了。
那你就只好自己在写一个咯:
#include <stdio.h>
class AClass
{
public:
//你写了一个构造函数,它有一个int类型的参数
AClass(int data)
{
}
//系统不帮你生成,你就自己写一个
AClass()
{
}
};
int main()
{
AClass c;
}
这样就能编译通过了。
同样的情况是如果你没写默认构造函数,当你继承出来一个子类,它也不能生成
#include <stdio.h>
class BaseClass
{
public:
//你写了一个构造函数,它有一个int类型的参数
BaseClass(int data)
{
}
};
//ChildClass继承于BaseClass但BaseClass没有默认构造函数
class ChildClass : public BaseClass
{
};
int main()
{
ChildClass c;
}
这样的代码也不能编译通过
2、如何用构造函数初始化类成员变量:
class AClass
{
public:
AClass():data(0),data2(0)
{
}
private:
int data1,data2;
};
其实也就是构造函数首部后加
冒号,然后将要初始化的成员变量写在后面,紧更着变量名的后面就放初始化的值,多个变量之间用逗号相隔。
当然你可能会问这样写和放在函数体的 花括号里写有什么区别
别急,先来说说这种用法其实还可以在冒号后面加父类的构造函数,表示要用父类的某一个构造函数
#include <stdio.h>
class BaseClass
{
public:
BaseClass()
{
printf("BaseClass\n");
}
~BaseClass()
{
printf("~BaseClass\n");
}
BaseClass(int data)
{
this->data = data;
printf("BaseClass(int data)\n");
}
protected:
int data;
};
class ChildClass : public BaseClass
{
public:
ChildClass():BaseClass(123)
{
printf("ChildClass\n");
}
~ChildClass()
{
printf("~ChildClass\n");
}
void showData()
{
printf("data:%d\n",data);
}
};
int main()
{
ChildClass c;
c.showData();
}
运行结果:
(看,先调用父类的构造函数(你选择的那个),然后调用子类的。数据也没错。
如果放花括号里面呢?
#include <stdio.h>
class BaseClass
{
public:
BaseClass()
{
printf("BaseClass\n");
}
~BaseClass()
{
printf("~BaseClass\n");
}
BaseClass(int data)
{
this->data = data;
printf("BaseClass(int data)\n");
}
protected:
int data;
};
class ChildClass : public BaseClass
{
public:
ChildClass()
{
BaseClass(123);//注意这里,现在把父类构造函数放在这里
printf("ChildClass\n");
}
~ChildClass()
{
printf("~ChildClass\n");
}
void showData()
{
printf("data:%d\n",data);
}
};
int main()
{
ChildClass c;
c.showData();
}
现在得到的结果是:
(程序没有调用我们选择的那个父类构造函数,而是用了父类的默认构造函数,然后数据也没有被正确赋值。)
很奇怪的一点事为什么在调用BaseClass和ChildClass直接输出了BaseClass(int data)和~BaseClass呢?
原来如果我们把父类的构造函数放在子类构造函数的花括号里面的话,就相当于在子类构造函数里声明出一个父类对象(这个父类对象是一个临时变量,作用域只在函数里),在退出函数时,系统会自动释放临时变量,于是就调用了父类的析构函数。
于是我们只是把临时变量的data设为123,而不是把自己的data设为123.。故后面会打印data:-858993460这个乱码,因为它根本就没有被赋值!
3、不要在构造函数和析构函数里面使用虚函数
看看这个例子:
#include <stdio.h>
class BaseClass
{
public:
BaseClass()
{
Func();//构造函数里调用虚函数
}
virtual void Func()
{
printf("Func In BaseClass\n");
}
void CallFunc()
{
Func();//其他函数里调用虚函数
}
};
class ChildClass : public BaseClass
{
public:
void Func()
{
printf("Func In ChildClass\n");
}
};
int main()
{
ChildClass c; //构造函数和析构函数里调用的虚函数是 BaseClass 里的版本
c.CallFunc(); //其他函数里调用的虚函数是 ChildClass 里的重载版本
}
(在构造函数里面调用的虚函数还是本类的原来版本,但在其他函数调用的虚函数可能是子类的重载版本。同样的事情也发生在析构函数里面)
4、拷贝构造函数和重载“=”操作符
#include <stdio.h>
class AClass
{
public:
AClass()
{
}
//拷贝构造函数
AClass(const AClass& c)
{
printf("AClass(const AClass& c)\n");
}
//重载“=”操作符
void operator = (const AClass& c)
{
printf("void operator = (const AClass& c)\n");
}
};
int main()
{
AClass c1;
AClass c2 = c1; //这样调用的是拷贝构造函数
AClass c3;
c3 = c1; //这样调用的是重载“=”操作符
}
运行结果:
(在声明变量的时候赋值是“初始化”,调用拷贝构造函数。在变量声明完成之后再赋值就是“赋值”,调用重载“=”操作符)
5、构造函数的隐式转换与explicit关键字
如果你自己写的构造函数只有一个参数(或者只有一个参数没有默认值),你可以在声明是用“=”去调用这个构造函数,而不是拷贝构造函数。
例子:
#include <stdio.h>
class AClass
{
public:
AClass(int data)
{
printf("AClass, data:%d\n",data);
}
};
int main()
{
AClass c = 123;
}
程序可以正常运行:
这种情况叫做构造函数的隐式转换。
你可以用explicit关键字去禁止它
#include <stdio.h>
class AClass
{
public:
//用explicit关键字禁止构造函数的隐式转换
explicit AClass(int data)
{
printf("AClass, data:%d\n",data);
}
};
int main()
{
AClass c = 123;
}
这样程序就不能编译通过,编译器会报错:
6.将析构函数声明为虚函数(这部分的内容我是直接复制我的上一篇博客《c++中的虚函数与纯虚函数》的,有兴趣的可以去看看)
#include <stdio.h>
class BaseClass
{
public:
virtual ~BaseClass()//析构函数是虚函数
{
printf("Func In BaseClass\n");
}
};
class ChildClass1 : public BaseClass
{
public:
~ChildClass1()
{
printf("Func In ChildClass\n");
}
};
int main()
{
ChildClass1* child = new ChildClass1();
BaseClass* pClass = child;
delete pClass;
return 0;
}
我们会的到结果:
看我们 delete 掉 BaseClass* 类型的 pClass ,因为 pClass 的值是由 ChildClass1* 类型的 child 复制得到的,所以会调用ChildClass1的析构函数(就是说他不只调用了父类的析构函数,也会调用子类的析构函数)
如果不是虚函数呢?
#include <stdio.h>
class BaseClass
{
public:
~BaseClass() //注意:现在不是虚函数
{
printf("Func In BaseClass\n");
}
};
class ChildClass1 : public BaseClass
{
public:
~ChildClass1()
{
printf("Func In ChildClass\n");
}
};
int main()
{
ChildClass1* child = new ChildClass1();
BaseClass* pClass = child;
delete pClass;
return 0;
}
结果只调用了父类的析构函数
有时候我们会在子类里 new 一些变量出来,很多时候都是在子类的析构函数里才把他们 delete 掉。
而如果你 new 一个这样的子类出来,然后将它转型为父类指针 delete 掉,就需要将基类的析构函数声明为虚函数。