淄博网站制作营销,wordpress 轮播图自适应,电子商务网站建设教程试卷,郑州网站seo多少钱创作不易#xff0c;感谢三连#xff01;
一、六大默认成员函数
C为了弥补C语言的不足#xff0c;设置了6个默认成员函数 二、构造函数
2.1 概念 在我们学习数据结构的时候#xff0c;我们总是要在使用一个对象前进行初始化#xff0c;这似乎已经成为了一件无法改变的… 创作不易感谢三连
一、六大默认成员函数
C为了弥补C语言的不足设置了6个默认成员函数 二、构造函数
2.1 概念 在我们学习数据结构的时候我们总是要在使用一个对象前进行初始化这似乎已经成为了一件无法改变的事情如以下的Data类 对于Date类可以通过 Init 公有方法给对象设置日期但如果每次创建对象时都调用该方法设置信息未免有点麻烦我们的祖师爷就在想像初始化这种傻瓜式的行为能不能交给编译器去完成呢能否在对象创建时就将信息设置进去呢于是就有了构造函数 构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次。 2.2 特性 构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象而是初始化对象。 其特征如下特性1. 函数名与类名相同。特性2. 无返回值。特性3. 对象实例化时编译器自动调用对应的构造函数。由编译器完成特性4. 构造函数可以重载。即一个类可以有多种构造函数也就是多种初始化方式 思考
1、为什么调用无参构造不加个括号呢总感觉很奇怪
答其实按道理来说加个括号比较合理但是如果我们加上了一个括号如上图的Date d3就会发现这个和函数的声明会难以区分从函数的声明来看会被翻译成声明了一个d3函数该函数无参返回一个Date对象所以为了区分这两种情况要求无参构造不能加括号。那你可能会问为什么传参构造就不会当成有参函数的声明了呢因为有参函数声明的写法应该是Date d2int xint yint z这个样子的写法那么你会发现有参构造和他是有区别的。所以这里不需要区分。
2、每次都写一个有参构造和无参构造是不是有点麻烦有没有改进方法
答这个时候我们之前学的缺省参数就派上用场了我们可以将有参和无参构造合并成一个全缺省构造函数。一样可以完成这个初始化过程如下图 3、既然构造函数支持重载那么全缺省的构造函数和无参构造函数可以同时存在吗
答不行这两个只能存在一个虽然你在定义的时候好像没有报错但是你在调用的时候就存在歧义因为编译器区分不出来应该去调用哪个 特性5如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成。 如下图当我们注释掉我们之前写的构造函数编译器调用了他自动生成的默认构造函数将实例化对象的成员初始化成了随机值。 如下图如果我们自己写了一个构造函数无论有参还是无参编译器的默认构造函数都不会生成了 思考
1、不实现构造函数的情况下编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用d对象调用了编译器生成的默认构造函数但是d对象_year/_month/_day依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用
答C把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型如int/char...自定义类型就是我们使用class/struct/union等自己定义的类型看看 下面的程序就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员 函数。 所以我们可以得到两个结论
默认生成的构造函数
1对内置类型不做处理
2自定义类型的成员会去调用他们的默认构造无参构造、自动生成的构造、全缺省的构造
2、 既然都可以默认处理自定义类型了那为什么不顺便把内置类型也处理一下呢
这其实是设计过程中遗留下来的一个问题后来在C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在类中声明时可以给默认值。 特性6无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。特性4的思考3中已经分析过了
注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为 是默认构造函数。
思考
1、我们怎么去把握什么时候用编译器的构造函数什么时候用自己写的构造函数呢
答无论是自己写的还是编译器提供的一般的建议是确保每个类都提供一个默认构造函数因为有时候如果该类中有自定义类型的成员我们就可以利用特性自定义类型的成员会去调用他们的默认构造让编译器来帮助我们完成对自定义类型成员的初始化。
2、内置类型的初始化一般怎么处理
答由于编译器不会处理所以有两种思路1、自己写构造函数根据该类的特定去初始化其内置类型成员。尽量用缺省这样可以同时应对无参和有参的情况 2、利用c11新增加的特性让内置类型在声明的时候给一个默认值。一般来说这两个可以综合起来运用。 三、析构函数
3.1 概念
通过前面构造函数的学习我们知道一个对象是怎么来的那一个对象又是怎么没呢的析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。
3.2 特性
析构函数是特殊的成员函数
其特征如下 特性1. 析构函数名是在类名前加上字符 ~。 特性2. 无参数无返回值类型。 特性3. 一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构 函数不能重载 特性4. 对象生命周期结束时C编译系统系统自动调用析构函数
typedef int DataType;
class Stack
{
public:Stack(size_t capacity 3){_array (DataType*)malloc(sizeof(DataType) * capacity);if (NULL _array){perror(malloc申请空间失败!!!);return;}_capacity capacity;_size 0;}void Push(DataType data){// CheckCapacity();_array[_size] data;_size;}// 其他方法...~Stack(){if (_array){free(_array);_array NULL;_capacity 0;_size 0;}}
private:DataType* _array;int _capacity;int _size;
};
int main()
{Stack s;s.Push(1);s.Push(2);
} 特性5 如果类中没有显式定义析构函数则C编译器会自动生成一个析构函数一旦用户显式定义编译器将不再生成。 通过上图我们可以得到结论
默认构造函数对
1内置类型成员不处理
2自定义类型成员调用他的析构函数
思考
1、对于构造函数我们会想办法对他的内置类型初始化那析构函数需要对内置类型需要处理吗
答并不需要对于内置类型来说销毁时是不需要进行资源清理的最后系统的内存会将其回收的因为我们把内置类型成员恢复成0是没有意义的因为他不管是多少当内存被系统回收后最后都是会被覆盖的不用多此一举
2、我们之前学过局部对象在函数调用完成后会随着函数栈帧的销毁而销毁该过程是由编译器完成的那究竟什么时候我们需要用到析构函数 答析构函数并不是对对象本身进行销毁因为对象本身作为一个局部变量在函数结束后会自动被回收的所以析构函数本质上是对对象的资源进行清理什么叫做资源呢可以理解成我们实例化某些对象时需要向内存申请在堆区开辟的空间所以需要在对象销毁前将该空间还给操作系统否则就容易造成内存泄露
结论如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如 Date类有资源申请时一定要写否则会造成资源泄漏比如Stack类Stack类的实例化需要在堆区申请空间
3、了解了构造函数和析构函数我们来对比一下和C语言使用起来的区别如下图 使用起来不仅简洁而且不需要担心自己忘记初始化栈或者销毁栈。只要一开始我们把每个类的定义的构造函数和析构函数都考虑清楚那么其他的就放手交给编译器去做
四、拷贝构造函数
在现实生活中可能存在一个与你一样的自己我们称其为双胞胎。 然后我们的祖师爷思考那在创建对象时可否创建一个与已存在对象一某一样的新对象呢所以有了 拷贝构造函数
4.1 概念
拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存 在的类类型对象创建新对象时由编译器自动调用。
4.2 特性
拷贝构造函数也是特殊的成员函数
其特征如下特性1拷贝构造函数是构造函数的一个重载形式。可以理解成比较特殊的构造函数特性2拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。 思考
1、拷贝明明是一个对象拷贝给另一个对象为什么这边只有一个参数呢
答因为成员函数会隐藏一个this指针在运行的时候编译器会自动帮我们处理所以我们只需要传那个我们需要拷贝的类类型对象就行
2、为什么传值方式编译器会无限递归 我们观察上图通过函数栈帧的了解我们可以知道每次传值调用的时候本质上形参和实参并不是同一块空间而是我们在调用的时候开辟了一块形参的空间然后将实参的数据拷贝过来再到函数中进行使用。而每次拷贝本质上都是创建一个同类型的对象。然后他为了和实参同步数据也会调用自己的拷贝构造 因此就跟套娃一样引发无线递归。但如果是传引用就不存在这个问题了因为存引用本身就是给实参起一个别名函数调用的时候操作的是同一块空间不需要创建新的对象也不需要拷贝。所以不会引发无穷递归
3、为什么概念里提到对本类类型对象的引用一般用const修饰有什么好处吗
好处1确保被拷贝的对象不会被修改比如我们一不小心写反了这个时候const可以及时帮助我们报错来提示我们 好处2如果拷贝构造传的是const修饰的变量如果你的拷贝构造函数没用const修饰就会造成权限放大 把拷贝构造函数的参数用const修饰就可以避免这种问题 特性3若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。
通过上图我们可以得到结论
1、内置类型编译器可以直接拷贝
2、自定义类型的拷贝需要调用其拷贝构造函数
思考
1 我们发现我们将我们原来写的拷贝构造删除掉编译器生成的拷贝构造也可以完成字节序的值拷贝工作啊那我们还有必要自己写拷贝构造函数吗 答要分具体情况而定如果是之前的Date日期类就不需要但是有些情况下浅拷贝就会造成很严重的后果
// 这里会发现下面的程序会崩溃掉这里需要深拷贝去解决。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity 10){_array (DataType*)malloc(capacity * sizeof(DataType));if (nullptr _array){perror(malloc申请空间失败);return;}_size 0;_capacity capacity;}void Push(const DataType data){// CheckCapacity();_array[_size] data;_size;}~Stack(){if (_array){free(_array);_array nullptr;_capacity 0;_size 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
} 报错原因如下 2、根据思考1总结浅拷贝可能造成的问题然后思考怎么用深拷贝去解决问题。
答问题1两个对象操控同一块空间严重情况下会造成数据丢失如上图s1 push了4个元素后他的size变成了4但是s2并不知道size变成了4他的size还是0如果s2继续push 5 6 7 8那么因为操控的是同一块空间那么就会造成s2 push的数据将s1 push的数据给覆盖了造成了数据丢失这个在实际工作中是很严重的问题假如你在银行先存了100万我后存了200元如果由于这个原因导致我的200把你的100万覆盖了。你在系统上看到的就是200元而不是你原来的100万问题2造成空间的多次释放这个在上图已经解释过了由于共用一块空间s2先调用析构函数把空间释放了但是s1并不知道他再调用自己的析构函数释放的时候就会造成程序的崩溃
3、怎么用深拷贝解决上述问题
上述问题的根源是指向了同一块空间所以我们解决思路就是给拷贝出来的对象也开辟一块相应的空间让他们能够各自操作各自独立的空间。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity 10){_array (DataType*)malloc(capacity * sizeof(DataType));if (nullptr _array){perror(malloc申请空间失败);return;}_size 0;_capacity capacity;}Stack(const Stack st){_array (DataType*)malloc(st._capacity * sizeof(DataType));if (nullptr _array){perror(malloc申请空间失败);return;}memcpy(_array, st._array, st._size * sizeof(DataType));//要记得把原来的数据拷贝过去_size st._size;_capacity st._capacity;}void Push(const DataType data){// CheckCapacity();_array[_size] data;_size;}~Stack(){if (_array){free(_array);_array nullptr;_capacity 0;_size 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);
//看看操作s2会不会影响s1s2.Push(5);s2.Push(6);s2.Push(7);s2.Push(8);return 0;
}
如上图这个深拷贝的关键就是要开辟一个新的空间然后将原空间的数组拷贝过来 我们发现s1和s2的array已经不是指向一块空间了 而且对s2操作不会影响到s1他们去调用自己的析构函数都会去释放自己独立的空间这就是深拷贝 4.3 使用场景
1、使用已存在对象创建新对象 2、函数参数类型为类类型对象 3、函数返回值类型为类类型对象 为了提高程序效率一般对象传参时尽量使用引用类型返回时根据实际场景能用引用 尽量使用引用看看栈帧销毁后他的空间是否还存在如果存在就引用不存在就不要引用 五、运算符重载 以前我们学习操作符的时候都知道操作符只能对内置类型其效果对有多个成员的结构体成员是起不到效果的因为编译器无法判断对哪一个类型成员进行操作或者哪一个类型成员进行比较但是有些时候我们还是避免不了要对结构体进行比较和操作如果总是通过调用函数来操作自定义类型的话可读性太差所以我们的祖师爷发明了运算符重载。
5.1 运算符重载
C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其 返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为关键字operator后面接需要重载的运算符符号。 函数原型返回值类型 operator操作符(参数列表)
注意事项
1、不能通过连接其他符号来创建新的操作符比如operator 2、重载操作符必须有一个类类型参数
3、用于内置类型的运算符其含义不能改变例如内置的整型不能改变其含义
4、作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐 藏的this所以至少有一个类型参数就够了
5、.*少用注意和*区分 ::访问限定符 sizeof计算类型大小 ?:三目运算符 .类成员访问操作符 注意以上5个运算符不能重载。 如上图我们如果用全局的operator那么我们就不能给他的成员变量用private保护起来否则会访问不到对应的操作数 因此我们的运算符重载一般在类的里面去定义这样有两个好处
1、在类内部定义就可以用private去保护成员变量了保证了封装性
2、在类里面定义这样operator就有一个隐藏的this指针所以只要传一个参数就可以了。
5.2 赋值运算符重载
特性1参数类型const T传递引用可以提高传参效率特性2返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值特性3检测是否自己给自己赋值避免额外的开销特性4返回*this 要复合连续赋值的含义
Date operator(const Date d)
{
if(this ! d)
{
_year d._year;
_month d._month;
_day d._day;
}
return *this;
}
思考
1、为什么要避免自赋值的情况 不避免可以吗
如果是自赋值如果类里面含有指针指向动态开辟的内存的话那么自身赋值就可能出错因为在赋值前需要把原来的空间给释放掉。就不能赋值了。
2、为什么要用引用返回
为了支持连续赋值
特性5赋值运算符只能重载成类的成员函数不能重载成全局函数 思考
1、之前我们实现其他运算符也是可以定义全局函数啊大不了传两个参数不就行了。为什么这里赋值运算符重载必须是成员函数
答赋值运算符如果不显式实现编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突了故赋值运算符重载只能是类的成员函数。
上图的情况就是编译器自动生成的默认赋值重载函数
为了不和该情况冲突C强制让重载必须是成员函数。 特性6用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝。注意内置类型成员变量是直接赋值的而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。 思考
1、既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了还需要自己实现吗
答当然像日期类这样的类是没必要但是一旦涉及到资源管理就必须要自己去实现赋值运算符的重载。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity 10)
{
_array (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr _array)
{
perror(malloc申请空间失败);
return;
}
_size 0;
_capacity capacity;
}
void Push(const DataType data)
{
// CheckCapacity();
_array[_size] data;
_size;
}
~Stack()
{
if (_array)
{
free(_array);
_array nullptr;
_capacity 0;
_size 0;
}
}
private:
DataType *_array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2;
s2 s1;
return 0;
} 比如上述代码会崩掉 5.3 前置和后置重载 前置和后置都是一元运算符为了让前置与后置形成能正确重载。C规定后置重载时多增加一个int类型的参数但调用函数时该参数不用传递编译器自动传递 int只是用来占位没有实际意义 前置
Date operator()
{
_day 1;
return *this;
}
d1相当于调用d1.operator( )
后置
Date operator(int)
{
Date temp(*this);
_day 1;
return temp;
}
d1相当于调用d1.operator(0) 从这里可以看出后置还需要再实例化一个对象用来拷贝原先的数据并且由于temp是局部变量出作用域销毁所以这里不能用传引用返回效率相比前置有一定拷贝的损失所以我们平时要尽量用前置。
关于 - - ……我们后面通过一个日期类来实现
六、const成员函数修饰*this
有些时候我们可能会遇到以下情况 由于我们定义的d1是const类型当取d1的地址传给隐藏的*this时出现了权限放大那我们要样才能让const类型对象调用自己的成员函数就必须给*this指针也加上const这样传参就不会出现权限放大的问题。但是C中的*this指针是隐含的参数我们没办法直接加C为了解决此类问题规定当我们将const修饰放在成员函数后面的时候默认就是将该成员函数隐藏的*this进行const修饰
将const修饰的“成员函数”称之为const成员函数const修饰类成员函数实际修饰该成员函数 隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。
注意
1、非const的对象可以调用const或者是非const的成员函数而const的对象只能调用const的成员函数其实总的来说就是权限可以变小、可以平移就是不能放大
2、使用建议内部不改变成员变量的成员函数最好加上const这样const对象和普通对象都可以调用
七、取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 编译器默认会生成。 这两个运算符一般不需要重载使用编译器生成的默认取地址的重载即可只有特殊情况才需要重载比如想让别人获取到指定的内容 八、用类实现一个数组
我们可以设置一个类在里面定义一个静态数组然后重载一个[ ]来让这个类模拟数组 你可能会觉得这样子是不是多此一举其实不是的。
1、一般来说编译器对数组的越界检查并不是非常的明显如果我们用这个类去模拟数组功能就可以很丰富比如说使用assert去检查越界 2、 我可以重载两个[ ]一个用const修饰*this确保类不被改变另一个不用const修饰确保类可以改变这样可以根据不同的场景去调用 九、日期类的实现时间计算器
先展示全部代码然后再细扣
9.1 Date.h
#pragma once
#includeiostream
#includeassert.h
using namespace std;
class Date
{//友元告诉该类这两个全局函数是我们的朋友允许使用私有成员friend ostream operator(ostream out, const Date d);friend istream operator(istream in, Date d);
public://全缺省的构造函数Date(int year 1900, int month 1, int day 1);//用来打印void Print() const;//比较bool operator(const Date d) const;bool operator!(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;//日期加天数Date operator(int day);Date operator(int day) const;//日期减天数Date operator-(int day);Date operator-(int day) const;// d1Date operator();// int参数 仅仅是为了占位跟前置重载区分Date operator(int);// --d1 - d1.operator--()Date operator--();// d1-- - d1.operator--(1)Date operator--(int);//返回两个日期的相差天数int operator-(const Date d) const;//void operator(ostream out);做成员函数的话限制了左操作数必须是d。变成 dcout;
private:int _year;int _month;int _day;//获取每月的天数int GetMonthDay(int year, int month) const;//判断是不是闰年bool is_leapyear(int year) const;
};
9.2 Date.c
#includeDate.hDate::Date(int year, int month, int day)
{//确保日期合法if ((month 0 month 13) (day 0 day GetMonthDay(year, month))){_year year;_month month;_day day;}elsecout 日期非法 endl;
}void Date::Print() const
{cout _year / _month / _day endl;
}int Date::GetMonthDay(int year, int month) const
{assert(month0 month 13);//确保传进来的month是合法的int montharr[13] { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month 2 ((year % 4 0 year % 100 ! 0) || (year % 400 0)))return 29;elsereturn montharr[month];
}bool Date::operator(const Date d) const
{return _year d._year _month d._month _day d._day;
}bool Date::operator!(const Date d) const
{return !(*this d);//复用
}bool Date::operator(const Date d) const
{if (_year d._year)return true;else if (_year d._year _month d._month)return true;else if (_year d._year _month d._month _day d._day)return true;elsereturn false;
}bool Date::operator(const Date d) const
{return (*this d) || (*this d);
}bool Date::operator(const Date d) const
{return !(*this d);
}bool Date::operator(const Date d) const
{return!(*this d);
}Date Date::operator(int day)//为了满足连续
{//如果传的是负数 负的相当于-正的if (day 0){*this - -day;return *this;}_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this;
}Date Date::operator(int day) const
{Date temp(*this);temp day;//复用return temp;
}Date Date::operator-(int day)
{//如果传的是负数 -负的相当于正的if (day 0){*this -day;return *this;}_day - day;while (_day 0){--_month;//借位if (_month 0){--_year;_month 12;}_day GetMonthDay(_year, _month);}return *this;
}Date Date::operator-(int day) const
{Date temp(*this);temp - day;//复用-return day;
}Date Date::operator()
{*this 1;return *this;
}Date Date::operator(int)
{Date temp(*this);*this 1;return temp;
}Date Date::operator--()
{*this - 1;return *this;
}Date Date::operator--(int)
{Date temp(*this);*this - 1;return temp;
}//方法1不断直到等于大的那个年份好写但是效率低点
//int Date::operator-(const Date d) const
//{
// Date max *this;
// Date min d;
// int flag 1;
// if (*this d)//假设错了就认错
// {
// Date max d;
// Date min *this;
// int flag -1;//用来标记
// }
// int count 0;
// while (min ! max)
// {
// min;
// count;
// }
// //循环结束得到的count就是目标天数
// return count * flag;
//}//方法2先把两个年份修饰到1月1日然后算两个年之间有多少年如果是平年365闰年366 难写但是效率高点
bool Date::is_leapyear(int year) const
{if ((year % 4 0 year % 100 ! 0) || (year % 400 0))return true;elsereturn false;
}int Date::operator-(const Date d) const
{//不知道哪个操作数大先假设Date max *this;Date min d;int flag 1;if (*this d)//假设错了就认错{Date max d;Date min *this;int flag -1;//用来标记}int count 0;//大的减到1月1日 countwhile (!(max._day 1 max._month 1)){--max;count;}//小的减到1月1日 count--while (!(min._day 1 min._month 1)){--min;--count;}//都减到1月1日了 算差多少年while (min._year ! max._year){if (is_leapyear(min._year))count 366;elsecount 365;min._year;}return flag * count;
}ostream operator(ostream out, const Date d)
{out d._year d._month d._day endl;return out;
}istream operator(istream in, Date d)
{in d._year d._month d._day;return in;
}
9.3 流提取和插入
通过运算符重载的学习我们知道了cout和cin的本质也是也是一个输入和输出流对象而和是他们重载出来的运算符cout属于ostream类cin属于istream类 我们还可以发现为什么cout和cin可以自动识别类型呢因为他的类里面有针对不同类型的运算符重载 那我们是否也可以通过重载和去打印日期和提取日期呢
我们先尝试重载打印日期 如果我们在Date内部去对重载要使用的话是这样的 为什么是这样呢因为在Date类里面定义的话默认Data类是第一个操作数d1cout显然不符合我们的习惯。
所以方法就是在类外去重载,这样我们可以去改变操作数使其变成coutd1,这样可以符合我们的使用习惯但是这样会面临一个问题类外没办法访问Data类的私有成员。。如果我们把权限放开了又不合适这个时候就要用到我们的友元即告诉Date类这个全局函数是我们的朋友可以访问私有成员。 注意事项
1、用他们的类引用作返回值是为了应对连续流提取和连续流插入的情况
2、要注意流提取不能对Date进行const修饰因要通过键盘读取数据存进date对象的成员变量里面
3、对于这样的短小的函数可以用内联在头文件定义
9.4 比较运算符的重载
基本上只要实现了和(),其他的就可以直接复用函数。
9.5 不希望别人使用的成员函数 这两个函数并不希望别人使用因为这两个函数都是在成员函数内部去调用的所以不需要公有。
9.6 和 -和-
要注意的是是直接改变对象而并不会改变原对象。所以可以直接就操作却要重新创建一个类对象然后进行拷贝再拷贝的对象上进行操作
注意事项
1、和-都是引用返回、不能用const修饰改变原对象返回值是类支持连续操作
2、和-都是传值返回因为拷贝的是一个局部变量出作用域会销毁、可以用const修饰再拷贝对象上操作返回值也是类支持连续
3、按道理来说我们实现和只需要实现一个即可。另外一个进行复用就行从效率的角度考虑先实现再用复用会更好因为不会进行拷贝而且是传引用返回可以避免不必要的损失如果是复用那么无论是还是都会调用一个也就是无论如何都要拷贝。