C++默认构造函数(二)

目录

构造函数补充

构造函数初始化列表的使用

赋值运算符重载函数

运算符重载函数介绍

运算符重载函数的使用

赋值运算符重载函数

赋值运算符重载函数的使用

拷贝构造函数和赋值运算符重载函数

重载前置++和后置++

前置++

后置++

重载流插入<<与流提取>>

流插入运算符<<重载

流提取运算符>>重载

const成员函数

取地址操作符重载与const成员取地址操作符重载

实现日期类练习


声明:本篇为C++默认构造函数最后一篇

构造函数补充

在C++中,可以在构造函数的函数体中为变量进行初始化,但是实际上该过程并不是初始化,可以理解为赋值,因为对象还没有真正创建,并且初始化只能初始化一次,但是赋值可以执行多次,而当对象创建时,所有成员变量就是当成整体创建,那么每一个变量在和何处被初始化变成了一个问题,为了解决这个问题,C++标准中引入构造函数初始化列表,只要是本类的成员变量都是在初始化列表处初始化,具体初始化的内容由程序员自己决定


#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 23):_year(year), _month(month), _day(day){}void print(){cout << _year << "/" << _month << "/" << _day << endl;}
};int main()
{Date d;d.print();return 0;
}
输出结果:
2024/3/23

构造函数初始化列表的使用

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式,语法如下

类名(参数列表)
:成员变量(值)
,成员变量(值)
,成员变量(值)
{}

📌

注意,并不是所有成员变量都需要写进初始化列表,没写入初始化列表的成员变量也会像写入初始化列表中的成员变量一样走一遍初始化列表,只是没有显式

1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2. 类中包含以下成员,必须放在初始化列表位置进行初始化:

  1. 引用成员变量
#include <iostream>
#include <cstdlib>
#include <cassert>
using namespace std;class Array
{
private:int* _arr;int _size;int& ref;
public:Array():_arr(nullptr), _size(4), ref(_size)//引用类型必须初始化{_arr = (int*)malloc(sizeof(int) * _size);assert(_arr);}Array(const Array& data):ref(_size)//拷贝构造中,引用类型也必须初始化{_arr = (int*)malloc(sizeof(int) * data._size);assert(_arr);for (int i = 0; i < data._size; i++){_arr[i] = data[i];}}//重载[]int& operator[](int i){return _arr[i];}//const类型的引用,不可以通过返回的引用改变数组中的值const int& operator[](int i) const{return _arr[i];}
};int main()
{Array a;for (int i = 0; i < 4; i++){a[i] = i + 1;}const Array p(a);for (int i = 0; i < 4; i++){cout << p[i] << " ";}return 0;
}
输出结果:
1 2 3 4

  1. const成员变量
  2. 自定义类型成员(且该类没有默认构造函数时)
#include <iostream>
using namespace std;class Time
{
private:int _time;
public:Time(int time){}
};class Date
{
private:int _year;int _month;int _day;Time _t;
public:Date():_year(2023),_month(3),_day(21){}
};int main()
{Date d;return 0;
}
报错信息:
类 "Time" 不存在默认构造函数

在C++11标准规范中,可以在成员变量创建的同时给缺省值,此时如果给了这个缺省值,想使用缺省值时就不需要再将该成员变量写入初始化列表中,如果不想使用缺省值,则将成员变量写入初始化列表中并给定初始值,否则默认初始值为0

#include <iostream>
using namespace std;//构造函数初始化列表
class Date
{
private:int _year = 2023;int _month = 2;int _day = 28;
public:Date()//_year没有写入初始化列表,使用缺省值: _month(3)//写入初始化列表中,给了初始值为3,使用初始值, _day()//写入初始化列表中,但是没给初始值,默认初始值为0{}void print(){cout << _year << "/" << _month << "/" << _day << endl;}
};int main()
{Date d;d.print();return 0;
}
输出结果:
2023/3/0

在初始化列表中无法处理例如动态申请内存的行为,此时可以在函数体内完成,例如

