产生:
构造函数在对象定义后由编译器自动调用,析构函数在函数调用结束(类的作用域结束时),由编译器自动调动。只有调用构造函数之后,对象才真正的产生。只占内存,不调用构造函数的变量不能称之为一个对象。析构函数只是释放对象所占的外部资源,对象的内存依旧存在,内存的释放需要依赖于函数栈帧的下降。
使用时的注意事项:
系统默认的构造函数和析构函数都是无参函数,构造函数可以传入参数,因此能够进行重载,析构函数不能传参,因而没有重载函数。
对象构造的顺序与对象析构的顺序相反,可以通过构造函数直接初始化成员对象。
构造函数不能自己调用,因为对象一出定义的地方就会存在。析构函数可以自己调用,但一般不要自己调用,因为不管前面有没有调用析构函数,编译器都会自己调用。(特别是占有外部资源的变量,在自己调用析构函数之后,对象所占的外部资源被释放,编译器再次调用时析构函数释放外部资源时就会发生崩溃)。析构函数调用完成之后对象不存在,但内存存在。
特别是拷贝构造函数,会在之后单独介绍。
const int NAME_LEN=20; class CGoods { public: // 默认的构造函数 CGoods() { cout<<this<<endl; cout<<"CGoods constructor"<<endl; mpName = NULL; mAmount = 0; mPrice = 0.0; } CGoods(char *n, int a, float p) { cout<<this<<endl; cout<<"CGoods(char*,int,float)"<<endl; mpName = new char[strlen(n)+1]; strcpy(mpName, n); mAmount = a; mPrice = p; } //拷贝构造函数 CGoods(CGoods &src) { cout<<&src<<" -> "<<this<<endl; cout<<"CGoods copy constructor"<<endl; mpName = new char[strlen(src.mpName)+1]; strcpy(mpName, src.mpName); mAmount = src.mAmount; mPrice = src.mPrice; } //赋值运算符的重载函数 void operator=(CGoods &src) { cout<<&src<<" -> "<<this<<endl; cout<<"operator="<<endl; //防止自赋值 if(this == &src) return; //释放原来的资源 delete []mpName; mpName = NULL; //重新开辟资源 mpName = new char[strlen(src.mpName)+1]; strcpy(mpName, src.mpName); mAmount = src.mAmount; mPrice = src.mPrice; } ~CGoods() { cout<<this<<endl; cout<<"CGoods destructor"<<endl; delete []mpName; mpName = NULL; } void Show(); private: char *mpName; int mAmount; float mPrice; }; void CGoods::Show() { cout<<"name:"<<mpName<<endl; cout<<"amount:"<<mAmount<<endl; cout<<"price:"<<mPrice<<endl; }
以上述代码为例,在main主函数中写入如下代码:
1、栈构造函数
int main() { // good1.CGoods("shangpin1", 100, 20.5) // CGoods::CGoods(&good1, "shangpin1", 100, 20.5) CGoods good1; CGoods good1("shangpin1", 100, 20.5); good1.Show(); cout<<"==============================="<<endl; }
CGoods good1;创建对象good1,调用系统默认的构造函数,经编译器编译后产生this指针,指向对象good1。特别注意,不能将CGoods good1;写成CGoods good1();原因是:C++继承了C的语法,CGoods good1();是一个函数声明,编译器会将good1作为一个函数名来处理。
CGoods good1(“shangpin1”,100,20.5);创建对象good1,调用自定义的三个参数且参数类型相同的构造函数:CGoods(char *n, int a, float p){ }
2、在堆里调用构造函数
int main() { CGoods *pgood2 = new CGoods("shangpin2", 200, 30.6); delete pgood2; pgood2 = NULL; cout<<"==============================="<<endl; }
在堆里调用构造函数时,需注意以下几点:
(1)new的执行主要有两步:一是根据对象的大小开辟内存,二是根据参数的传入方式调用相应的构造函数。
(2)delete pgood2;有两个过程:一是编译器调用析构函数释放额外资源,二是释放内存。
(3)最后注意要将pgood2置空,不能让它指向一块儿已经释放了的内存。
3、对象的类型强转
int main() { CGoods *pgood2 = new CGoods("shangpin2", 200, 30.6); delete (int *)pgood2; pgood2 = NULL; cout<<"==============================="<<endl; }
在2的基础上,先将pgood2强转成(int *)类型再delete,此时编译器将不再调用析构函数,因为析构函数调用的是类类型的对象,对于本例来说,调用的必须是CGoods类型的对象。因此,对对象进行类型强转,在delete时虽然不影响对象内存的释放,但会导致对象的析构函数无法调用。特别是对于占用外部资源的对象,在delete时不要进行类型强转。
4、开辟数组对象
int main() { CGoods *pgood3 = new CGoods[3]; delete []pgood3; pgood3 = NULL; cout<<"==============================="<<endl; }
如上述代码所示,开辟数组对象时,调用默认的构造函数。不能通过CGoods *pgood3 = new CGoods[3]("shangpin3",100,20.1)来开辟数组对象,只有单个对象才能调用自定义的构造函数。此外,不能将delete[]pgoods;写成delete pgoods3;错误的写法不影响内存的释放,但会影响析构函数的调用,析构时只析构数组对象中的第一个对象。
5、拷贝构造函数
int main() { CGoods(CGoods &src) 拷贝构造函数 // CGoods src = good1 -> src.CGoods(good1); // CGoods src = good1 -> //good4.CGoods(good1) -> CGoods::CGoods(&good4, good1) CGoods good4=good1; // => CGoods good4(good1); cout<<"==============================="<<endl; }
编译器生成的拷贝构造函数是浅拷贝,只是把对象内存拷贝一份,并不拷贝对象所占的外部资源。因此,拷贝构造有外部资源(比如用new或malloc开空间)的对象时,需调用自己定义的拷贝构造函数。
拷贝构造函数不能传值,只能传引用,如果传值的话会无限递归地调用拷贝构造函数,陷入死循环。
对于CGoods good4 = good1;编译器会将其编译为CGoods good4(good1);即用good1拷贝构造good4。
6、赋值运算符的重载
int main() { // void operator=(CGoods src); 赋值运算符的重载函数 good4.operator=(good1); good1=good1;//错误,不能进行自赋值 cout<<"==============================="<<endl; }
同拷贝构造函数,编译器生成的赋值函数只是把对象的值拷贝一份,对于占有外部资源的对象,在赋值时需要调用赋值运算符的重载函数。赋值运算符的重载函数,需要注意以下三点:
(1)防止自赋值 (2)释放原来的外部资源 (3)重新开辟资源
赋值运算符的重载函数可以传值,但是一般最好传引用,因为传值的开销比较大。
7、构造函数的其他注意事项
int main() { CGoods good5=CGoods("shangpin4",50,40.7); (1) good5 = CGoods("shangpin5",50,40.7);(2) //good6 = (CGoods)(50);(3) //good7 = (CGoods)("shangpin5",50,40.7); <--> good7 = (CGoods)(40.7); (4) cout<<"==============================="<<endl; }
对于语句(1),用临时对象构造一个同类型的新对象good5时,临时对象不产生,编译器会将其优化为CGoods good5("shangpin4",50,40.7);即直接构造good5。
对于语句(2),具体的过程如下:
1.调用第一个参数为char*,第二个参数为int,第三个参数为float的构造函数显示生成临时对象。
2.调用赋值运算符的重载函数,临时对象给goo5赋值。
3.析构临时对象
可见用临时对象构造一个同类型的已存在的对象时,要以三个函数的调用为代价,因此编程时要尽可能的避免这种写法。
对于语句(3),等价于语句good6 = CGoods(50);语句(3)中(CGoods)(50)是类型强转,即将50强转成CGoods类型的对象,编译器会将其处理为对象的构造,即在所有定义的构造函数中寻找是否有只有一个参数且类型为整型的构造函数,如果有的话,构造对象good6,没有的话,编译器报错。语句(3)编译时会出现错误。
对于语句(4),编译器会将参数作为逗号表达式处理,因此只对40.7进行类型强转。