网站建设工作策划书,网站同城在线哪里做,优化设计电子课本,医疗器械监督管理条例类的六个默认成员函数
在C中#xff0c;有六个默认成员函数#xff0c;它们是编译器在需要的情况下自动生成的成员函数#xff0c;如果你不显式地定义它们#xff0c;编译器会自动提供默认实现。这些默认成员函数包括#xff1a; 默认构造函数 (Default Constructor)中有六个默认成员函数它们是编译器在需要的情况下自动生成的成员函数如果你不显式地定义它们编译器会自动提供默认实现。这些默认成员函数包括 默认构造函数 (Default Constructor) 如果你没有为类显式定义任何构造函数编译器将生成一个无参的默认构造函数。用于创建对象而不需要提供任何参数。例如ClassName obj; 析构函数 (Destructor) 如果你没有为类定义析构函数编译器将生成一个默认的析构函数。用于在对象被销毁时释放资源、进行清理工作等操作。例如当对象超出作用域、被删除或者动态分配的对象被 delete 时。 拷贝构造函数 (Copy Constructor)具有缺陷---浅拷贝 如果你没有为类定义拷贝构造函数编译器将生成一个默认的拷贝构造函数。用于创建一个对象作为另一个对象的副本通常在赋值、传递参数等情况下调用。例如ClassName obj2 obj1; 赋值运算符重载 (Copy Assignment Operator)具有缺陷---浅拷贝 如果你没有为类定义赋值运算符重载函数编译器将生成一个默认的赋值运算符重载函数。用于将一个对象的值复制给另一个对象通常在赋值操作中调用。例如obj2 obj1; 移动构造函数 (Move Constructor) 移动赋值运算符 (Move Assignment Operator)
主要常见的是前四个本篇文章只介绍前四个默认成员函数最后两个有兴趣的小伙伴可以自己去查一下别的资料。
初始化操作 /*1.正常使用Data类初始化*/
#include iostream
using namespace std;
class Data {public:void Init(int year, int month, int day) {_year year;_month month;_day day;}void ShowInfo() {cout _year - _month - _day endl;}private:int _year;int _month;int _day;};
int main() {Data d1;d1.Init(2024, 1, 29);d1.ShowInfo();Data d2;d2.Init(2024, 1, 28);d2.ShowInfo();return 0;} 每当我们定义一个自定义类型创造对象的时候一定需要对其进行初始化操作。既然对于所有的对象都需要进行初始化操作那么每次显示调用Init函数就显得非常冗余。如果每当我们创建一个对象自动对其进行初始化就省去显示调用Init函数的步骤。
构造函数
C引入了一个新的概念构造函数。构造函数是一个特殊的成员函数名字与类名相同创建类类型对象时由编译器自动调用以保证每个数据成员都有一个合适的初始值并且在对象的整个生命周期内只调用一次。 /*2.使用构造函数进行初始化*/
#include iostream
using namespace std;
class Data {public:Data(int year, int month, int day) {_year year;_month month;_day day;}void ShowInfo() {cout _year - _month - _day endl;}private:int _year;int _month;int _day;};
int main() {Data d1(2024,1,29);d1.ShowInfo();Data d2(2024,1,28);d2.ShowInfo();return 0;} 这样我们每创建一个对象编译器就会自动调用对应的构造函数对其进行初始化操作。也就是说构造函数是为了方便我们对实例化对象进行初始化而创造出来的。
构造函数的特性
构造函数是一种特殊的成员函数。它的特性是 函数名与类名相同。 无返回值。 对象实例化时编译器自动调用对应的构造函数。 构造函数可以重载。 /*3.初步介绍构造函数*/
#include iostream
using namespace std;
class Data {
public:Data(){}Data(int year,int month,int day){_yearyear;_monthmonth;_dayday;}void ShowInfo() {cout _year - _month - _day endl;}
private:int _year;int _month;int _day;};
void TestDate(){Data d1;Data d2(2024,1,28);}
int main() {TestDate();return 0;}
探究构造函数的规则---创建对象自动调用构造函数 /*4.探究构造函数的规则---创建对象自动调用构造函数*/
#include iostream
using namespace std;
class Data {
public:Data(){coutData()endl;}Data(int year,int month,int day){coutData(int year,int month,int day)endl;_yearyear;_monthmonth;_dayday;}void ShowInfo() {cout _year - _month - _day endl;}
private:int _year;int _month;int _day;};
void TestDate(){Data d1;Data d2(2024,1,28);d2.ShowInfo();}
int main() {TestDate();return 0;} 当我们创建d1对象时后面不加括号表示调用无参的构造函数。后面带括号括号里带参数表示调用有参的构造函数。创建对象的时候系统会自动调用对应的构造函数对其进行初始化操作。
探究构造函数的规则---编译器默认生成无参构造函数 /*5.探究构造函数的规则---编辑器默认有无参构造函数*/
#include iostream
using namespace std;
class Data {
public:void ShowInfo() {cout _year - _month - _day endl;}
private:int _year;int _month;int _day;};
void TestDate(){Data d1;d1.ShowInfo();}
int main() {TestDate();return 0;} 当我们没有显示编写构造函数的时候创建完对象后我们发现对象内年月日的初始值是0。
如果类中没有显示定义构造函数编译器会自动生成一个无参的默认构造函数但是一旦我们显示定义了构造函数编译器就不会自动生成无参构造函数。
探究构造函数的规则---自己创建有参的构造函数时编辑器不会自动创建无参构造函数 /*6.探究构造函数的规则---自己创建有参的构造函数时编辑器不会自动创建无参构造函数*/
#include iostream
using namespace std;
class Data {
public:
// Data(){// coutData()endl;// }Data(int year,int month,int day){coutData(int year,int month,int day)endl;_yearyear;_monthmonth;_dayday;}void ShowInfo() {cout _year - _month - _day endl;}
private:int _year;int _month;int _day;};
void TestDate(){Data d1;Data d2(2024,1,28);d2.ShowInfo();}
int main() {TestDate();return 0;} 我们显示定义了有参的构造函数此时编译器不会自动生成无参的构造函数。当我们创建对象后面不加括号的时候系统会自动调用无参的构造函数但是类类型中没有无参的构造函数编译器没有生成所以就报错了。解决的办法就是自己再写一个无参的构造函数。
探究构造函数的规则---默认构造函数具体情况---无参全缺省 /*7.探究构造函数的规则---默认构造函数具体情况---无参全缺省*/
#include iostream
using namespace std;
class Data {
public:Data(){coutData()endl;_year1999;_month1;_day1;}Data(int year2024,int month2,int day1){coutData(int year,int month,int day)endl;_yearyear;_monthmonth;_dayday;}void ShowInfo() {cout _year - _month - _day endl;}
private:int _year;int _month;int _day;};
void TestDate(){Data d1;d1.ShowInfo();}
int main() {TestDate();return 0;} 当我们没有显示的定义构造函数时编译器会默认帮我们定义一个无参的构造函数。全缺省的构造函数等价于无参的构造函数。因此不可以同时存在当我们创建对象后面不加括号时系统调用无参的构造函数此时编译器不知道调用无参的构造函数还是全缺省的构造函数。
问题产生的本质是缺省参数导致重载模棱两可可以理解为缺省参数不存在此时含有缺省参数的函数与另一个函数不构成重载即使编译器没有报错。 /*7.缺省参数形成冲突*/
#include iostream
using namespace std;
class Data {public:Data(int year, int month 2, int day 1) {cout Data(int year,int month,int day) endl;_year year;_month month;_day day;}Data(int year) {_year year;_month 1;_day 1;}void ShowInfo() {cout _year - _month - _day endl;}private:int _year;int _month;int _day;};
void TestDate() {Data d1(2024);d1.ShowInfo();}
int main() {TestDate();return 0;} 当我们创建对象传入2024一个参数的时候编译器不知道调用有缺省参数的函数还是没有缺省参数的函数缺省参数导致重载模棱两可可以理解为缺省参数不存在即Data(int year, int month 2, int day 1) 等价于Data(int year) 与下面的 Data(int year)不构成重载即使编译器不会报错。 清理操作 /*8.正常使用栈进行清理工作*/
#include iostream
using namespace std;
typedef int DataType;
class Stack {public:Stack(int capacity 4) {_array (DataType*)malloc(sizeof(DataType) * capacity);_capacity capacity;_size 0;}void CheckCapacity(){if(_capacity_size){int newcapacity_capacity*2;_array(DataType*)realloc(_array,sizeof(DataType)*newcapacity);_capacitynewcapacity;}}void push(DataType data) {CheckCapacity();_array[_size] data;}void pop() {_size--;}void Destroyed() {free(_array);_array NULL;_capacity 0;_size 0;}bool empty() {return _size 0;}DataType top() {return _array[_size - 1];}private:DataType* _array;int _capacity;int _size;};
void TestStack() {Stack st;st.push(1);st.push(2);st.push(3);st.push(4);st.push(5);while (!st.empty()) {cout st.top() endl;st.pop();}st.Destroyed();}
int main() {TestStack();return 0;}
当我们使用创建对象时使用完之后都需要显示调用Destroyed销毁函数进行清理工作防止内存泄漏。既然对于所有的对象都需要进行清理操作那么每次显示调用Destroyed函数就显得非常冗余。如果每当我们创建一个对象对象出了作用域之后自动对其进行销毁就省去显示调用Destroyed函数的步骤。
析构函数 /*9.使用析构函数进行清理工作*/
#include iostream
using namespace std;
typedef int DataType;
class Stack {public:Stack(int capacity 4) {_array (DataType*)malloc(sizeof(DataType) * capacity);_capacity capacity;_size 0;}void CheckCapacity() {if (_capacity _size) {int newcapacity _capacity * 2;_array (DataType*)realloc(_array, sizeof(DataType) * newcapacity);_capacity newcapacity;}}void push(DataType data) {CheckCapacity();_array[_size] data;}void pop() {_size--;}~Stack() {free(_array);_array NULL;_capacity 0;_size 0;}bool empty() {return _size 0;}DataType top() {return _array[_size - 1];}private:DataType* _array;int _capacity;int _size;};
void TestStack() {Stack st;st.push(1);st.push(2);st.push(3);st.push(4);st.push(5);while (!st.empty()) {cout st.top() endl;st.pop();}}
int main() {TestStack();return 0;}
C引入了一个新的概念析构函数。析构函数是一个特殊的成员函数名字与类名相同名字前面添加‘~’类类型对象出作用域时由编译器自动调用。
析构函数的特性
析构函数是特殊的成员函数其特征如下 析构函数名是在类名前加上字符 ~。 无参数无返回值类型。 一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构函数不能重载。 对象生命周期结束时C编译系统系统自动调用析构函数。 /*10.初步介绍析构函数*/
#include iostream
using namespace std;
class Date {public:Date(int year, int month, int day) {cout Date(int year, int month, int day) endl;_year year;_month month;_day day;}void Show() {cout _year - _month - _day endl;}~Date() {cout ~Date() endl;}private:int _year;int _month;int _day;};void Test() {Date d1(2024, 1, 29);d1.Show();}
int main() {Test();return 0;} 拷贝构造 /*11.正常用一个已有的变量拷贝构造一个新变量---内置类型*/
#include iostream
using namespace std;int main() {int a10;int ba;coutbendl;return 0;} 我们用已经存在的a变量去构造新的b变量很明显如果a、b都是内置类型我们很容易理解。但是如果我们希望用一个已经存在的自定义类型去构造新的自定义类型对象应该如何操作 /*12.正常用一个已有的变量拷贝构造一个新变量---自定义类型*/
#include iostream
using namespace std;
class Date {public:Date() {}Date(int year, int month, int day) {_year year;_month month;_day day;}void Show() {cout _year - _month - _day endl;}~Date() {cout ~Date() endl;}void assignment(const Date d) {_year d._year;_month d._month;_day d._day;}private:int _year;int _month;int _day;};void Test() {Date d1(2024, 1, 29);//d2d1?Date d2;d2.assignment(d1);d2.Show();}
int main() {Test();return 0;} 我们可以在类里面定义一个函数函数的参数是自定义类型的引用作用是进行赋值这样我们创建一个新对象后调用这个对象的赋值函数就可以实现我们想要的效果。我们可以发现对于每一个自定义类型来说我们都希望可以这样实现自定义类型 d2d1每个自定义类型都在类里面定义一个函数外部调用会显得特别冗余且外部调用形式是 Date d2; d2.assignment(d1); 先创建对象再进行赋值而我们的期望是创建对象和赋值一体化这两者似乎有所区别。
拷贝构造函数 /*12.使用拷贝构造函数拷贝构造一个新变量---自定义类型*/
#include iostream
using namespace std;
class Date {public:Date() {}Date(int year, int month, int day) {_year year;_month month;_day day;}void Show() {cout _year - _month - _day endl;}~Date() {cout ~Date() endl;}Date(const Date d) {_year d._year;_month d._month;_day d._day;}private:int _year;int _month;int _day;};void Test() {Date d1(2024, 1, 29);//d2d1?Date d2(d1);d2.Show();}
int main() {Test();return 0;} C引入了一个新的概念拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数的特性
拷贝构造函数也是特殊的成员函数其特征如下 拷贝构造函数是构造函数的一个重载形式。 拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。 若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。
探究拷贝构造函数的规则---编译器默认生成拷贝构造函数 /*13.默认生成的拷贝构造函数*/
#include iostream
using namespace std;
class Date {public:Date() {}Date(int year, int month, int day) {_year year;_month month;_day day;}void Show() {cout _year - _month - _day endl;}~Date() {cout ~Date() endl;}private:int _year;int _month;int _day;};void Test() {Date d1(2024, 1, 29);//d2d1?Date d2(d1);d2.Show();}
int main() {Test();return 0;} 即使我们没有显示编写拷贝构造函数我们依旧可以用Date d2(d1);拷贝构造d2对象。因为编译器会自动生成对应的拷贝构造函数。
探究拷贝构造函数的规则---编译器默认生成拷贝构造函数的缺陷浅拷贝 /*14.默认生成的拷贝构造函数---本质*/
#include iostream
using namespace std;
class f1 {public:f1(int x) {p (int*)malloc(sizeof(int));*p x;}void Show() {cout *p endl;cout p endl;}~f1() {if (p) {free(p);}}private:int* p;};void Test() {f1 x1(1);f1 x2(x1);x1.Show();x2.Show();}
int main() {Test();return 0;}
运行上面的代码我们会发现程序崩掉了。如果在28行打一个断点进入调试模式我们发现一直运行到28行处程序都没有发生问题。此时我们得到 我们发现x1的地址和x2的地址是一样的。很容易知道接下来出Test函数后x1和x2对象都需要调用析构函数此时同一个地址会释放两次空间程序就崩了。
编译器默认生成的拷贝构造函数按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。可以理解为浅拷贝是将自定义类型类内成员变量进行简单的等号赋值。即x2.px1.p。此时指针就会指向同一段空间。而我们希望的是指针会新开辟一个空间只是指针的值是相同的空间不是相同的。 /*15.默认生成的拷贝构造函数---本质*/
#include iostream
using namespace std;
class f1 {public:f1(int x) {p (int*)malloc(sizeof(int));*p x;}void Show() {cout p endl;cout *p endl;}~f1() {if (p) {free(p);}}f1(const f1 x) {p (int*)malloc(sizeof(int));*p *x.p;}private:int* p;};void Test() {f1 x1(1);f1 x2(x1);x1.Show();x2.Show();}
int main() {Test();return 0;} 此时我们显示定义拷贝构造函数新开辟空间只是让指针上的值相等。这样就可以达到我们想要的效果指针指向的空间不同而指针上的值是相等的。
探究拷贝构造函数的规则---拷贝构造函数的使用场景编译器优化、返回对象生命周期延长
拷贝构造函数是C中的一个特殊函数用于创建一个新对象并将其初始化为另一个对象的副本。它通常在以下情况下被调用 对象的初始化当你创建一个新对象并将其初始化为另一个对象的副本时拷贝构造函数被调用。 函数参数传递当你将一个对象作为参数传递给一个函数时拷贝构造函数可以被用来创建传递给函数的副本。 函数返回值当函数返回一个对象的副本时拷贝构造函数被用来创建返回的副本。编译器优化可能省略 /*16.拷贝构造函数---的使用场景*/
#include iostream
using namespace std;
class Date {public:Date(int year, int month, int day) {cout Date(int year,int month,int day): this endl;}Date(const Dated) {cout Date(const Dated); this endl;}~Date() {cout ~Date(): this endl;}private:int _year;int _month;int _day;};
Date Test(Date d) {Date temp(d);return temp;}
int main() {Date d1(2024, 1, 29);Date d2 Test(d1);return 0;} Date d1(2024, 1, 29);我们调用构造函数创建对象d1。Test(d1)此时调用Test函数将d1传给形成d这一过程调用拷贝构造函数用已经存在的d1创建并初始化d对象。函数参数传递当你将一个对象作为参数传递给一个函数时拷贝构造函数可以被用来创建传递给函数的副本。在Test函数中显示调用拷贝构造函数用已经存在的d创建并初始化temp对象。对象的初始化当你创建一个新对象并将其初始化为另一个对象的副本时拷贝构造函数被调用。在Test函数作用域结束调用对象d的析构函数。在主函数作用域结束调用temp的析构函数接着调用d2的析构函数。
你可能会产生疑问在Test函数调用结束temp作为函数返回值此时应该还会调用拷贝构造函数。函数返回值当函数返回一个对象的副本时拷贝构造函数被用来创建返回的副本。为何运行代码并没有显示拷贝构造函数被调用。
实际上temp作为返回值此时还会调用拷贝构造函数但是经过编译器优化过这一步的拷贝构造函数被省略掉了。尽管你在 Test 函数中创建了一个 temp 对象并返回它但在实际的编译过程中编译器会进行优化避免调用拷贝构造函数直接将 temp 对象的值传递给 d2。这是一种性能优化可以减少不必要的对象拷贝操作提高程序效率。 我对代码进行调试注意看我的光标的位置我的光标处于主函数中但是析构函数只调用了一次显然这个析构函数是对Test中d对象进行析构而temp和d2对象并没有发生析构。显然temp在Test函数中作为返回值此时temp对象的生命周期得到延伸与d2同步但最后编译器会依照前后顺序先调用temp的析构函数再调用d2的析构函数。虽然拷贝构造函数被省略了但是 temp 对象的生命周期和 d2 对象的初始化完全一致因此 temp 对象的析构函数会在 d2 对象的析构函数之前被调用从而保证资源的正确释放。
运算符重载
运算符重载Operator Overloading是C的一个重要特性允许你为用户自定义的数据类型定义和重新定义运算符的行为。通过运算符重载你可以使用C内置运算符来执行自定义数据类型的操作使代码更具可读性和表现力。
内置类型判断相等很简单例如ab变量都是int类型判断相等只需要ab即可。但是如果我们想要定义自定义类型进行判断是否相等。我们希望也可以像“ab”这样使用对于ab都属于自定义类型此时编译器显然不能像内置类型那样操作。于是我们引入运算符重载的概念使得自定义类型也可以像内置类型那样进行操作。 /*17.运算符重载*/
#include iostream
using namespace std;
class Date {
public:
Date(int year,int month,int day){_yearyear;_monthmonth;_dayday;}
bool operator(const Date d2){return _yeard2._year _monthd2._month _dayd2._day;}
private:int _year;int _month;int _day;};int main() {Date d1(2024,1,29);Date d2(d1);cout(d1d2)endl;return 0;}
当我们使用d1d2这段代码的时候实际上会转化为d1.operator(d2) 此时调用d1对象的operator函数并传入d2对象作为参数。此时d1的地址会作为this指针operator函数会隐藏一个this指针this指针指向的地址是d1的地址_year等价于this-_year_month等价于this-_month,_day等价于this-_day。
赋值运算符重载
如果我们想要以这种形式“ab”对一个自定义类型进行赋值那么我们就需要对“”进行运算符重载。
赋值运算符重载格式
参数类型const T传递引用可以提高传参效率
返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值检测是否自己给自己赋值
返回*this 要符合连续赋值的含义 /*18.赋值运算符重载*/
#include iostream
using namespace std;
class Date {public:Date(int year, int month, int day) {_year year;_month month;_day day;}Date operator(const Dated) {if (this ! d) {_year d._year;_month d._month;_day d._day;}return *this;}void Show() {cout _year - _month - _day endl;}private:int _year;int _month;int _day;};int main() {Date d1(2024, 1, 29);Date d2 d1;d2.Show();return 0;} 实际上当我们没有显示定义赋值运算符重载的时候编译器会生成一个默认的赋值运算符重载以值的方式逐字节拷贝即浅拷贝。我们知道浅拷贝当成员变量是指针的时候p1p2此时指针指向的地址会相同而我们希望的是指针指向的地址不同指针指向地址上的值相同因为往往涉及到指针问题我们都需要显示定义赋值运算符重载将浅拷贝变成深拷贝。
默认赋值运算符重载的缺陷 /*19.默认赋值运算符重载的缺陷*/
#include iostream
using namespace std;
class f1 {public:f1(int x) {p (int*)malloc(sizeof(int));*p x;}void Show() {cout *p endl;cout p endl;}~f1() {if (p) {free(p);}}private:int* p;};void Test() {f1 x1(1);f1 x2 x1;x1.Show();x2.Show();}
int main() {Test();return 0;}
x1x2对象中p成员指向的地址是相同的当x1x2对象出作用域时会对同一块地址调用两次析构函数此时程序就会崩掉。此时就需要显示定义赋值运算符重载将浅拷贝变成深拷贝。
前置和后置的重载 /*20.前置和后置的重载(代码逻辑有缺陷)*/
#include iostream
using namespace std;
class Date {public:Date() {}Date(int year, int month, int day) {_year year;_month month;_day day;}Date operator() {_day 1;return *this;}Date operator(int) {Date temp(*this);_day;return temp;}void Show() {cout _year - _month - _day endl;}private:int _year;int _month;int _day;};
void Test() {Date d;Date d1(2024, 1, 29);d d1;d.Show();d1.Show();d d1;d.Show();d1.Show();}
int main() {Test();return 0;} Date operator(int) {Date temp(*this);_day;return temp;}
这是后置为了区分前置和后置规定后置参数中写一个int用来表示后置。
前置先对day进行1操作然后把处理过的自定义类型返回此时返回引用用引用可以提高效率因为不用引用还需要进行一次拷贝构造操作用引用就不需要进行拷贝构造操作。
后缀返回的值是前的值因此我们先拷贝构造一个副本对本体的day进行操作返回副本的值注意此时不能够返回引用因为副本出了作用域就消失了所以只能不传引用。
d1会被转化为d1.operator(int)从而调用后置的函数。d1会被转化为d1.operator()从而调用前置的函数。
结尾 最后感谢您阅读我的文章希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点请随时在评论区留言。 同时不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中我将继续探讨这个话题的不同方面为您呈现更多深度和见解。 谢谢您的支持期待与您在下一篇文章中再次相遇