C++修炼之构造函数与析构函数

C++修炼之构造函数与析构函数 默认成员函数上一章中我们谈到如果一个类中什么成员也没有那么这个类就叫作空类。其实这么说是不太严谨的因为一个类不可能什么都没有。当我们定义好一个类不做任何处理时编译器会自动生成以下6个默认成员函数默认成员函数如果用户没有手动实现则编译器会自动生成的成员函数构造函数主要完成初始化工作析构函数主要完成清理工作拷贝构造使用一个同类的对象初始化创建一个对象赋值重载把一个对象赋值给另一个对象取地址重载普通对象取地址操作取地址重载constconst对象取地址操作本章我们将学习两个默认成员函数——构造函数与析构函数。构造函数引例在C语言阶段我们实现栈的数据结构时有一件事很苦恼就是每当创建一个stack对象之前叫作定义一个stack类型的变量后首先得调用它的专属初始化函数StackInit来初始化对象。12345678910111213141516171819typedefintdataOfStackType;typedefstructstack{dataOfStackType* a;inttop;intcapacity;}stack;voidStackInit(stack* ps);//...intmain(){stack s;StackInit(s);//...return0;}这不免让人觉得有点麻烦。在C中构造函数为我们很好的解决了这一问题。构造函数的概念及特性构造函数是一个特殊的成员函数。构造函数虽然叫作构造但是其主要作用并不是开辟空间创建对象而是初始化对象。构造函数之所以特殊是因为相比于其它成员函数它具有如下特性函数名与类名相同无返回值对象实例化时编译器自动调用对应的构造函数构造函数可以重载举例12345678910111213141516171819202122232425classDate{public://无参的构造函数Date(){};//带参的构造函数Date(intyear,intmonth,intday){_year year;_month month;_day day;}private:int_year;int_month;int_day;};voidTestDate(){Date d1;//调用无参构造函数自动调用Date d2(2023, 3, 29);//调用带参构造函数自动调用}特别注意创建对象时编译器会自动调用构造函数若是调用无参构造函数则无需在对象后面使用()。否则会产生歧义编译器无法确定你是在声明函数还是在创建对象。错误示例12//错位示例Date d3();如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成。12345678910111213141516classDate{public://若用户没有显示定义则编译器自动生成。/*Date(int year,int month,int day){_year year;_month month;_day day;}*/private:int_year;int_month;int_day;};默认生成构造函数对内置类型成员不作处理对自定义类型成员会调用它的默认构造函数C把类型分成内置类型基本类型和自定义类型。内置类型就是语言提供的数据类型如int、char、double…自定义类型就是我们使用class、struct、union等自己定义的类型。举例默认构造函数对内置类型12345678910111213141516171819202122classDate{public://此处不对构造函数做显示定义测试默认构造函数/*Date(){}*/voidprint(){cout _year - _month - _day endl;}private:int_year;int_month;int_day;};voidTestDate1(){Date d1;d1.print();}如图所示默认构造函数的确未对内置类型做处理。默认构造函数对自定义类型123456789101112131415161718192021222324252627282930classstack{public://此处对stack构造函数做显示定义stack(){cout stack() endl;_a nullptr;_top _capacity 0;}private:int* _a;int_top;int_capacity;};classqueue{public://此处不对queue构造函数做显示定义测试默认构造函数/*queue(){}*/private://自定义类型成员stack _s;};voidTestQueue(){queue q;}如图所示在创建queue对象时默认构造函数对自定义成员_s做了处理调用了它的默认构造函数stack()。这一波蜜汁操作让很多C使用者感到困惑与不满为什么要针对内置类型和自定义类型做不同的处理呢终于在C11中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在类中声明时可以给默认值举例12345678910111213141516171819classDate{public://...voidprint(){cout _year - _month - _day endl;}private://使用默认值int_year 0;int_month 0;int_day 0;};voidTestDate2(){Date d2;d2.print();}默认值若不对成员变量做处理则使用默认值。无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个举例1234567891011121314151617181920212223242526classDate{public://无参的默认构造函数//Date()//{//}//全缺省的默认构造函数Date(intyear 0,intmonth 0,intday 0){_year year;_month month;_day day;}voidprint(){cout _year - _month - _day endl;}private:int_year 0;int_month 0;int_day 0;};析构函数析构函数与构造函数的特性相似但功能有恰好相反。构造函数是用来初始化对象的析构函数是用来销毁对象的。需要注意的是析构函数并不是对对象本身进行销毁因为局部对象出了作用域会自行销毁由编译器来完成而是在对象销毁时会自动调用析构函数对对象内部的资源做清理例如stack _s中的int* a。同样有了析构函数我们再也不用担心创建对象或定义变量后由于忘记释放内存而造成内存泄漏了。举例12345678910111213141516171819202122232425262728293031323334353637383940classStack{public:Stack(){//...}voidPush(intx){//...}boolEmpty(){// ...}intTop(){//...}voidDestory(){//...}private:// 成员变量int* _a;int_top;int_capacity;};voidTestStack(){Stack s;st.Push(1);st.Push(2);//过去需要手动释放st.Destroy();}析构函数的特性析构函数名是在类名前加上字符~无参数无返回值一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数析构函数不能重载举例123456789101112131415161718192021classDate{public:Date(){cout Date() endl;}~Date(){cout ~Date() endl;}private:int_year 0;int_month 0;int_day 0;};voidTestDate3(){Date d3;//d3生命周期结束时自动调用构造函数}为便于观察我们可以在析构函数内部写点儿东西。编译器生成的默认析构函数对自定类型成员调用它的析构函数举例12345678910111213141516171819202122232425262728293031323334353637classstack{public://此处对stack构造函数做显示定义stack(){cout stack() endl;_a nullptr;_top _capacity 0;}~stack(){cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}private:int* _a;int_top;int_capacity;};classqueue{public://此处不对queue构造函数做显示定义测试默认构造函数/*queue(){}*/private://自定义类型成员stack _s;};voidTestQueue1(){queue q;}这里可能有小伙伴会好奇为什么析构函数不像构造函数那样区分内置类型与自定义类型呢答案是因为内置类型压根不需要我们担心清理工作在其生命周期结束时会自动销毁。而自定义类型需要担心因为自定义类型里可能含有申请资源例如malloc申请内存须手动释放。