做软件下载网站违法吗,本地wordpress搬家,发帖效果好的网站,临河做网站目录
类的默认成员函数#xff1a;
#xff08;一#xff09;构造函数
#xff08;二#xff09;析构函数
#xff08;三#xff09;拷贝构造函数 类的默认成员函数#xff1a;
类里面有6个特殊的成员函数分别包揽不同的功能; #xff08;一#xff09;构造函数…目录
类的默认成员函数
一构造函数
二析构函数
三拷贝构造函数 类的默认成员函数
类里面有6个特殊的成员函数分别包揽不同的功能; 一构造函数 说明C把类型分成内置类型(基本类型)和⾃定义类型。内置类型就是语⾔提供的原⽣数据类型如int/char/double/指针等⾃定义类型就是我们使⽤class/struct等关键字⾃⼰定义的类型。 构造函数的特点
1自动调用在类实例化对象的时候会自动的调用构造函数。
2构造函数没有返回值啥都不写而不是说写void名字和当前类的名字相同。
3构造函数可以分为三种1.没有形参的构造2.全缺省参数的构造3.自动生成的构造编译器默认生成的是无参构造。他们三个只能存在一个。如果类中没有显式定义构造函数则C编译器会⾃动⽣成⼀个⽆参的默认构造函数⼀旦⽤⼾显式定义编译器将不再⽣成。 因为在调用的时候不需要传递任何的参数所以构造函数总结下来可以称之为0实参构造函数。 自己写了构造函数编译器不再生成。
class Date {
private:int _year;int _month;int _day;public:Date(int year 1999, int month 01, int day 01) // 自己定义的全缺省构造函数{_year year;_month month;_day day;}void Print(){cout this-_year / this-_month / this-_day endl;}};int main()
{Date d1(2024, 9, 23);Date d2;cout 传参了 endl;d1.Print();cout 没有传参 endl;d2.Print();return 0;
} 去掉自己写的构造函数的时候使用的是编译器的默认构造
class Date {
private:int _year;int _month;int _day;public://Date(int year 1999, int month 01, int day 01) // 自己定义的全缺省构造函数//{// _year year;// _month month;// _day day;//}void Print(){cout this-_year / this-_month / this-_day endl;}};int main()
{//Date d1(2024, 9, 23);Date d1; Date d2;cout 传参了 endl;d1.Print();cout 没有传参 endl;d2.Print();return 0;
}这里可以看到编译器初始化成员变量是随机值。 因为c没有明确规定构造函数的初始化规则所以不同的编译器会有不同的初始化规则在vs上可能会初始化为随机值但是在其他编译器上可能就不一样构造函数中编译器对内置类型是没有确定处理的。 同样一段代码我们放到devc中执行的结果编译器的初始化结果和vs是不一样的。 tips正因为默认的构造函数初始化打击不确定的所以大多数构造函数都是需要自己写的。
ps想要redpanda dev编译器的可以私我给发。
(4)构造函数支持函数重载
我们可以写着两个构造函数无参构造和全缺省构造函数但是会产生调用歧义。
class Date {
private:int _year;int _month;int _day;public:Date(int year 1999, int month 01, int day 01) // 自己定义的全缺省构造函数{_year year;_month month;_day day;}// 自己定义的无参数的默认构造函数//Date() //{// _year 1999;// _month 07;// _day 12;//}// 自己定义的带参默认构造//Date(int year, int month, int day)//{// _year year;// _month month;// _day day;//}void Print(){cout this-_year / this-_month / this-_day endl;}};int main()
{Date d1; Date d2(2024,2,3);cout 传参了 endl;cout 没有传参 endl;d2.Print();return 0;
}
还有就是在创建对象调用的时候是无参构造就不需要给对象传参了是有参数构造的必须要传参不然编译器会报错的。
5为了更深层次的理解构造函数我们使用C来实现一个栈的初始化和压栈操作
#include stdlib.h
#include iostream
using namespace std;
class stack {
private:int _top;// 栈顶int _capacity; // 数组空间的大小int* _arr; // 使用动态数组来实现栈public:stack(int n 4) // 实现构造函数和init函数差不多{_top 0;_capacity 4;_arr (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 16个字节的空间}// 压栈入数据void push(int x){// 判断栈是否满了if (_top _capacity) // 如果栈满了就重新开空间{int newcapacity _capacity * 2;int* tmp (int*)realloc(_arr, sizeof(int) * newcapacity);if (tmp nullptr){cout realloc fail! endl;return;}_capacity newcapacity;_arr tmp;}_arr[_top] x;_top;}
};int main()
{stack st;st.push(1);st.push(2);st.push(3);st.push(4);st.push(5);return 0;
}
可以看到我们写的栈的初始化函数就是构造函数我们没有手动调用它在程序执行过程中自动调用了。
二析构函数
和构造函数相反对应的是析构函数它们可以说是相辅相成。 C规定对象在销毁时会⾃动调⽤析构函数完成对象中资源的清理释放⼯作。 析构函数的特性
1函数名和对象名一样没有返回值没有参数定义时在函数名前面加~
2同构造函数一样一个类只能存在一个析构函数构造函数在创建对象时调用析构函数在对象生命周期结束的时候调用。
class Date {
private:int _year;int _month;int _day;public:Date(int year 1999, int month 01, int day 01) // 自己定义的全缺省构造函数{_year year;_month month;_day day;}// 自己写的析构函数~Date(){cout 这里调用了析构函数~Date() endl;}void Print(){cout this-_year / this-_month / this-_day endl;}};int main()
{Date d1;// 这里定义的两个对象没有显示的调用析构函数如d1.Date().Date d2;return 0;
}
step1d1和d2两个对象当代码执行到return 0这句代码的时候生命周期结束我们在这句代码处打一个断点进行调试。 step2我们接下来对代码进行逐步调试发现代码直接跳到了我们刚自己实现的析构函数的位置 step3程序继续执行执行完析构函数内的程序代码之后打印结束程序结束。 在调用析构函数的时候有几个对象就析构几次可以看到我们创建了两个析构函数打印了两次。因为在main的函数栈帧中执行规则和数据结构的栈的执行规则差不多都是后进先出所以在析构多个对象的时候遵循先定义的后析构也可以理解为生命周期先结束的最后析构。
3跟构造函数类似编译器默认⽣成的析构函数对内置类型成员不做处理自定义类型成员会调⽤他的析构函数。
4还需要注意的是我们显⽰写析构函数对于⾃定义类型成员也会调⽤他的析构也就是说⾃定义类型成员⽆论什么情况都会⾃动调⽤析构函数 比如说我们定义了一个类里面存在另外一个类实例化的对象我们在另一个类中实现了析构函数在定义这个类实例化的对象的时候这个实例化的对象生命周期结束的时候会去调用另一个类的析构函数。 5没有申请资源时析构函数可以不写有资源申请时⼀定要⾃⼰写析构否则会造成资源泄漏。 在上面我面我们定义了一个栈的类做构造函数的例子我们在写的时候申请了空间但是我们没有将空间释放掉也没有写析构函数而编译器自己默认生成的析构函数只会清理内置类型的空间所以其实已经造成了内存泄漏了最终泄露的这块内存由操作系统回收。 我们给它加上析构函数将开辟在堆上的空间释放掉。
#include cstdlib
#include stdlib.h
#include iostreamusing namespace std;class stack {
private:int _top;// 栈顶int _capacity; // 数组空间的大小int* _arr; // 使用动态数组来实现栈public:stack(int n 4) // 实现构造函数和init函数差不多{_top 0;_capacity 4;_arr (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 16个字节的空间}// 压栈入数据void push(int x){// 判断栈是否满了if (_top _capacity) // 如果栈满了就重新开空间{int newcapacity _capacity * 2;int* tmp (int*)realloc(_arr, sizeof(int) * newcapacity);if (tmp nullptr){cout realloc fail! endl;return;}_capacity newcapacity;_arr tmp;}_arr[_top] x;_top;}// 自己写的析构函数~stack(){free(_arr);_arr nullptr;_capacity _top 0;}
};int main()
{stack st;return 0;}
当然如果可以也可以在析构函数里面申请空间除非你有病。
三拷贝构造函数
需要理解的几个点
1拷贝构造是构造函数的重载他和构造函数一样都是为了初始化一个对象。构造函数是初始化当前对象拷贝构造是使用另一个函数来初始化当前对象。 2拷⻉构造函数的第⼀个参数必须是类类型对象的引⽤使⽤传值⽅式编译器直接报错因为语法逻辑上会引发⽆穷递归调⽤。拷⻉构造函数也可以多个参数但是第⼀个参数必须是类类型对象的引⽤后⾯的参数必须有缺省值。 假设不是类类型的对象引用——引发无穷递归
class Date
{
public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;} // 编译报错error C2652 : “Date”: ⾮法的复制构造函数: 第⼀个参数不应是“Date”//Date(Date d)Date(const Date d){_year d._year;_month d._month;_day d._day;} Date(Date d) // 这里语法上是不允许的,只是为了举例。{_year d._year;_month d._month;_day d._day;} void Print(){cout _year - _month - _day endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2(d1);return 0;
} tips:为了避免发生被拷贝对象成员被修改的情况在拷贝构造函数的参数前面加一个const限定。如在上面的日期类的拷贝构造函数中
Date(const Date d) //这里加一个const修饰能够避免源对象d1被修改
{_year d._year;_month d._month;_day d._day;
}int main()
{Date d1;Date d2(d1);return 0;
} 3C规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造⾃定义类型传值传参和传值返回都会调⽤拷⻉构造完成 class Date
{
public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;} Date(const Date d) //这里加一个const修饰能够避免源对象{_year d._year;_month d._month;_day d._day;} void Print(){cout _year - _month - _day endl;}
private:int _year;int _month;int _day;
};void func(Date d)
{d.Print();
}int main()
{Date d1;Date d2(d1);func(d1); // 这里传值传参会调用拷贝构造return 0;
}
对代码进行调试运行过程如图 4若未显式定义拷⻉构造编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉)类似于memcpy函数对⾃定义类型成员变量会调⽤他的拷⻉构造 5如果在定义对象类的时候产生了资源的申请就需要自己写拷贝构造函数同析构函数一样是否有资源的申请决定是否需要自己写拷贝构造函数一般需要自己写析构函数的话就需要自己写拷贝构造函数。
45两点且看下面代码
class stack {
private:int _top;// 栈顶int _capacity; // 数组空间的大小int* _arr; // 使用动态数组来实现栈public:stack(int n 4) // 实现构造函数和init函数差不多{_top 0;_capacity 4;_arr (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 16个字节的空间}// 压栈入数据void push(int x){// 判断栈是否满了if (_top _capacity) // 如果栈满了就重新开空间{int newcapacity _capacity * 2;int* tmp (int*)realloc(_arr, sizeof(int) * newcapacity);if (tmp nullptr){cout realloc fail! endl;return;}_capacity newcapacity;_arr tmp;}_arr[_top] x;_top;}// 自己写的析构函数~stack(){free(_arr);_arr nullptr;_capacity _top 0;}
};int main()
{stack st1;stack st2(st1);//使用st1来初始化st2return 0;}
当我们执行这段代码的时候会发生下列情况 程序挂掉了为什么
我们来调试看一下 对于这种情况我们就需要自己写一个拷贝构造函数了。
class stack {
private:int _top;// 栈顶int _capacity; // 数组空间的大小int* _arr; // 使用动态数组来实现栈public:stack(int n 4) // 实现构造函数和init函数差不多{_top 0;_capacity 4;_arr (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 16个字节的空间}// 压栈入数据void push(int x){// 判断栈是否满了if (_top _capacity) // 如果栈满了就重新开空间{int newcapacity _capacity * 2;int* tmp (int*)realloc(_arr, sizeof(int) * newcapacity);if (tmp nullptr){cout realloc fail! endl;return;}_capacity newcapacity;_arr tmp;}_arr[_top] x;_top;}// 自己写的析构函数~stack(){free(_arr);_arr nullptr;_capacity _top 0;}// 自己写的拷贝构造函数stack(const stack st){_capacity st._capacity;_top st._top;int new_capacity st._capacity;int* tmp (int*)malloc(sizeof(int) * new_capacity);//重新申请一片空间if (tmp nullptr){perror(malloc fail!);return;}_arr tmp;}};
执行代码重新调试看一下 地址不一样了我们重新申请了空间。 在上面的代码调试过程中我们发现使用编译器默认生成的拷贝构造函数不会申请空间它会一个个字节的拷贝拷贝动态数组的时候它拷贝的值动态数组指针变量的地址并没有申请空间这一操作我们将这种拷贝方式称为浅拷贝。而我们自己实现的拷贝构造函数自己申请了空间我们将这种拷贝称为深拷贝。 也可以使用等号的方式调用拷贝构造函数初始化对象在承接上面的代码我们定义一个新的对象如
int main()
{stack st1;stack st2(st1);//使用st1来初始化st2// 也可以使用等号的方式调用拷贝构造函数初始化对象stack st3 st1; return 0;}
这样写不会报错。 6 传值返回会产⽣⼀个临时对象调⽤拷⻉构造产生拷贝传值引⽤返回返回的是返回对象的别名(引⽤)没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象函数结束就销毁了那么使⽤引⽤返回是有问题的这时的引⽤相当于⼀个野引⽤类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉但是⼀定要确保返回对象在当前函数结束后还在才能⽤引⽤返回。 不能返回临时对象返回临时对象是会销毁的里面的各种指针就成了野指针返回的是一个被销毁了的空间所以引用这时候是这块被销毁的空间的别名。