#include <iostream>
#include <cstdlib>
#include <cassert>
using namespace std;class Array
{
private:int* _arr;int _size;
public:Array():_arr(nullptr),_size(4){_arr = (int*)malloc(sizeof(int) * _size);//在构造函数体中分配空间assert(_arr);}Array(const Array& data){_arr = (int*)malloc(sizeof(int) * data._size);assert(_arr);for (int i = 0; i < data._size; i++){_arr[i] = data[i];}}//重载[]int& operator[](int i){return _arr[i];}const int& operator[](int i) const{return _arr[i];}
};int main()
{Array a;for (int i = 0; i < 4; i++){a[i] = i + 1;}const Array& p(a);for (int i = 0; i < 4; i++){cout << p[i] << " ";}return 0;
}
输出结果:
1 2 3 4

所以,如果不使用缺省值,尽量使用初始化列表初始化,因为不管是否使用初始化列表,对于自定义类型的成员变量,一定会先使用初始化列表初始化

📌

注意:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

class A
{
public:A(int a):_a1(a),_a2(_a1){}void Print() {cout<<_a1<<" "<<_a2<<endl;}
private:int _a2;int _a1;
};
int main() {A aa(1);aa.Print();
}
输出结果:
1 -858993460

在上面的代码中,因为成员变量_a2_a1先声明,所以在初始化时先走_a2(_a1),所以_a2被初始化为随机值,接着再初始化_a1,所以_a1为1

赋值运算符重载函数

运算符重载函数介绍

在C++中,为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}
};int main()
{Date d1;Date d2(d1);cout << (d1 == d2) << endl;//自定义类型无法使用内置的关系运算符进行比较return 0;
}
报错信息:
二进制“==”:“Date”不定义该运算符或到预定义运算符可接收的类型的转换

为了自定义类型的对象之间可以进行关系运算,可以使用运算符重载,如下面代码

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}int getYear(){return _year;}int getMonth(){return _month;}int getDay(){return _day;}
};bool operator==(Date& d1, Date& d2)
{return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}int main()
{Date d1;Date d2(d1);cout << (d1 == d2) << endl;//有重载==的函数时可以比较return 0;
}
输出结果:
1

运算符重载函数的使用

对于运算符重载函数来说,其函数名为:operator+需要重载的运算符,而该函数的原型如下:

函数返回类型 operator运算符(参数列表)

定义运算符重载函数时,需要注意下面的问题

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5. .* :: sizeof ?: . :注意以上5个运算符不能重载

对于下面的代码

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}int getYear(){return _year;}int getMonth(){return _month;}int getDay(){return _day;}
};//全局运算符重载函数
bool operator==(Date& d1, Date& d2)
{return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}int main()
{Date d1;Date d2(d1);cout << (d1 == d2) << endl;//有重载==的函数时可以比较return 0;
}
输出结果:
1

在上面的代码中,因为运算符重载函数不在类中,并且因为类的成员变量为private,所以需要调用获取函数来得到当前对象的成员变量中的值,并且因为在全局中,并不存在哪一个对象调用函数,所以没有this指针,此时形参的个数对应运算符的操作数的个数

📌

注意上面的全局运算符重载函数中形参不可以使用const修饰,因为如果使用了const修饰,那么就是d1d2都是const修饰的对象,而this只是*const,而不是const*,本来是d1d2const修饰不可以修改引用的对象的值,但是如果传递给了this可能会出现通过this改变d1d2引用的对象的值,所以此处涉及到将引用的权限放大

考虑到如果将运算符重载函数写在类外需要额外写三个函数来获取到指定的值,所以可以将运算符重载函数写进类中

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}//类中的运算符重载函数bool operator==(const Date& d){return _year == d._year && _month == d._month && _day == d._day;}
};int main()
{Date d1;Date d2(d1);cout << (d1 == d2) << endl;//直接调用类中实现的运算符重载函数//上面的代码相当于:cout << d1.operator==(d2) << endl;return 0;
}
输出结果:
1

在上面的代码中,因为运算符重载函数在类中,所以存在this指针,所以只需要传递一个参数(加上this指针参数和额外的参数一共两个参数对应==操作符的操作数个数),并且形参对象引用d指的是第二个操作数,因为d1 == d2等价于d1.operator==(d2),因为是d1在调用运算符重载函数,所以this指针指向的对象即为d1

赋值运算符重载函数

赋值运算符重载函数也是运算符重载函数中的一种,因为重载的运算符为赋值运算符=,重载赋值运算符时,首先不能改变赋值运算符的特性,包括连续赋值

赋值运算符重载函数的使用

赋值运算符重载函数的格式

  • 参数类型:const T&,传递引用可以提高传参效率(T为类名)
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值,并且需要检测是否自己给自己赋值,以减少赋值次数
  • 返回*this :要复合连续赋值的含义
#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}void operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;}
};int main()
{Date d(2024, 2, 28);Date d1;d1 = d;d1.print();return 0;
}
输出结果:
2024/2/28

在上面的代码中,类Date中对赋值运算符进行了重载,将引用指向的对象中的值给调用该运算符重载函数的对象,但是上面的代码无法实现赋值运算符的连续赋值,因为没有返回值

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}void operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;}
};int main()
{Date d(2024, 2, 28);Date d1;Date d2;d1 = d;d1.print();d2 = d1 = d;return 0;
}
报错信息:
二元“=”: 没有找到接受“void”类型的右操作数的运算符(或没有可接受的转换)

