c++——类和对象(中)
目录
- 一、 类的6个默认成员函数
- 二、 构造函数
- 2.1概念
- 2.2特性
- 三、 析构函数
- 3.1概念
- 3.2 特性
- 四、 拷贝构造函数
- 4.1概念
- 4.2特征
- 五、赋值运算符重载
- 5.1 运算符重载
- 5.2赋值运算符重载
- 5.3前置++和后置++重载
- 六、日期类的实现
- 七、const成员函数
- 八、取地址及const取地址操作符重载
- 九、总结
一、 类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
class Data {};
二、 构造函数
2.1概念
如下:
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2002, 11, 24);
d1.Print();
Date d2;
d2.Init(2007, 1, 11);
d2.Print();
return 0;
}
对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
2.2特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
- 函数名与类名相同。
- 无返回值。(不需要写void)
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
#include<iostream>
using namespace std;
class Date
{
public:
//无参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//它们每个对象的隐含this指针都指向各自对象的地址
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //注意无参加了括号就成函数声明了
Date d2(07, 1, 11);
d1.Print();
d2.Print();
}
注意:调用无参对象时不需要加(),否则会认为是函数的声明。
Date d1;
Date d1(); //错误,容易和函数声明混淆
- 只有当我们没有显式定义构造函数,编译器才会自动生成一个无参构造函数。
#include<iostream>
using namespace std;
class Date
{
public:
/*
//无参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//它们每个对象的隐含this指针都指向各自对象的地址
*/
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //注意无参加了括号就成函数声明了
//Date d2(07, 1, 11);
d1.Print();
//d2.Print();
}
我们没有传入值到类中。因此当使用编译器的无参构造函数,类的内置类型不会被初始化。
全缺省能代替无参功能
无参的、全缺省的、我们不写编译器生成的都称为默认构造函数
- 编译器自动生成的默认构造函数,对内置类型的对象不做处理,只调用自定义类型对象构造函数。
class test
{
public:
test()
{
cout << "huangyanling" << endl;
}
private:
int x;
int y;
};
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Init()
{
_year = 07;
_month = 1;
_day = 11;
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
//内置类型
int _year;
int _month;
int _day;
//自定义类型
test a;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
当创建基于Date类的对象d1时,编译器默认生成无参构造函数,去调用了自定义类型 test类的构造函数。
默认无参构造函数不处理内置类型,所以在打印Date类中的内置类型year,month,day才会出现随机值。
编译器生成的默认构造函数(初始化):
1.内置类型成员,不去调用默认构造(有的编译器会调用默认构造),我们就当做它不调用默认构造,我们不去依赖编译器初始化
2.自定义类型成员,会去调用默认构造
7.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个被认为是默认构造函数的有三种:
- 显示的无参构造函数
Date()
{
_year = 2007;
_month = 1;
_day = 11;
}
- 显示的全缺省构造函数
Date(int year = 2007, int month = 1, int day = 11)
{
_year = year;
_month = month;
_day = day;
}
- 编译器默认生成的构造函数
注意:默认构造函数是指不传参就可以调用的构造函数
上面提到的无参构造函数和全缺省函数。
结论:
不能依靠编译器的情况:
一般情况下,构造函数都需要我们自己写
可以依靠编译器的情况(一般较少)
1.内置类型成员都有缺省值,且初始化符合我们的要求
2.全是自定义类型的构造,且这些类型都定义默认构造
三、 析构函数
3.1概念
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
3.2 特性
析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符
~
。 - 无参数无返回值类型。(不需要写void)
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载。 - 对象生命周期结束时,C++编译系统系统自动调用析构函数。
#include<iostream>
using namespace std;
class Date
{
public:
//构造
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//析构
~Date()
{
cout << "~Date" << endl;
if (_year || _month || _day)
{
_year = 0;
_month = 0;
_day = 0;
}
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year = 07;
int _month = 1;
int _day = 11;
};
void test()
{
Date d;
d.Print();
}
int main()
{
test();
return 0;
}
test函数内部创建了Date类的对象d,test函数调用结束,对象d离开了作用域,对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
可以看到这里调用了析构函数
- 需不需要写析构的情况
- 在动态栈中需要malloc动态申请空间,对象在销毁时会自动调用它的析构函数
class Stack
{
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
private:
int *_a;
int _capacity;
int _top;
};
- 没有动态申请,不需要写析构
class Date
{
private:
int _year;
int _month;
int _day;
};
- 需要释放资源的成员都是自定义类型,不需要写析构。
class MyQueue
{
private:
Stack _pushst;
Stack _popst;
};
同样析构对内置类型成员不做处理,对自定义类型会去调用它的析构函数
四、 拷贝构造函数
4.1概念
拷贝构造函数会创建一个新对象,并用另一个同类型的对象初始化,生成一个新的对象副本。
拷贝构造只有一个参数,规定使用引用传参(一般用const修饰),使用已存在的类类型对象创建时编译器自动调用。
4.2特征
拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类的类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2007, int month = 1, int day = 11)
{
_year = year;
_month = month;
_day = day;
}
//隐含的this指针对象是d2,这里形参d是d1
//我们一般在这里加个const权限平移或缩小,可以及时发现这里赋值的顺序是否写反
/*以下这两种写法都可以
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
*/
Date(const Date& d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
//以下形参错误,造成无穷递归
//自定义类型拷贝须引用或指针,但我们通常用引用
void func(Date d)
{
}
//内置类型可以直接写
void func(int n)
{
}
int main()
{
Date d1(2007, 1, 11);
Date d2(d1); //d2拷贝成d1
d1.Print();
d2.Print();
func(d1); //自定义类型
func(17); //内置类型
return 0;
}
》
内置类型成员完成值拷贝/浅拷贝
自定义类型成员会调用它的拷贝构造,调用之后又要继续往下调用它的拷贝构造,造成无穷递归
- 若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
- 浅拷贝
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 11)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2007, 1, 11);
Date d2(d1);
d1.Print();
d2.Print();
return 0;
}
如上对于内置类型我们可以不写拷贝构造,编译器会自动生成,这种内置类型的拷贝叫浅拷贝或值拷贝
- 涉及资源申请的类需要使用深拷贝
- 深拷贝
引入:
其中一种场景
如栈这种动态需要申请的就需要我们显式的去完成深拷贝。
#include<iostream>
#include<stdlib.h>
using namespace std;
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack()" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_top = 0;
}
//st2(st1)
Stack(const Stack& st)
{
//st2创建一个和st1一样大的空间
_a = (int*)malloc(sizeof(int) * st._capacity);
if (nullptr == _a)
{
perror("malloc fail");
return;
}
//st1的数据拷贝给st2
memcpy(_a, st._a, sizeof(int) * st._top);
_capacity = st._capacity;
_top = st._top;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
private:
int* _a;
int _capacity;
int _top;
};
int main()
{
Stack st1;
Stack st2(st1);
}
正常运行调用了两次析构
- 编译器生成的默认拷贝构造函数可以完成字节序的值拷贝,一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
- 拷贝构造函数典型调用场景。
使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象
五、赋值运算符重载
对于内置类型可以直接比较大小
那对于自定义类型是不可以的
int main()
{
int a = 7;
int b = 2;
cout << (a < b)<< endl;
//流插入的优先级是很高的要加括号
Date d1(2002, 11, 24);
Date d2(2007,1,11);
cout << (d1 < d2) << endl;
return 0;
}
如下我们引出自定义也可以比较的操作符
5.1 运算符重载
为了使代码更易读和更易于维护,C++引入了运算符重载。运算符重载是拥有特殊函数名的函数,函数名为关键字operator,该函数和普通函数一样有返回类型。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型参数(因为重载运算符的目的是用于自定义类型的使用)
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5.(.*) ( :: ) (sizeof) ( ?: ) (.) 注意这5个运算符不能重载。这个经常在笔试选择题中出现。
//重载==运算符,对两个自定义类型进行比较,只需要传入一个类对象的参数即可
bool operator==(const Date & d)//尽量使用引用传入
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
5.2赋值运算符重载
- 重载格式
-
参数类型:const 类名&,传递引用可以提高传参效率
-
返回值类型:类名&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值(注意:返回的是局部变量就不能用引用)
-
检测是否自己给自己赋值
d1=d1;
-
返回*this :要复合连续赋值的含义
#include<iostream>
#include<stdbool.h>
using namespace std;
class Date
{
public:
Date(int year = 2007, int month = 1, int day = 11)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
//注意返回类型,出了作用域对象还在就可以用引用返回
Date& operator=(const Date& d)
{
//为了防止自己赋值给自己(d1=d1)
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//this是指向当前类对象的指针
return *this;
}
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
int _year;
int _month;
int _day;
};
//直接比较它们的对象传引用,函数内部不改变加const
//比较两个日期中小的那个,返回x1是否比x2小
bool operator<(const Date& x1, const Date& x2)
{
if (x1._year < x2._year)
{
return true;
}
else if (x1._year == x2._year && x1._month < x2._month)
{
return true;
}
else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day)
{
return true;
}
return false;
}
int main()
{
Date d1(2002, 11, 24);
Date d2(2007,1,11);
d1 < d2;
d1 = d2; //已经存在的两个对象之间的拷贝——运算符重载函数
//Date d3(d1); //用一个存在的对象初始化另一个对象——构造函数
//注意赋值函数的返回值
Date d4, d5;
d5 = d4 = d1;
return 0;
}
2.赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& d1, const Date& d2)
{
if (&d1 != &d2)
{
d1._year = d2._year;
d1._month = d2._month;
d1._day = d2._day;
}
return d1;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2002, 11, 24);
Date d2;
d2 = d1; //可以不写赋值运算符重载
}
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
默认生成赋值重载跟拷贝构造行为一样
1、内置类型成员–值拷贝/浅拷贝
2、自定义类型成员会去调用他的赋值重载
5.3前置++和后置++重载
- 前置++重载
//this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++()
{
_day++;
return *this;
}
- 后置++重载
Date operator++(int)
{
Date temp(*this); //拷贝构造
_day++;
return temp;
}
为了让前置++与后置++形成能正确重载, C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1, 而temp是临时对象,因此只能以值的方式返回,不能返回引用
六、日期类的实现
在实现日期类我们可以先写一个小于或大于函数和一个等于函数,可以直接复用更方便
Date.h文件
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
//2.友元函数声明
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1);
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
bool operator<(const Date& x);
bool operator==(const Date& x);
bool operator<=(const Date& x);
bool operator>(const Date& x);
bool operator>=(const Date& x);
bool operator!=(const Date& x);
int GetMonthDay(int year, int month); //计算闰年的子函数
Date& operator+=(int day); //日期+天数。+=有返回值因为有连续赋值的情况
Date operator+(int day); //自己值不发生改变,不用引用,复用+=
Date& operator-=(int day); //日期-天数
Date operator-(int day);
Date& operator++(); //前置++,返回的值发生了改变
Date operator++(int); //后置++,返回的值没发生改变
Date& operator--(); //前置--
Date operator--(int);
int operator-(const Date& day); //日期-日期
//Date对象默认占用第一个参数,做了左操作数,一般写在全局中
void operator<<(ostream& out);
//1.给全局提供一个共有成员函数
/*
int GetYear()
{
return _year;
}
*/
private:
int _year;
int _month;
int _day;
};
//写在全局out就能做第一个参数了
//定义在全局访问不了私有,1.在日期类加一个公有成员函数或2.友元函数
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
Date.cpp文件
#include "Date.h"
Date::Date(int year, int month, int day)
{
if(month>0&&month<13&&day>0&&day<=GetMonthDay(year,month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
}
bool Date::operator<(const Date& x)
{
if (_year < x._year)
{
return true;
}
if (_year == x._year && _month < x._month)
{
return true;
}
if (_year == x._year && _month == x._month && _day < x._day)
{
return true;
}
return false;
}
bool Date::operator==(const Date& x)
{
return _year == x._year
&& _month == x._month
&& _day == x._day;
}
//有了小于函数和等于函数后,这里我们可以直接复用来实现功能
bool Date::operator<=(const Date& x)
{
return *this < x || *this == x;
}
bool Date::operator>(const Date& x)
{
return !(*this <= x);
}
bool Date::operator>=(const Date& x)
{
return !(*this < x);
}
bool Date::operator!=(const Date& x)
{
return !(*this == x);
}
//对于内置类型一般不考虑引用
int Date::GetMonthDay(int year, int month)
{
static int daysArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
//只有2月的时候判断
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
return daysArr[2] = 29;
}
else
{
return daysArr[month];
}
}
//日期+天数
Date& Date::operator+=(int day)
{
//如果传进来的是负数
if (day < 0)
{
return *this -= -day;
}
_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)
{
Date tmp(*this);
//复用+=函数,这里+的函数没有改变,改变的是拷贝构造tmp的临时变量
tmp += day;
return tmp;
}
//前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
//后置++
//这里加个int参数不是为了接收具体某个值,仅仅是为了区分前后置++,构成重载
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
Date& Date::operator-=(int day)
{
//如果传进来的是负数
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day < 1)
{
//当前月的天数已经减完了
--_month;
if (_month == 0)
{
_month = 12;
--_year;
}
//获取上个月的天数
_day += GetMonthDay(_year, _month);
}
return *this;
}
//复用-=
Date Date::operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
//前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
Date Date::operator--(int)
{
Date tmp = *this;
*this = *this - 1;
return tmp;
}
//日期-日期
int Date::operator-(const Date& day)
{
Date max = *this;
Date min = day;
int flag = 1;
if (*this < day)
{
max = day;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++min;
++n;
}
return flag * n;
}
//全局
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
int year, month, day;
in >> year >> month >> day;
if (month > 0 && month < 13 && day>0 && day <= d.GetMonthDay(year, month))
{
d._year = year;
d._month = month;
d._day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
return in;
}
test.cpp
#include "Date.h"
//实现一个日期加多少天数后返回日期
void TestDate1()
{
Date d1(2024, 1, 11);
d1 += -66;
d1.Print();
Date d2(2024, 1, 11);
//两种写法等价
//Date d3(d2 + 17);
Date d3 = d2 + 17;
d2.Print();
d3.Print();
}
//前后置++
void TestDate2()
{
Date d1(2007, 1, 11);
Date ret1 = d1++; //d1.operator++()
d1.Print();
ret1.Print();
Date d2(2007, 1, 11);
Date ret2 = ++d2; //d2.operator++(0)括号内数字随便可以
d2.Print();
ret2.Print();
}
//日期-天数
void TestDate3()
{
Date d1(2024, 12, 8);
d1 -= 300;
d1.Print();
Date d2(2024, 12, 8);
d2 - 300;
d2.Print();
}
//日期+负的天数或-负的天数
void TestDate4()
{
Date d1(2024, 12, 8);
Date d2(2024, 12, 8);
d1 += -300;
d2 -= -300;
d1.Print();
d2.Print();
}
//日期-日期
void TestDate5()
{
Date d1(2024, 12, 8);
Date d2(1923, 2, 15);
cout << d1 - d2 << endl;
cout << d2 - d1 << endl;
}
//前后置--
void TestDate6()
{
Date d1(2024, 12, 8);
Date ret1 = d1--;
ret1.Print(); //输出时只有ret1的值没有发生改变
d1.Print();
Date d2(2024, 12, 8);
Date ret2 = --d2;
ret2.Print();
d2.Print();
}
//流插入是一个双操作数,流提取
void TestDate7()
{
Date d1(2024, 12, 11);
d1 += 100;
//流插入不能写成成员函数,因为Date对象默认占了左操作数
//在成员函数
//d1 << cout; //d1.operator<<(cout);
//cout是个ostream的类对象,d1是日期类对象
//ostream是iostream库定义的
cout << d1;
Date d2(2024, 12, 11);
Date d3(1949, 10, 1);
cout << d2 << d3 << d1;
cin >> d1 >> d2;
cout << d2 << d1;
}
int main()
{
TestDate1();
TestDate2();
TestDate3();
TestDate4();
TestDate5();
TestDate6();
TestDate7();
return 0;
}
函数重载支持自动识别类型
运算符重载是为了自定义类型支持运算符
它们都有重载但是它们之间没有关系,它们是独立的,
两个运算符重载能构成函数重载
- 可以直接支持内置类型是库里面实现了
- 可以直接支持自动识别类型是因为函数重载
七、const成员函数
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
(1)权限只能平移或缩小,不能放大
(2)const成员函数内部和非const成员函数内部均可以调用其他常量成员函数或非常量成员函数
(3)成员函数内部不需要修改成员变量时,都可以加上const,方便常量对象调用。
(4)const成员函数中不能对类的任何成员进行修改。
如果不改变成员函数可以都加上const
八、取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Date
{
public:
//不想让别人取到这个普通对象地址
Date* operator&()
{
cout << "Date* operator&()" << endl;
return nullptr;
}
const Date* operator&()const
{
cout << "const Date* operator&()const" << endl;
return this;
}
private:
int _year=1;
int _month=1;
int _day=1;
};
int main()
{
Date d1;
const Date d2;
cout << &d1<<endl;
cout << &d2<<endl;
return 0;
}
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。
很少有使用场景