一般产地证去哪个网站做,网站开发怎么做才有利于seo,温州企业建站系统,wordpress 视频收费目录
一、前言
二、拷贝构造函数
#x1f4a6;拷贝构造函数概念
#x1f4a6;拷贝构造函数特性
#x1f34e; 解释特性2#xff1a;拷贝构造函数的参数只有一个且必须使用引用传参#xff0c;使用传值方式会引发无穷递归调用
#x1f350;解释特性3#xff1a;…目录
一、前言
二、拷贝构造函数
拷贝构造函数概念
拷贝构造函数特性 解释特性2拷贝构造函数的参数只有一个且必须使用引用传参使用传值方式会引发无穷递归调用
解释特性3若未显示定义系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝我们叫做浅拷贝或者值拷贝
产生拷贝构造的三种形式 拷贝构造的总结
三、赋值运算符重载
运算符重载
赋值运算符重载
四、共勉 一、前言 在我们前面学习的类中我们会定义成员变量和成员函数这些我们自己定义的函数都是普通的成员函数但是如若我们定义的类里什么也没有呢是真的里面啥也没吗如下 class Date {}; 如果一个类中什么成员都没有简称为空类。空类中什么都没有吗并不是的任何一个类在我们不写的情况下都会自动生成6个默认成员函数。 【默认成员函数概念】用户没有显式实现编译器会生成的成员函数称为默认成员函数 ⭐其中上次的博客已经详细的讲解了构造函数析构函数的使用方法所以本次博客将继续深度的讲解拷贝构造和赋值运算符的重载问题。⭐ 二、拷贝构造函数
拷贝构造函数概念 我们在创建对象时可否创建一个与另一个对象一摸一样的新对象呢 int main()
{Date d1(2022, 5, 18);// 将 d1 的 数据拷贝给 d2 让 d2 进行初始化Date d2(d1);return 0;
} 能否让d2的值跟d1一样呢也就是说我拿d1去初始化d2此时调用的函数就是拷贝构造函数。 拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存在的类 类型对象创建新对象时由编译器自动调用 拷贝构造函数特性 拷贝构造函数也是特殊的成员函数其特征如下 拷贝构造函数是构造函数的一个重载形式。拷贝构造函数的参数只有一个且必须使用引用传参使用传值方式会引发无穷递归调用。若未显示定义系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝我们叫做浅拷贝或者值拷贝。 如下即为拷贝构造函数 Date(Date d)
{_year d._year;_month d._month;_day d._day;
} 解释特性2拷贝构造函数的参数只有一个且必须使用引用传参使用传值方式会引发无穷递归调用 为什么传值传参会引发无穷递归呢 我们先举一个普通的func函数作为例子 //传值传参
void Func(Date d)
{}
int main()
{Date d1(2022, 5, 18);Func(d1);return 0;
} 此函数调用传参是传值传参。在C语言中把实参传给形参是把实参的值拷贝给形参而我的实参d1是自定义类型的需要调用拷贝构造传值传参是要调用拷贝构造的但是我如果不想调用拷贝构造呢就需要引用传参因为此时d就是d1的别名 void Func(Date d) {} 此时再回到我们刚才的例子如若我不传引用传参就会疯狂的调用拷贝构造 Date(Date d)
{_year d._year;_month d._month;_day d._day;
} 为了避免出现无限递归调用拷贝构造所以要加上引用加上引用后d就是d1的别名不存在拷贝构造了。同类型的传值传参是要调用拷贝构造的 Date(const Date d) {}
//最好加上const对d形成保护 解释特性3若未显示定义系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝我们叫做浅拷贝或者值拷贝 看如下代码 class Date
{
public:
//构造函数Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}
//拷贝构造函数/*Date(const 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;
};
void Func(Date d)
{d.Print();
}
int main()
{Date d1(2023, 10, 23);Date d2(d1);Func(d1);d2.Print();
} 运行效果图展示 为什么我这里没有写拷贝构造函数它也会自动完成拷贝构造呢由此我们要深思拷贝构造与构造和析构是不一样的构造和析构都是针对自定义类型才会处理而内置类型不会处理而默认拷贝构造针对内置类型的成员会完成值拷贝浅拷贝也就是像把d1的内置类型成员按字节拷贝给d2。 由此得知对于日期类这种内置类型的成员是不需要我们写拷贝构造的那是不是所有的类都不需要我们写拷贝构造呢来看下面的例子 栈类需要深拷贝的类 class Stack
{
public://构造函数Stack(int capacity 10){_a (int*)malloc(sizeof(int) * capacity);assert(_a);_top 0;_capacity capacity;}
//不写拷贝构造编译器调用默认拷贝构造/*Stack(const Stack st){_a st._a;_top st._top;_capacity st._capacity;}*///析构函数~Stack(){cout ~Stack(): this endl;free(_a);_a nullptr;_top _capacity 0;}
private:int* _a;int _top;int _capacity;
};
int main()
{Stack st1(10);Stack st2(st1);
} 运行结果出现报错 我们通过调试看到运行崩溃了可见栈的拷贝构造函数不能像日期类一样不写而让编译器去调用默认拷贝构造就是按照日期类的模式写了拷贝构造也会出错因为此时的st1指针和st2指针指向的就是同一块空间通过调试可以看出 st1和st2指向同一块空间会引发一个巨大的问题析构函数那出错因为我st2会先析构析构完后我st1再析构不过我st1指向的空间已经被st2析构过了因为它俩指向同一块空间同一块空间我释放两次就会有问题。 出了析构存在问题增删查改那也会有问题这个后续会谈到。 其实刚才写的栈的拷贝构造就是浅拷贝真正栈的拷贝构造应该用深拷贝来完成此部分内容我等后续会专门出一篇博文详解这里大家先简单接触下。 综上我们可以得知浅拷贝针对日期类这种是没有问题的而类的成员若是指向一块空间的话就不能用浅拷贝了。 字符串类需要深拷贝的类 class MyString {
public:// 默认构造函数MyString(const char* str winter) {_str (char*)malloc(sizeof(char)*(strlen(str) 1));if (_str nullptr){perror(malloc fail!);exit(-1);}strcpy(_str, str);}// 析构函数~MyString() {cout ~String() endl;free(_str);}void MyPrintf(){cout _str endl;//printf(%s\n, _str);}private:char* _str;
};int main()
{MyString s1(hello C);MyString s2(s1);s1.MyPrintf();cout endl;s2.MyPrintf();cout endl;
} 如图指向了同一块空间 那么会引发什么问题呢会导致 _str 指向的空间被释放两次引发程序崩溃。 加入深入拷贝构造函数 class MyString {
public:// 默认构造函数MyString(const char* str winter) {_str (char*)malloc(sizeof(char)*(strlen(str) 1));if (_str nullptr){perror(malloc fail!);exit(-1);}strcpy(_str, str);}// 析构函数~MyString() {cout ~String() endl;free(_str);}//拷贝构造函数MyString(const MyString s) {// 给新对象申请一段和原对象一样大小的空间_str (char*)malloc(sizeof(char) * (strlen(s._str) 1));if (_str nullptr){perror(malloc fail!);exit(-1);}// 把原对象的数据一一拷贝给新对象strcpy(_str, s._str);}void MyPrintf(){cout _str endl;//printf(%s\n, _str);}private:char* _str;
};int main()
{MyString s1(hello C);MyString s2(s1);s1.MyPrintf();cout endl;s2.MyPrintf();cout endl;
} ⭐总结 1️⃣你可以观察在当前这这个类中是否存在显式的析构函数若是存在的话表示当前这个类涉及资源管理了【资源管理指得就是去堆中申请空间了】此时你一定要自己去是实现拷贝构造以达到一个深拷贝若是不涉及资源管理的话直接使用编译器自动生成的进行浅拷贝就可以了 2️⃣ 像Date日期类这种只存在【年】、【月】、【日】这种内置类型的浅拷贝就可以了像是复杂一些的例如链表、二叉树、哈希表这些都会涉及资源的管理就要考虑到深拷贝了 产生拷贝构造的三种形式 深刻理解了拷贝构造之后我们再来看看产生拷贝构造的三种形式 1.当用类的对象去初始化同类的另一个对象时
Date d1;
Date d2(d1);
Date d3 d2; //也会调用拷贝构造在实例化对象d2和d3的时候都去调用了拷贝构造最后它们初始化后的结果都是一样的 2.当函数的形参是类的对象调用函数进行形参和实参结合时
void func(Date d) //形参是类的对象
{d.Print();
}int main(void)
{Date d1;func(d1); //传参引发拷贝构造return 0;
}函数func()的形参是类的对象此时在外界调用这个函数并传入对应的参数时就会引发拷贝构造 3.当函数的返回值是对象函数执行完成返回调用者时
Date func2()
{Date d(2023, 3, 24);return d;
}int main(void)
{Date d1 func2();d1.Print();return 0;
}可以看到这一种方式也会引发拷贝构造当函数内部返回一个Date类的对象时此时外界再使用Date类型的对象去接收时就会引发拷贝构造。 拷贝构造的总结 总结 1. 拷贝构造算是六大默认成员函数中较难理解的了。主要就是要理清【内置类型】和【自定义类型】是否会调用拷贝构造的机制。还有在实现这个拷贝构造时要主要的两点一个就是在形参部分要进行引用接收否则会造成无穷递归的现象还有一点就是在前面加上const进行修饰可以防止误操作和权限放大的问题2. 一般的类自己生成拷贝构造就够用了只有像Stack这样自己直接管理资源的类需要自己实现深拷贝。 三、赋值运算符重载
运算符重载 如下的日期类 class Date
{
public://构造函数Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}void Print(){cout _year - _month - _day endl;}
private:int _year;int _month;int _day;
}; 我能否按照如下的方式对日期类的对象进行大小比较呢 很明显是不可以的从波浪线提示的警告就能看出。我们都清楚内置类型是可以直接进行比较的但是自定义类型是不能直接通过上述的运算符进行比较的为了能够让自定义类型使用各种运算符于是就提出了运算符重载的规则。 C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为关键字operator后面接需要重载的运算符符号。函数参数运算符操作数函数返回值运算符运算后结果函数原型返回值类型 operator操作符(参数列表)注意运算符重载函数的参数由运算符决定比如运算符应有两个参数双操作数运算符就有两个参数单操作数运算符或--就有一个参数 就比如我现在写一个日期比较相等的运算符重载传值传参会引发拷贝构造所以要加上引用最好再加上const以便于提高效率 bool operator(const Date d1, const Date d2) //避免传值传参调用拷贝构造
{return d1._year d2._year d1._month d2._month d1._day d2._day;
} 仔细观察我的截图毕竟我都写好了运算符重载可是我调用运算符重载的方式怎么还能跟调用普通函数一样呢与其这样还取名运算符重载又有何意义所以真正的调用应该如下 调用的时候直接和内置类型进行运算符操作那样编译器会自动处理成调用运算符重载的样子 注意上述的运算符重载就算完成了吗当然不是按理说我们要把运算符重载函数放成类里的成员函数。 并且这里的参数也不能像如上的方式写 如若直接把运算符重载函数放到类里编译器会报错运算符函数的参数太多。报错的原因就在于成员函数存在隐含的this指针。 这也就意味着实际的参数有3个因此我们要少写一个参数 bool operator(const Date d)//编译器会处理成 bool operator(Date* const this, const Date d)
{return _year d._year _month d._month _day d._day;
} 并且我在调用成员函数的时候也要做出改变 if (d1.operator(d2))
{cout endl;
} 和刚才一样为了凸显出运算符重载的意义我们调用的时候可以直接像内置类型一样操作运算符因为编译器会帮我们处理 if (d1 d2)//编译器会处理成对应重载运算符调用if (d1.operator(d2))或者if (d1.operator(d1, d2))
{cout endl;
} 现在我们来写一个日期类的比较大小来练练手 class Date
{
public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}//日期比较大小bool operator(const Date d){if (_year d._year ||_year d._year _month d._month ||_year d._year _month d._month _day d._day)return false;elsereturn true;}void Printf(){cout _year - _month - _day endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2023, 10, 23);cout d1 的日期为 endl;d1.Printf();Date d2(2022, 10, 22);cout d2 的日期为 endl;d2.Printf();cout endl;cout d1与d2日期的大小比较;if (d1 d2){cout endl;}else{cout endl;}Date d3(d1);d3 d2;return 0;
} 接下来再来总结下运算符重载的注意点 不能通过连接其他符号来创建新的操作符比如operator重载操作符必须有一个类类型对自定义类型成员才可运算符重载或者枚举类型的操作数用于内置类型的操作符其含义不能改变例如内置的整型不能改变其含义作为类成员的重载函数时其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this限定为第一个形参.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。 赋值运算符重载 前面我们已经学习了拷贝构造是拿同类型的对象去初始化另一个对象那如果我不想用拷贝构造呢 int main()
{Date d1(2022, 5, 17);Date d2(2022, 5, 20);Date d3(d1);//拷贝构造 -- 一个存在的对象去初始化另一个要创建的对象d2 d1; //赋值重载/复制拷贝 -- 两个已经存在的对象之间赋值
} 可不可以直接拿d1去赋值给d2呢这就是我们要谈的赋值运算符重载。赋值运算符重载和上文的运算符重载是有点相似的。有了运算符重载的基础写一个赋值重载还是很简单的。 //d2 d1; - d2.operator(d2, d1);
void operator(const Date d)
{_year d._year;_month d._month;_day d._day;
} 但是这里的赋值重载是存在一定问题的我们C语言的赋值是支持连等赋值的如下 int i 0, j, k;
k j i; 我们把i赋值给j随后把j作为返回值再赋值给k。要知道C是建立在C的基础上的刚刚我们写的赋值重载支持连等吗 很显然是不支持的原因就是当我把d1赋值给d2后没有一个返回值来赋给d3这就导致出错。改正如下 此外这里的赋值重载还可进一步改进 改进1刚才我们写的赋值重载是传值返回传值返回会生成一个拷贝会调用拷贝构造。如果出了作用域要让其对象还在我们就可以用传引用返回改进2有可能会存在这样的情况d1d1像这样自己给自己赋值的情况还要再调用赋值重载函数属实没必要所以我们还可以加个if条件判断。 修正如下 //d2 d1; - d2.operator(d2, d1);Date operator(const Date d){if (this ! d) //不推荐写成if (*this ! d) 怕的是万一没有重载!呢因为这里是对象的比较{_year d._year;_month d._month;_day d._day;}return *this;} 注意 operator赋值也是默认成员函数我们不写赋值重载编译器也会默认生成不过编译器完成的依旧是值拷贝或浅拷贝像这个日期类就可以不写赋值重载。 赋值重载和拷贝构造一样我们不写它会对内置类型完成值拷贝而像栈这样的就不能不写了因为我们要写一个深拷贝的赋值重载才可以理由和拷贝构造类似。 具体实现等真正谈到深拷贝再来。 补充 void TestDate2()
{Date d1(2022, 5, 18);Date d3 d1; //等价于 Date d3(d1);
} Date d3 d1 是拷贝构造不是赋值 拿一个对象初始化另一个对象是拷贝构造。如下d2 d1才是赋值 void TestDate2()
{Date d1(2022, 5, 18);Date d2(2022, 5, 20);Date d3 d1; //等价于 Date d3(d1); 是拷贝构造d2 d1; //两个已经存在的对象才是赋值
} 四、共勉 以下就是我对C类的默认成员函数--------拷贝构造函数赋值运算重载的理解如果有不懂和发现问题的小伙伴请在评论区说出来哦同时我还会继续更新对C 类的默认成员函数-------const成员const取地址操作符重载的理解请持续关注我哦