所以,为了解决这个问题,赋值运算符重载函数需要给定返回值为类类型

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}
};int main()
{Date d(2024, 2, 28);Date d1;Date d2;d2 = d1 = d;d1.print();d2.print();return 0;
}
输出结果:
2024/2/28
2024/2/28

在上面的代码中,赋值运算符重载函数给了返回值为Date类型的引用,此时可以使用连续赋值,因为赋值运算符从右往左结合,所以具体过程为d对象赋值给d1,d1对象的值赋值给d2,从函数调用的角度理解为d2.operator=(d1.operator=(d));(注意不是d2.operator=(d1).operator=(d);,本句理解为d2被赋值为d1中的内容,然后再被赋值为d中的内容,相当于d2 = d1; d2 = d;

📌

赋值运算符重载函数的返回值也可以不用引用,但是此时在返回时会调用拷贝构造函数将返回值的内容拷贝给调用赋值运算符重载函数的对象,为了减少调用拷贝构造的次数,更推荐使用引用,该解释同样适用于形参

另外,还有一个小问题,如果两个相同的对象进行赋值,那么将产生额外的一次赋值,对于这个问题,在赋值时需要判断形参引用的对象和this指针指向的对象是否是同一个地址的对象

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
};int main()
{Date d(2024, 2, 28);d = d;return 0;
}

在上面的代码中,判断this指针指向的对象的地址和引用的对象地址是否相等,如果二者相等,则证明是同一个对象,不需要进行赋值直接返回即可,注意形参的Date &d为创建对象的引用,而if语句中的&d是取引用的地址

注意,赋值运算符重载函数必须作为成员函数,不能作为全局函数

#include <iostream>
using namespace std;class Date
{
public:int _year;int _month;int _day;Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}
};
Date& operator=(Date& d1, Date& d)
{    if (&d1 != &d){d1._year = d._year;d1._month = d._month;d1._day = d._day;}return d1;
}
int main()
{Date d(2024, 2, 28);Date d1;d1 = d;return 0;
}
报错信息:
“operator=”必须是成员函数

赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,所以赋值运算符重载只能是类的成员函数

拷贝构造函数和赋值运算符重载函数

与拷贝构造函数类似,赋值运算符重载函数如果用户没有实现,编译器会自动实现。默认如果不自主实现还是按照字节拷贝,按照字节方式拷贝也会遇到像拷贝函数一样的问题(指对象中有资源申请时)

  1. 一个对象改变,另一个对象也会跟着改变,严重者会数据覆盖
  2. 释放资源时会因为多次释放一个空间导致程序崩溃
#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}};int main()
{Date d(2024, 2, 28);Date d1;d1 = d;//编译器自动生成的默认赋值运算符重载函数Date d2(d);//编译器自动生成的拷贝构造函数d1.print();d2.print();return 0;
}
输出结果:
2024/2/28
2024/2/28

与拷贝构造函数一样,如果类对象中有涉及到资源申请,那么需要自己实现赋值运算符重载函数,否则直接使用默认的即可

重载前置++和后置++

前置++

对于运算符重载函数的使用规则,那么可以很容易写出++的重载函数,如下:

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}void operator++(){_day += 1;}
};int main()
{Date d;++d;d.print();return 0;
}
输出结果:
2024/3/23

