目录
(3)、为什么拷贝构造函数的形参会是对象的引用,而不是对象?
一、this指针(2)
1.this指针保存在哪里?
答:this指针理论上形参,所以是保存在栈帧上。
2.this指针可以为空吗?
可以。
此时this指针接收p对象,值为空。但程序可以正常运行,因为成员函数是在编译链接阶段通过函数名(符号表)去找的,和p对象无关。
二、构造函数
很多时候,我们都会忘记给成员变量初始化,如果成员变量有指针,则会变为野指针。为了防止这种情况,C++祖师爷就创造了构造函数。
1.概念
构造函数 是一个 特殊的成员函数,名字与类名相同 , 创建类类型对象时由编译器自动调用 ,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次 。
2.特点
构造函数 是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象 。其特征如下:1. 函数名与类名相同。2. 无返回值。3. 对象实例化时编译器 自动调用 对应的构造函数。4. 构造函数可以重载。5. 当我们不写构造函数时,编译器会生成默认的无参构造函数,并且该无参构造函数对内置类型(int,char,指针等等)不做处理(有些编译器会处理,但统一看成不确定),默认为随机值;对自定义类型成员(类,结构体等等)会调用它的默认构造函数。6. C++11支持声明成员变量时给定缺省值,但当自己定义了构造函数后,编译器就不会提供默认的构造函数,缺省值也会失去作用。
7 .什么是默认构造函数:(1)、编译器自动生成的叫默认构造函数(2)、无参构造函数也可以叫默认构造函数(3)、全缺省也可以叫默认构造函数总之,可以不用传参数就调用构造的,都可以叫默认构造函数。并且这三个函数 不能同时存在。
3.应用
三、析构函数
1.概念
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作 。
2.特点
析构函数 是特殊的成员函数,其 特征 如下:1. 析构函数名是在类名前加上字符 ~ 。2. 无参数无返回值类型。3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数。5. 若我们不写析构函数,编译器会自动生成默认的析构函数。并且作用和构造函数类似。
四、拷贝构造函数
1.概念
拷贝构造函数 : 只有一个形参 ,该形参是本 类类型对象的 引用 ( 一般常用 const 修饰 ) ,在用 已存 在的类类型对象创建新对象时由编译器自动调用 。
2.特点
1. 拷贝构造函数 是构造函数的一个重载形式 。2. 拷贝构造函数的 参数只有一个 且 必须是类类型对象的引用 ,使用 传值方式编译器直接报错 , 因为会引发无穷递归调用。3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。4.C++规定,当进行自定义类型对象传参拷贝时,必须用深拷贝(拷贝构造函数)5.当不显示定义拷贝构造函数时,编译器会生成默认拷贝构造函数,用法如下:对于内置类型,对成员进行值拷贝;对于自定义类型,会调用该自定义类型的拷贝构造函数。
3.为什么会有拷贝构造函数?
(1)、首先我们下面这个场景中会发现:
如果一个类成员变量有指针类型,那么当进行对象的赋值时(值拷贝)时,如将实参对象d1传递给形参对象d2,此时对象d1与对象d2中的指针变量指向同一块空间,当d2存在的函数结束后,d2会调用析构函数释放资源,此时指针变量指向的空间被释放,接着d1对象存在的函数会结束,然后d1就会调用析构函数再次对指针变量指向的空间进行释放,这样就二次释放同一份空间,造成野指针的问题。
(2)、拷贝构造函数就是解决上述问题:
当出现对象的赋值的时候,就会自动调用拷贝构造函数,而我们可以在拷贝构造函数里面对指针类型的赋值改成其他方式赋值,例如为d2的指针变量重新申请一块空间,这样d1和d2的析构函数释放的就是两份不同的空间。
这种方法又被成为“深拷贝”;
(3)、为什么拷贝构造函数的形参会是对象的引用,而不是对象?
答案就在特点的第二点,如果形参是对象,那么当实参拷贝给形参时,就是对象的拷贝的情况,此时又会调用拷贝构造函数,然后又是实参对象拷贝给形参对象,以此会循环递归。
五、运算符重载
引言:
内置类型对象可以直接用各种运算符,内置类型都是简单类型,语言自己定义,编译直接转换成指令。
而自定义类型就不支持,这时就要用到运算符重载。
1.概念
C++ 为了增强代码的可读性引入了运算符重载 , 运算符重载是具有特殊函数名的函数 ,也具有其返回值类型、函数名字以及参数列表,其返回值类型、参数列表与普通的函数类似。
2.特点
(1). 函数名字为:关键字 operator + 需要重载的运算符符号 。(2).函数原型: 返回值类型 operator 操作符 ( 参数列表 )(3).不能通过连接其他符号来创建新的操作符:比如 operator@(4).重载操作符必须有一个类类型参数(5).用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不能改变其含义(6).作为类成员函数重载时,其形参看起来比操作数数目少1 ,因为成员函数的第一个参数为隐藏的 this指针 指向一个类对象。(7).(.*)( :: )(sizeof )(?:)( . ) 注意以上 5 个运算符不能重载。这个经常在笔试选择题中出现。(8).运算符重载与函数重载没有关联!(9).一个类重载哪些运算符,主要看这个运算符对于这个类来说有没有意义,有意义就可以实现,没有意义就不要实现。
![](https://img-blog.csdnimg.cn/direct/1742afba091d406a97f2626bf2fc15b4.png)
六、默认重载运算符——赋值(=)
1.实现:
Date& operator=(const Date& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; }
(1)、this指针获得的是左操作数对象,d引用获得是右操作数对象。
(2)、有时候会有p1=p1,也就是自己拷贝自己的情况,这种情况不需要过多赋值,所以用一个if判断语句判断。
(3)、有时候会进行连续赋值的情况(p1=p2=p3),内置类型的赋值运算符是由返回值的,返回值就是左操作数的值(上述例子是先将p3赋值给p2,然后返回p2的值,然后又赋值给p1),所以我们重载运算符最后返回this指向对象的引用,也就是返回左操作数的值。
2.特点:
1、我们不写opertor=函数时,编译器会默认生成一个重载函数,并且该函数是进行的浅拷贝(以值的方式逐字节拷贝),且行为跟拷贝构造函数类似,内置类型进行值拷贝(浅拷贝),自定义类型调用它的赋值。所以当遇到成员有指针类型的情况,我们需要手动去写opertor=,实现深拷贝。
2、赋值运算法只能重载成类的成员函数不能重载为全局函数。
七、日期类Date的实现
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<assert.h> using namespace std; class Date { private: int _year; int _month; int _day; public: //全缺省构造 Date(int year= 1900,int month=1,int day=1) { _year = year; _month = month; _day = day; if (_year < 1 || _month < 1 || _month>12 || _day<1 || _day>getMonDay(_year, _month)) { myPrint(); cout << "日期非法" << endl; } } //拷贝构造 Date(const Date& p) { _year = p._year; _month = p._month; _day = p._day; } //打印 void myPrint() { cout << _year << "/" << _month << "/" << _day<<endl; } //== bool operator==(const Date& p) { return (_year == p._year) && (_month == p._month) && (_day == p._day); } //!+ bool operator!=(const Date& p) { return !(*this == p); } //> bool operator>(const Date& p) { if (_year > p._year) { return true; } else if (_year == p._year && _month > p._month) { return true; } else if (_year == p._year && _month == p._month && _day > p._day) { return true; } return false; } //>= bool operator>=(const Date& p) { return *this > p || *this == p; } //< bool operator<(const Date& p) { return !(*this >= p); } //<= bool operator<=(const Date& p) { return *this < p || *this == p; } //获取该月天数 int getMonDay(int year,int month) { assert(year >= 1 && month >= 1 && month <= 12); int ArrayMon[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month == 2 && (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) { return 29; } return ArrayMon[month]; } //+=复用+ /*Date operator+(int i) { Date p1(*this); p1._day += i; while (p1._day > getMonDay(p1._year, p1._month)) { p1._day -= getMonDay(p1._year, p1._month); p1._month++; if (p1._month == 13) { p1._year++; p1._month = 1; } } return p1; } Date& operator+=(int i) { *this = *this + i; return *this; }*/ //+ Date operator+(int i) { Date p1(*this); p1 += i; return p1; } //+= Date& operator+=(int i) { if (i <= 0) { return *this -= (-i); } _day += i; while (_day > getMonDay(_year, _month)) { _day -= getMonDay(_year, _month); _month++; if (_month == 13) { _year++; _month = 1; } } return *this; } //-= Date& operator-=(int i) { if (i <= 0) { return *this += -i; } _day -= i; while (_day <= 0) { _month--; if (_month == 0) { _year--; _month = 12; } _day+=getMonDay(_year,_month); } return *this; } //- Date operator-(int i) { Date tmp(*this); tmp -= i; return tmp; } //前置++ Date& operator++() { (*this) += 1; return *this; } //后置++ Date operator++(int) { Date tmp(*this); *this += 1; return tmp; } //日期-日期 int operator-(Date& d) { int flag = 1; Date max = *this; Date min = d; if (min > max) { max = d; min = *this; flag = -1; } int count = 1; while (min <= max) { ++min; count++; } return count; } //= Date& operator=(const Date& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; } }; int main() { Date p1(2023, 1, 1); Date p2(p1); p1.myPrint(); p2.myPrint(); bool ret = (p1 == p2); //==,>=,<= cout << ret << endl; cout << (p1 >= p2)<<endl; //p1-p2.p1+100 Date tmp = p1 += 30; tmp.myPrint(); tmp = p1 + 30; tmp.myPrint(); p2 = p1; Date p4(100, 20, 10); Date p5(2021, 10, 10); p5 -= 10000; p5.myPrint(); Date p6 = p5 - 10000; p6.myPrint(); Date p7(2023, 10, 24); Date p8(2024, 2, 10); cout << p8 - p7; return 0; }