网站设计 网站建设 手机网站建设,张掖哪家公司做网站,树莓派wordpress frp,电商平台搭建构思文章目录 3.析构函数析构函数的特点#xff1a; 4.拷贝构造函数拷贝构造的特点#xff1a; 3.析构函数
析构函数与构造函数功能相反#xff0c;析构函数不是完成对对象本身的销毁#xff0c;比如局部对象是存在栈帧的#xff0c;函数结束栈帧销毁#xff0c;他就释放了 4.拷贝构造函数拷贝构造的特点 3.析构函数
析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁比如局部对象是存在栈帧的函数结束栈帧销毁他就释放了不需要我们管C规定对象在销毁时会自动调用析构函数完成对象中资源的清理释放工作。析构函数的功能类比我们之前Stack实现的Destroy功能而像Date没有Destroy其实就是没有资源需要释放所以严格说Date是不需要析构函数的。
析构函数的特点 析构函数名是在类名前加上字符 ~。无参数无返回值。 (这里跟构造类似也不需要加void)一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。对象生命周期结束时系统会自动调用析构函数。跟构造函数类似我们不写编译器自动生成的析构函数对内置类型成员不做处理自定类型成员会调用他的析构函数。还需要注意的是我们显示写析构函数对于自定义类型成员也会调用他的析构也就是说自定义类型成员无论什么情况都会自动调用析构函数。如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数如Date如果默认生成的析构就可以用也就不需要显示写析构如MyQueue但是有资源申请时一定要自己写析构否则会造成资源泄漏如Stack。一个局部域的多个对象C规定后定义的先析构。 #includeiostream
using namespace std;
typedef int STDataType;
class Stack
{public:Stack(int n 4){_a (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr _a){perror(malloc申请空间失败);return;}_capacity n;_top 0;}~Stack(){cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}private:STDataType* _a;size_t _capacity;size_t _top;
};
// 两个Stack实现队列
class MyQueue
{public://编译器默认生成MyQueue的析构函数调用了Stack的析构释放的Stack内部的资源// 显示写析构也会自动调用Stack的析构/*~MyQueue(){}*/private:Stack pushst;Stack popst;
};
int main()
{Stack st;MyQueue mq;return 0;
}对比一下用C和C实现的Stack解决之前括号匹配问题isValid我们发现有了构造函数和析构函数确实方便了很多不会再忘记调用Init和Destory函数了也方便了不少。
#includeiostream
using namespace std;
// 用最新加了构造和析构的C版本Stack实现
bool isValid(const char* s) {Stack st;while (*s){if (*s [ || *s ( || *s {){st.Push(*s);}else{// 右括号比左括号多数量匹配问题if (st.Empty()){return false;}// 栈里面取左括号char top st.Top();st.Pop();// 顺序不匹配if ((*s ] top ! [)|| (*s } top ! {)|| (*s ) top ! ()){return false;}}s;}// 栈为空返回真说明数量都匹配 左括号多右括号少匹配问题return st.Empty();
}
// 用之前C版本Stack实现
bool isValid(const char* s) {ST st;STInit(st);while (*s){// 左括号入栈if (*s ( || *s [ || *s {){STPush(st, *s);}else // 右括号取栈顶左括号尝试匹配{if (STEmpty(st)){STDestroy(st);return false;}char top STTop(st);STPop(st);// 不匹配if ((top ( *s ! ))|| (top { *s ! })|| (top [ *s ! ])){STDestroy(st);return false;}}s;}// 栈不为空说明左括号比右括号多数量不匹配bool ret STEmpty(st);STDestroy(st);return ret;
}
int main()
{cout isValid([()][]) endl;cout isValid([(])[]) endl;return 0;
}/*
关于编译器自动生成的析构函数是否会完成一些事情呢下面的程序我们会看到编译器
生成的默认析构函数对自定类型成员调用它的析构函数。
*/
class Time
{
public:~Time(){cout ~Time() endl;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year 1970;int _month 1;int _day 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}/*程序运行结束后输出~Time()在main方法中根本没有直接创建Time类的对象为什么最后会调用Time类的析构函数因为main方法中创建了Date对象d而d中包含4个成员变量其中_year, _month, _day三个是内置类型成员销毁时不需要资源清理最后系统直接将其内存回收即可而_t是Time类对象所以在d销毁时要将其内部包含的Time类的_t对象销毁所以要调用Time类的析构函数。但是main函数中不能直接调用Time类的析构函数实际要释放的是Date类对象所以编译器会调用Date类的析构函数而Date没有显式提供则编译器会给Date类生成一个默认的析构函数目的是在其内部调用Time类的析构函数即当Date对象销毁时要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数而是显式调用编译器为Date类生成的默认析构函数注意创建哪个类的对象则调用该类的析构函数销毁那个类的对象则调用该类的析构函数
*//*
如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如Date类
有资源申请时一定要写否则会造成资源泄漏比如Stack类。
*/注意 一般情况下有动态申请的资源就需要显示所写的析构函数来释放资源。 例如栈需要写析构 没有动态申请的资源不需要写析构函数。因为没有资源需要释放。 例如 class Data{
private:int _year;int _month;int _day;int _arr[100];
};需要释放资源的成员都是自定义类型不需要写析构函数。 例如 class MyQue{
private:Stack _pushst;Stack _popst;
};因为默认生成的构造会自动调用默认构造函数 默认生成的析构会自动调用默认析构函数 4.拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用且任何额外的参数都有默认值则此构造函数也叫做拷贝构造函数也就是说拷贝构造是一个特殊的构造函数。
拷贝构造的特点 拷贝构造函数是构造函数的一个重载。拷贝构造函数的第一个参数必须是类类型对象的引用使用传值方式编译器直接报错因为语法逻辑上会引发无穷递归调用。 拷贝构造函数也可以多个参数但是第一个参数必须是类类型对象的引用后面的参数必须有缺省值。C规定自定义类型对象进行拷贝行为必须调用拷贝构造所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。若未显式定义拷贝构造编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝)对自定义类型成员变量会调用他的拷贝构造。像Date这样的类成员变量全是内置类型且没有指向什么资源编译器自动生成的拷贝构造就可以完成需要的拷贝所以不需要我们显示实现拷贝构造。像Stack这样的类虽然也都是内置类型但是_a指向了资源编译器自动生成的拷贝构造完成的值拷贝/浅拷贝不符合我们的需求所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员编译器自动生成的拷贝构造会调用Stack的拷贝构造也不需要我们显示实现MyQueue的拷贝构造。这里还有一个小技巧如果一个类显示实现了析构并释放资源那么他就需要显示写拷贝构造否则就不需要。传值返回会产生一个临时对象调用拷贝构造传值引用返回返回的是返回对象的别名(引用)没有产生拷贝。但是如果返回对象是一个当前函数局部域的局部对象函数结束就销毁了那么使用引用返回是有问题的这时的引用相当于一个野引用类似一个野指针一样。传引用返回可以减少拷贝但是一定要确保返回对象在当前函数结束后还在才能用引用返回。 class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}// d2(d1)//Date(Date d);// 正确写法//拷贝构造函数Date(const Date d){cout Date(Date d) endl;//注意_year d._year;这个里面的_year不是private:里面的int _year;//_year d._year;这个左边的_year是d2的_year也就是this-_year因为this指针是d2也就是d2传给了this//右边的d._year是d也就是d1的_year_year d._year;_month d._month;_day d._day;/*d._year _year;d._month _month;d._day _day;*/}
private:int _year;int _month;int _day;
};class MyQueue
{
private:/*Stack _pushst;Stack _popst;*/
};void func(int i){}void func(Date d) {}int main()
{// 可以不写默认生成的拷贝构造就可以用Date d1(2023, 4, 25);Date d2(d1);//Data(Data d)里面的d是d1的别名//this指针是d2也就是d2传给了this//内置类型直接拷贝void func(int i);//直接把4个字节的10拷贝给ifunc(10);//自定义类型的拷贝规定了要定义拷贝构造去拷贝//void func(Date d){}会先调用Date(const Date d);然后进入void func(Date d) //如果Date(const Date d);改成了Date(Date d);那么就会出现无限递归编译器会报错func(d1);// 必须自己实现实现深拷贝/*Stack st1;Stack st2(st1);*/return 0;
}警惕无穷递归
class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}Date(const Date d);// 正确写法//Date(const Date d) // 错误写法编译报错会引发无穷递归//{// _year d._year;// _month d._month;// _day d._day;//}private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2(d1);return 0;
}1)内置类型成员完成值拷贝/浅拷贝 2)自定义类型成员会调用它的拷贝构造 自定义类型指向浅拷贝会出现两个问题 析构两次报错 一个函数修改会影响另一个函数 Data和MyQueue都不需要写。因为MyQueue里面会调用Stack而Stack需要自己实现。Stack的实现和MyQueue无关。 Stack需要自己实现 class Stack
{
public:Stack(int capacity 4){cout Stack() endl;_a (int*)malloc(sizeof(int) * capacity);if (nullptr _a){perror(malloc申请空间失败);return;}_capacity capacity; _top 0;}// st2(st1)Stack(const Stack st){_a (int*)malloc(sizeof(int) * st._capacity);if (nullptr _a){perror(malloc申请空间失败);return;}memcpy(_a, st._a, sizeof(int) * st._top);_top st._top;_capacity st._capacity;}~Stack(){cout ~Stack() endl;free(_a);_a nullptr;_capacity _top 0;}private:int* _a nullptr;int _top 0;int _capacity;
};class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}private:int _year;int _month;int _day;
};class MyQueue
{
private:Stack _pushst;Stack _popst;
};int main()
{//1):内置类型成员完成值拷贝/浅拷贝// 可以不写默认生成的拷贝构造就可以用Date d1(2023, 4, 25);Date d2(d1);//Data(Data d)里面的d是d1的别名//this指针是d2也就是d2传给了this//2);自定义类型成员会调用它的拷贝构造//如果只传值那么就会导致两个函数指向了同一个空间析构函数调用的话就崩了// 而且就算不析构函数也会出问题。比如给其中一个函数赋值会影响另一个函数// 所以必须调用拷贝构造函数// 必须自己实现拷贝构造函数实现深拷贝//栈后进先出后创建的先析构st2先析构st1后析构//添加Stack(const Stack st);前会报错因为st1和st2的析构函数指向了同一个空间而一个空间无法释放两次//添加Stack(const Stack st);后不报错了//Stack(const Stack st);就是我们自己实现的深拷贝Stack st1;Stack st2(st1);return 0;
}注意 在编译器生成的默认拷贝构造函数中内置类型是按照字节方式直接拷贝的而自定义类型是调用其拷贝构造函数完成拷贝的。 为了提高程序效率一般对象传参时尽量使用引用类型返回时根据实际场景能用引用尽量使用引用。
//这个采用引用返回是可以的因为采用引用返回可以减少拷贝而且函数结束后返回的值是没被销毁的
Stack func1(){static Stack st;return st;
}
//这个采用引用返回是不可以的因为函数结束后返回的值是被销毁了
Stack func2(){Stack st;return st;
}int main(){func1();func2();return 0;
}传引用返回要谨慎传值引用没事
Stack Func()
{static Stack st;//改成Stack st;就不行因为Stack st;在Func()结束后就销毁了就会导致拷贝构造传值错误st.Push(1);st.Push(2);st.Push(3);//...return st;
}int main()
{Stack ret Func();cout ret.Top() endl;return 0;
}class Date
{
public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}// Date d2(d1);//是拷贝构造/*Date(const Date d){_year d._year;_month d._month;_day d._day;}*/// 不是拷贝构造就是一个普通构造//Date(Date* p)//{// _year p-_year;// _month p-_month;// _day p-_day;//}//析构函数~Date(){cout ~Date() endl;}
private:int _year;int _month;int _day;
};typedef int STDataType;
class Stack
{
public:Stack(int n 4){_a (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr _a){perror(malloc申请空间失败);return;}_capacity n;_top 0;}void Push(STDataType x){if (_top _capacity){int newcapacity _capacity * 2;STDataType* tmp (STDataType*)realloc(_a, newcapacity *sizeof(STDataType));if (tmp NULL){perror(realloc fail);return;}_a tmp;_capacity newcapacity;}_a[_top] x;}STDataType Top(){assert(_top 0);return _a[_top - 1];}// st2(st1)Stack(const Stack st){_a (STDataType*)malloc(sizeof(STDataType) * st._capacity);if (nullptr _a){perror(malloc申请空间失败);return;}memcpy(_a, st._a, sizeof(STDataType) * st._top);_top st._top;_capacity st._capacity;}~Stack(){cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}private:STDataType* _a;size_t _capacity;size_t _top;
};class MyQueue
{
public:
private:Stack pushst;Stack popst;
};void Func(Stack st){}void Func(int x){}Date f()
{Date ret;//...return ret;
}int main()
{Date d1(2024, 8, 9);//都是拷贝构造//自动生成的拷贝构造对内置类型成员变量会完成值拷贝 / 浅拷贝(一个字节一个字节的拷贝)Date d2(d1);Date d4 d1;Date d5(f());Date d6 f();//Satck不可以浅拷贝因为Stack这里_a是一个指针直接浅拷贝会导致两个指针指向同一块空间析构就会崩溃Stack st1(10);Stack st2(st1);Func(st1);Func(1);MyQueue m1;MyQueue m2(m1);return 0;
}这里就不调用拷贝构造了因为这里是引用返回不是传值返回 传值返回返回的是值的拷贝所以要调用拷贝构造 引用返回返回的不是值的拷贝返回的是它的别名所以不调用拷贝构造 Date operator(const Date d)//返回的是*this这个对象的别名*this是d4
{if (this ! d)//以预防d1 d1;的情况{_year d._year;_month d._month;_day d._day;}return *this;
}