因为前++相当于计算+=1,而因为前面实现过获取X天后的日期的函数GetAfterXDays_plusEqual,所以可以直接用该函数进行复用,从而实现++操作

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}int GetMonthDays(int year, int month){int monthDays[] = { 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 monthDays[month] + 1;}   else{return monthDays[month];}}Date& GetAfterXDays_plusEqual(int days){_day += days;while (_day > GetMonthDays(_year, _month)){_day -= GetMonthDays(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;}Date GetAfterXDays_plus(int days){Date tmp(*this);tmp._day += days;while (tmp._day > GetMonthDays(tmp._year, tmp._month)){tmp._day -= GetMonthDays(tmp._year, tmp._month);tmp._month++;if (tmp._month == 13){tmp._year++;tmp._month = 1;}}return tmp;}void operator++(){//因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)*this = GetAfterXDays_plusEqual(1);}
};int main()
{Date d;++d;d.print();return 0;
}
输出结果:
2024/3/23

注意到上面实现的++是无返回值的++运算符重载函数,但是如果函数没有返回值,将无法将++后的值给另外一个对象

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}int GetMonthDays(int year, int month){int monthDays[] = { 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 monthDays[month] + 1;}   else{return monthDays[month];}}Date& GetAfterXDays_plusEqual(int days){_day += days;while (_day > GetMonthDays(_year, _month)){_day -= GetMonthDays(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;}Date GetAfterXDays_plus(int days){Date tmp(*this);tmp._day += days;while (tmp._day > GetMonthDays(tmp._year, tmp._month)){tmp._day -= GetMonthDays(tmp._year, tmp._month);tmp._month++;if (tmp._month == 13){tmp._year++;tmp._month = 1;}}return tmp;}void operator++(){//_day += 1;//因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)*this = GetAfterXDays_plusEqual(1);//return *this;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
};int main()
{Date d;++d;d.print();Date d1;d1 = ++d;return 0;
}
报错信息:
二元“=”: 没有找到接受“void”类型的右操作数的运算符(或没有可接受的转换)

所以为了解决这种问题将考虑为++运算符重载函数加上返回值为类对象引用类型

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}int GetMonthDays(int year, int month){int monthDays[] = { 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 monthDays[month] + 1;}   else{return monthDays[month];}}Date& GetAfterXDays_plusEqual(int days){_day += days;while (_day > GetMonthDays(_year, _month)){_day -= GetMonthDays(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;}Date GetAfterXDays_plus(int days){Date tmp(*this);tmp._day += days;while (tmp._day > GetMonthDays(tmp._year, tmp._month)){tmp._day -= GetMonthDays(tmp._year, tmp._month);tmp._month++;if (tmp._month == 13){tmp._year++;tmp._month = 1;}}return tmp;}Date& operator++(){//_day += 1;//因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)*this = GetAfterXDays_plusEqual(1);return *this;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
};int main()
{Date d;++d;d.print();Date d1;d1 = ++d;return 0;
}
输出结果:
2024/3/23

后置++

上面的函数中实现了前置++,但是并没有实现后置++,如果在没有实现后置++时,使用后置++,则会出现下面的情况

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}int GetMonthDays(int year, int month){int monthDays[] = { 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 monthDays[month] + 1;}else{return monthDays[month];}}Date& GetAfterXDays_plusEqual(int days){_day += days;while (_day > GetMonthDays(_year, _month)){_day -= GetMonthDays(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;}Date GetAfterXDays_plus(int days){Date tmp(*this);tmp._day += days;while (tmp._day > GetMonthDays(tmp._year, tmp._month)){tmp._day -= GetMonthDays(tmp._year, tmp._month);tmp._month++;if (tmp._month == 13){tmp._year++;tmp._month = 1;}}return tmp;}Date& operator++(){//_day += 1;//因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)*this = GetAfterXDays_plusEqual(1);return *this;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
};int main()
{Date d;d++;return 0;
}
报错信息:
二进制“++”:“Date”不定义该运算符或到预定义运算符可接收的类型的转换

所以有前置++的实现并不能同时应用于后置++,对于后置++来说,编译器为了与前置++作区分,需要在形参位置添加一个整型占位形参,如下

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}int GetMonthDays(int year, int month){int monthDays[] = { 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 monthDays[month] + 1;}else{return monthDays[month];}}Date& GetAfterXDays_plusEqual(int days){_day += days;while (_day > GetMonthDays(_year, _month)){_day -= GetMonthDays(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;}Date GetAfterXDays_plus(int days){Date tmp(*this);tmp._day += days;while (tmp._day > GetMonthDays(tmp._year, tmp._month)){tmp._day -= GetMonthDays(tmp._year, tmp._month);tmp._month++;if (tmp._month == 13){tmp._year++;tmp._month = 1;}}return tmp;}//前置++Date& operator++(){//_day += 1;//因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)*this = GetAfterXDays_plusEqual(1);return *this;}//后置++,但是为了不同于前置++,在形参处加入int形参作为占位便于编译器区分Date operator++(int){//后置++满足先使用再++,所以返回值为原值Date tmp(*this);*this = GetAfterXDays_plusEqual(1);return tmp;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
};int main()
{Date d;d++;d.print();return 0;
}
输出结果:
2024/3/23

在上面的代码中,对于后置++重载函数来说,在形参处添加了一个int类型形参作为占位符,这个形参可以不给形参名,因为只是编译器用于区分

重载流插入<<与流提取>>

在C++标准库中,coutcin是属于iostreamostreamistream的对象,对于流插入<<运算符,之所以cout输出可以不用指定占位符编译器可以自动匹配的原因是ostream<<的运算符重载和函数重载,对于内置类型来说,有下面的函数重载

同样对于流提取运算符>>来说也是如此,如下图所示

但是流插入和流提取运算符并没有对自定义类型有函数重载,所以可以对流提取运算符和流插入运算符进行函数重载

流插入运算符<<重载

按照前面的运算符重载思路可以写出下面的代码

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}void operator<<(ostream& cout){cout << _year << "/" << _month << "/" << _day << endl;}
};int main()
{Date d;cout << d;return 0;
}
报错信息:
二元“<<”: 没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)

在上面的代码中,虽然重载了<<,但是形参是ostream流的对象,而隐含的形参是this,而在运算符重载函数形参列表的规则中,对于有两个操作数的运算符重载来说,第一个参数为左操作数,第二个参数为右操作数,所以上面的代码调用应该为d << cout

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}void operator<<(ostream& cout){cout << _year << "/" << _month << "/" << _day << endl;}
};int main()
{Date d;//cout << d;d << cout;return 0;
}
输出结果:
2024/3/22

但是和正常的输出刚好顺序相反,所以这种方法需要改变,但是因为不能改变this在形参的位置,所以考虑到将<<重载放置到全局中,此时可以决定两个操作数的顺序,但是这个方法就不能使用this指针,并且需要考虑到成员变量的private属性,本处给出一种解决方案是使用get函数

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}int getYear(){return _year;}int getMonth(){return _month;}int getDay(){return _day;}
};void operator<<(ostream& cout, Date& d)
{cout << d.getYear() << "/" << d.getMonth() << "/" << d.getDay() << endl;
}int main()
{Date d;cout << d;return 0;
}
输出结果:
2024/3/22

在上面的代码中,将流插入运算符重载函数放置到全局中可以控制coutd的顺序,此时即可写为cout << d,但是因为上面的<<并没有返回值,所以不可以连续插入,所以可以改进为如下

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}int getYear(){return _year;}int getMonth(){return _month;}int getDay(){return _day;}
};ostream& operator<<(ostream& cout, Date& d)
{cout << d.getYear() << "/" << d.getMonth() << "/" << d.getDay() << endl;return cout;
}int main()
{Date d;Date d1;cout << d << d1 << endl;return 0;
}
输出结果:
2024/3/22
2024/3/22

流提取运算符>>重载

对于流提取运算符>>的重载类似于流插入运算符<<,但是此时不能使用获取函数,所以对于流提取运算符的重载来说,考虑用友元解决,使用友元可以让全局函数中的对象获取到private属性的变量

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}//友元friend istream& operator>>(istream& cin, Date& d);int getYear(){return _year;}int getMonth(){return _month;}int getDay(){return _day;}
};ostream& operator<<(ostream& cout, Date& d)
{cout << d.getYear() << "/" << d.getMonth() << "/" << d.getDay() << endl;return cout;
}istream& operator>>(istream& cin, Date& d)
{cin >> d._year >> d._month >> d._day;return cin;
}int main()
{Date d;Date d1;cin >> d >> d1;cout << d << d1 << endl;return 0;
}
输入:
2024 2 28 2024 3 31
输出结果:
2024/2/28
2024/3/31

使用友元解决流插入运算符的重载函数

#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2024, int month = 3, int day = 22){_year = year;_month = month;_day = day;}//友元friend istream& operator>>(istream& cin, Date& d);friend ostream& operator<<(ostream& cout, Date& d);
};ostream& operator<<(ostream& cout, Date& d)
{cout << d._year << "/" << d._month << "/" << d._day << endl;return cout;
}istream& operator>>(istream& cin, Date& d)
{cin >> d._year >> d._month >> d._day;return cin;
}int main()
{Date d;Date d1;cin >> d >> d1;cout << d << d1 << endl;return 0;
}
输入:
2024 2 23 2022 3 31
输出结果:
2024/2/23
2022/3/31

const成员函数

const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

在前面的运算符重载函数中,当运算符重载函数在全局时不可以使用const修饰形式参数,因为const成员变量传递给成员函数时涉及到引用权限放大,那么const成员函数就是用来解决这种权限放大问题,可以将权限保持不变或者缩小,例如在下面的代码中

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}int getYear() const{return _year;}int getMonth() const{return _month;}int getDay() const{return _day;}
};//全局运算符重载函数,const形参
bool operator==(const Date& d1, const Date& d2)
{return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}int main()
{Date d1;Date d2(d1);cout << (d1 == d2) << endl;//有重载==的函数时可以比较return 0;
}
输出结果:
1

在上面的代码中,使用const修饰了==运算符重载函数的两个形式参数,此时d1d2不可以被修改,当对象d1d2调用get系列函数时,成员函数的形式参数需要保证获得的权限不被放大,所以需要修饰形式参数,但是因为this指针不可以直接显式做形式参数,所以不可以使用const显式对this指针进行修饰,此时就需要将const放置到函数名后,作为修饰this指针的const以满足指针及引用权限不被放大

但是,如果==运算符重载函数中的两个形式参数并不是const修饰的变量,此时调用const成员函数也不会有错,因为此时是权限的缩小(从可修改到成员函数的不可修改),如下面的代码

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}int getYear() const{return _year;}int getMonth() const{return _month;}int getDay() const{return _day;}
};//全局运算符重载函数,非const形参
bool operator==(Date& d1, Date& d2)
{return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}int main()
{Date d1;Date d2(d1);cout << (d1 == d2) << endl;//有重载==的函数时可以比较return 0;
}

对于const成员函数和非const成员函数之间也是如此

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}//非const成员函数void printYear(){cout << _year;}    int getYear() const{printYear();return _year;}
};int main()
{Date d1;Date d2(d1);return 0;
}
报错信息:
“void Date::printYear(void)”: 不能将“this”指针从“const Date”转换为“Date &”

而对于const成员函数和const成员函数之间可以直接调用

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}//非const成员函数void printYear() const{cout << _year;}    int getYear() const{printYear();return _year;}
};int main()
{Date d1;Date d2(d1);return 0;
}

同样非const成员函数可以调用const成员函数

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}//非const成员函数void printYear() const{cout << _year;}    int getYear() {printYear();return _year;}
};int main()
{Date d1;Date d2(d1);return 0;
}

取地址操作符重载与const成员取地址操作符重载

在C++中,这两个运算符重载函数可以不用显式定义,编译器会默认生成

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}};int main()
{Date d1;Date d2;const Date d3;cout << &d1 << endl;cout << &d2 << endl;cout << &d3 << endl;return 0;
}
输出结果:
00000031E9FEF628
00000031E9FEF658
00000031E9FEF688

也可以显式定义

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}Date* operator&(){return this;}const Date* operator&()const{return this;}};int main()
{Date d1;Date d2;const Date d3;cout << &d1 << endl;cout << &d2 << endl;cout << &d3 << endl;return 0;
}
输出结果:
0000006B086FFC38
0000006B086FFC68
0000006B086FFC98

只有特殊情况,才需要重载这两个函数,比如想让别人获取到指定的内容,让其访问非法地址

#include <iostream>
using namespace std;class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2024, int month = 3, int day = 21){_year = year;_month = month;_day = day;}Date* operator&(){return (Date*)0;}const Date* operator&()const{return (Date*)0;}};int main()
{Date d1;Date d2;const Date d3;cout << &d1 << endl;cout << &d2 << endl;cout << &d3 << endl;return 0;
}
输出结果:
0000000000000000
0000000000000000
0000000000000000

实现日期类练习

//头文件
#pragma once#include <iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;public://构造函数Date(int year = 2024, int month = 3, int day = 23):_year(year),_month(month),_day(day){}friend inline istream& operator>>(istream& cin, Date& d);friend inline ostream& operator<<(ostream& cout, Date& d);//获取月份日期函数int GetMonthDays(int year, int month);//+=运算符重载Date& operator+=(int days);//+运算符重载Date operator+(int days){Date tmp(*this);//复用+=重载tmp += days;return tmp;}//赋值运算符重载Date& operator=(const Date& d);//前置++运算符重载Date& operator++(){//复用+=重载*this += 1;return *this;}//后置++运算符重载Date operator++(int){//复用+=函数Date tmp(*this);tmp += 1;return tmp;}//>=运算符重载bool operator>=(const Date& d) const;//<=运算符重载bool operator<=(const Date& d) const;//<运算符重载bool operator<(const Date& d) const{//<的对立事件为>=,故直接对>=取反return !(*this >= d);}//>运算符重载bool operator>(const Date& d) const{//>的对立事件为<=,故直接对<=取反return !(*this <= d);}//==运算符重载bool operator==(const Date& d) const{return _year == d._year && _month == d._month && _day == d._day;}//!=运算符重载bool operator!=(const Date& d) const{//!=的对立事件为==,故直接对==取反return !(*this == d);}//-=运算符重载Date& operator-=(int days);//-运算符重载Date operator-(int days){Date tmp(*this);tmp._day -= days;return tmp;}//前置--运算符重载Date& operator--(){//复用-=重载函数*this -= 1;return *this;}//后置--运算符重载Date operator--(int){//复用-=重载Date tmp(*this);*this -= 1;return tmp;}//日期-日期int operator-(const Date& d);
};inline ostream& operator<<(ostream& cout, Date& d)
{cout << d._year << "/" << d._month << "/" << d._day << endl;return cout;
}inline istream& operator>>(istream& cin, Date& d)
{cin >> d._year >> d._month >> d._day;return cin;
}//实现文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date_class.h"//获取月份日期函数
int Date::GetMonthDays(int year, int month)
{int monthDays[] = { 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 monthDays[month] + 1;}return monthDays[month];
}//+=运算符重载
Date& Date::operator+=(int days)
{_day += days;while (_day > GetMonthDays(_year, _month)){_day -= GetMonthDays(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;
}//赋值运算符重载
Date& Date::operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}//>=运算符重载
bool Date::operator>=(const Date& d) const
{//如果年大就直接返回trueif (_year>d._year){return true;}else if(_year == d._year && _month > d._month)//年相等时比较月份,月份大就直接返回true{return true;}else if (_year == d._year && _month == d._month && _day > d._day)//年相等,月份相等时,天大就直接返回true{return true;}else//其他情况均返回false{return false;}
}//<=运算符重载
bool Date::operator<=(const Date& d) const
{//如果年小就直接返回trueif (_year < d._year){return true;}else if (_year == d._year && _month < d._month)//年相等时比较月份,月份小就直接返回true{return true;}else if (_year == d._year && _month == d._month && _day < d._day)//年相等,月份相等时,天小就直接返回true{return true;}else//其他情况均返回false{return false;}
}//-=运算符重载
Date& Date::operator-=(int days)
{_day -= days;while (_day <= 0){_month--;if (_month == 0){_year--;_month = 12;}_day += GetMonthDays(_year, _month);}return *this;
}//日期-日期
int Date::operator-(const Date& d)
{Date maxYear = *this;Date minYear = d;int flag = 1;if (maxYear < minYear){maxYear = d;minYear = *this;flag = -1;}int count = 0;while (maxYear != minYear){count++;++minYear;}return count * flag;
}//测试文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date_class.h"int main()
{Date d;Date d1(d);Date d2(2023, 1, 1);d--;cout << d;cout << (d != d1) << endl;cout << (d >= d1) << endl;Date d3;d3 = --d2;cout << d3;Date d4(2023, 2, 7);d4 -= 100;cout << d4;cout << d4 - d1 << endl;Date d5;Date d6;cin >> d5 >> d6;cout << d5 << d6 << endl;return 0;
}
输入:
2024 3 23
2023 2 23
输出结果:
2024/3/22
1
0
2022/12/31
2022/10/30
-510
2024/3/23
2023/2/23

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/767447.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Navicat 干货 | 探索 PostgreSQL 的外部数据包装器和统计函数

PostgreSQL 因其稳定性和可扩展性而广受青睐&#xff0c;为开发人员和数据管理员提供了许多有用的函数。在这些函数中&#xff0c;file_fdw_handler、file_fdw_validator、pg_stat_statements、pg_stat_statements_info 以及 pg_stat_statements_reset 是其中的重要函数&#x…

鸿蒙:@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

在实际应用开发中&#xff0c;应用会根据开发需要&#xff0c;封装自己的数据模型。对于多层嵌套的情况&#xff0c;比如二维数组&#xff0c;或者数组项class&#xff0c;或者class的属性是class&#xff0c;他们的第二层的属性变化是无法观察到的。这就引出了Observed/Object…

AJAX介绍使用案例

文章目录 一、AJAX概念二、AJAX快速入门1、编写AjaxServlet&#xff0c;并使用response输出字符&#xff08;后台代码&#xff09;2、创建XMLHttpRequest对象&#xff1a;用于和服务器交换数据 & 3、向服务器发送请求 & 4、获取服务器响应数据 三、案例-验证用户是否存…

由浅入深一步步了解什么是哈希(概念向)

文章目录 什么是哈希哈希函数直接定址法除留余数法 哈希冲突闭散列线性探测法二次探测法负载因子和闭散列的扩容 开散列开散列的扩容 非整形关键码 什么是哈希 我们来重新认识一下数据查找的过程&#xff1a; 在顺序结构以及平衡树中&#xff0c;记录的关键码与其存储位置之间…

【自然语言处理】统计中文分词技术(一):1、分词与频度统计

文章目录 一、词与分词1、词 vs 词素2、世界语言分类 二、分词的原因与基本原因1、为什么要分词2、分词规范3、分词的主要难点-切分歧义如何排除切分歧义利用词法信息利用句法信息利用语义信息利用语用、语境信息 4、分词的主要难点-未登录词未登录词如何识别未登录词 三、分词…

Docker入门到实践之环境配置

Docker入门到实践之环境配置 docker 环境安装 Ubuntu/Debian: sudo apt update sudo apt install docker.ioCentOS/RHEL: sudo yum install dockerArch Linux: sudo pacman -S docker如果未安装成功&#xff0c;或者env的path未设置成功&#xff0c;运行时会报错 Bash: Do…

[HackMyVM]靶场 Slowman

kali:192.168.56.104 靶机:192.168.56.132 端口扫描 # nmap 192.168.56.132 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-03-24 15:28 CST Nmap scan report for 192.168.56.132 Host is up (0.00066s latency). Not shown: 995 filtered tcp ports (no-response) …

NX二次开发-调内部函数创建进度条MT_create_progress_bar

一、概述 最近学习NX二次开发&#xff0c;看到NX打开装配模型或者加载模型时会显示进度条的问题&#xff0c;个人觉得很有意思&#xff0c;然后参考阿飞2018中的文章进行学习。 二、代码解析 //User Defined Header File#include <uf.h>#include <uf_ui.h>#includ…

使用 React antd 的ProFormSelect组件 搜索查询 多选的写法

使用 React antd 的ProFormSelect组件 搜索查询 多选的写法 需求&#xff1a;需要一个搜索框&#xff0c;可以选择员工&#xff0c;&#xff08;员工人数多无法一次性获取&#xff0c;全部放入options中&#xff09;&#xff0c;所以需要使用搜索功能&#xff0c;而且是可以多…

HarmonyOS NEXT应用开发之ArkWeb同层渲染

介绍 该方案展示了ArkWeb同层渲染&#xff1a;将系统原生组件直接渲染到前端H5页面上&#xff0c;原生组件不仅可以提供H5组件无法实现的一些功能&#xff0c;还能提升用户体验的流畅度 效果图预览 使用说明 进入页面即可看到同层渲染效果&#xff0c;Text&#xff0c;searc…

数据库系统概论(超详解!!!) 第四节 关系数据库标准语言SQL(Ⅰ)

1.SQL概述 SQL&#xff08;Structured Query Language&#xff09;结构化查询语言&#xff0c;是关系数据库的标准语言 SQL是一个通用的、功能极强的关系数据库语言 SQL的动词 基本概念 基本表 &#xff1a;本身独立存在的表&#xff1b; SQL中一个关系就对应一个基本表&am…

SecureCRT:高效安全的远程连接工具

SecureCRT是一款功能强大的终端仿真工具&#xff0c;主要用于连接和运行包括Windows、UNIX和VMS在内的远程系统。它支持多种协议&#xff0c;如SSH1、SSH2、Telnet、SFTP、Rlogin、Serial、SCP等&#xff0c;确保用户与目标设备之间的通信安全&#xff0c;并防止网络攻击和窥探…

HTTP系列之HTTP缓存 —— 强缓存和协商缓存

文章目录 HTTP缓存强缓存协商缓存状态码区别缓存优先级如何设置强缓存和协商缓存使用场景 HTTP缓存 HTTP缓存时利用HTTP响应头将所请求的资源在浏览器进行缓存&#xff0c;缓存方式分两种&#xff1a;强缓存和协商缓存。 浏览器缓存是指将之前请求过的资源在浏览器进行缓存&am…

安卓findViewById 的优化方案:ViewBinding与ButterKnife(一)

好多小伙伴现在还用findViewById来获取控件的id, 在这里提供俩种替代方案&#xff1a;ViewBinding与ButterKnife&#xff1b; 先来说说ButterKnife ButterKnife ButterKnife是一个专注于Android系统的View注入框架&#xff0c;在过去的项目中总是需要很多的findViewById来查…

java 实现发送邮件功能

今天分享一篇 java 发送 QQ 邮件的功能 环境&#xff1a; jdk 1.8 springboot 2.6.3 maven 3.9.6 邮件功能依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId>&…

idea打包war包部署到tomcat以及访问路径问题

idea将web项目打包成war最重要的是配置atrificats。 首先打开file -》 project structure 创建之后&#xff0c;output directory即为输出war包的路径。Name可以随意&#xff0c;之后点击绿色&#xff0c;打开directory content 选择webapp目录&#xff0c;记得勾选include in…

(C语言)浮点数在内存中的存储详解

1. 浮点数 常见的浮点数&#xff1a;3.14159、 1E10等 &#xff0c;浮点数家族包括&#xff1a; float、double、long double 类型。 浮点数表示的范围&#xff1a; float.h 中定义. 2. 浮点数的存储 我们先来看一串代码&#xff1a; int main() {int n 9;float* pFloa…

安全工具介绍 SCNR/Arachni

关于SCNR 原来叫Arachni 是开源的&#xff0c;现在是SCNR&#xff0c;商用工具了 可试用一个月 Arachni Web Application Security Scanner Framework 看名字就知道了&#xff0c;针对web app 的安全工具&#xff0c;DASTIAST吧 安装 安装之前先 sudo apt-get update sudo…

嵌入式数据库--SQLite

目录 1. SQLite数据库简介 2. SQLite数据库的安装 方式一&#xff1a; 方式二&#xff1a; 3. SQLite的命令用法 1.创建一个数据库 2.创建一张表 3.删除表 4.插入数据 5. 查询数据 6.删除表内一条数据 7.修改表中的数据 8.增加一列也就是增加一个字段 1. SQLite数据库…

LeetCode每日一题——统计桌面上的不同数字

统计桌面上的不同数字OJ链接&#xff1a;2549. 统计桌面上的不同数字 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 思路&#xff1a; 这是一个很简单的数学问题&#xff1a; 当n 5时&#xff0c;因为n % 4 1&#xff0c;所以下一天4一定会被放上桌面 当n 4…