目录
1、类中的6个默认成员函数
2、构造函数
2.1 概念
2.2 特性
3、析构函数
3.1 概念
3.2 特性
4、拷贝构造函数
4.1 概念
4.2 特征
5、赋值运算符重载
5.1 运算符重载
5.1.1 全局的operator
编辑
5.1.2 成员函数的operator
5.2 赋值运算符重载
6、创建Date类
7、const成员
7.1 对象调用const成员函数
7.2 成员函数调用const成员函数
编辑
8、取地址及const取地址操作符重载
8.1 取地址操作符
8.2 const取地址操作符
9、用const修改Date类
1、类中的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员
函数。
默认成员函数:用户没有显式实现(即自定义),编译器会生成的成员函数称为默认成员函数
这6个默认函数都是不写才会默认生成
2、构造函数
2.1 概念
对于Date类,每次创建对象时都需要对这个对象进行初始化,否则就会是随机值,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证
每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
2.2 特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。
此时可能会有疑问,这不还是随机数吗,怎么就有调用默认的构造函数呢?
所以默认生成无参构造函数时
1、针对内置类型(int/char/.../指针)的成员变量没有做处理(有些编译器会处理,语法并没有规定)
2、针对自定义类型(class/struct/enum)的变量,调用了他的无参构造函数进行初始化
但若是Time类中没有自定义的构造函数,任然会是随机值
若Time类中只有自定义的带参构造函数,则会报错
这里说的无参构造是指可以不传参就调用的构造函数(即第6点中的默认构造函数)
所以,自动生成的构造函数的意义就在于初始化自定义类型的对象
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明
时可以给默认值
6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数,也就是说,这3个函数只能存在一个
这三个函数的共同特点:不需要传参数
在前面,我们自定义构造函数时,一般要定义两个,一个无参,一个有参,这样子太麻烦了,所以我们可以直接定义一个全缺省的构造函数,这样子就只需要定义一个了
为什么无参的构造函数和全缺省的构造函数不能同时存在?
因为不传参时不知道调用谁
3、析构函数
3.1 概念
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的(对象会随着栈帧的销毁而销毁)。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作(如释放掉动态开辟出的空间,关闭掉打开的文件等)。
注意:这里的析构函数是先处理s2再处理s1,因为是按栈帧从底向上来的
3.2 特性
析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
5. 内置类型(基本类型),默认的析构函数不会处理,而自定义类型,默认的析构函数会调用它的析构函数来进行清理,这一点与构造函数类似
程序运行结束后输出:Time
在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,
_day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数中不能直接调用Time类的析构函数,因为实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁,main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
4、拷贝构造函数
4.1 概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存
在的类类型对象创建新对象时由编译器自动调用。
4.2 特征
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用
可是为什么拷贝构造函数的形参一定要是引用呢?
通常,为了防止d1中的值被修改,还会在引用前加const
此时还有一个问题,为什么下面这幅图的func函数不需要用引用呢?
此时会发现,调用了Date中的拷贝构造函数,原因是调用func,会传参,传参是拷贝构造(用d1构造d),因为此时不是内置类型,所以会调用拷贝构造函数,且类型是Date,所以会去Date类中调用拷贝构造函数,Date类中的拷贝构造函数是用引用写好了的,所以是正常使用的,只会调用1次,正常拷贝完成以后就会进入func函数,使用是正常的。当然,func函数还是用引用写会更好。
其实,自定义类型传参最后都用引用,否则每次都调用一次拷贝构造会有消耗。
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的。
4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
3、4点与下面的赋值运算符重载是相似的,放在下面一起讲解
5、赋值运算符重载
5.1 运算符重载
自定义类型不能直接比较大小或赋值(因为是自己定义的类型,编译器不知道这种类型的比较规则,内置类型是编译器知道的,所以可以直接比较),若自己写比较函数,可读性不强。
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
1. 不能通过连接其他符号来创建新的操作符:比如operator@
2. 重载操作符必须有一个类类型参数
3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this
5. .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
5.1.1 全局的operator
自定义类型在比较时,会自动调用这个重载函数
在全局的operator中,类中的成员变量只能定义为public,因为成员变量通常都是private,所以,将operator定义为成员函数会更好
5.1.2 成员函数的operator
5.2 赋值运算符重载
在刚创建一个类的对象时,可以用已经有的对象通过拷贝构造来给这个对象赋值,那如果这个对象已经存在了,如何通过已经存在的对象来给i这个对象赋值呢?
这时候就需要用到赋值运算符重载,即operator=
但是上面的代码是不够完美的,因为operator=的返回值是void,没办法完成连续赋值的操作。若d2=d1=d3,则在第一个=哪里会报错
比方说i=j=k,是将k的值先赋值给j,然后j=k这个表达式产生一个返回值j,再将返回值j赋值给i
所以opertaor=应该要产生返回值才是完美的,像d2=d1=d3,后面的要返回d1,再将d1赋值给d2,且这里的出了作用域还在,所以可以引用返回
此时会发现,将拷贝构造函数和赋值运算符重载函数都删掉了,拷贝构造和赋值运算符重载依然可以正常进行,这是为什么呢?
因为拷贝构造和赋值运算符重载都是6个默认成员函数之一,如果我们不写,系统也会自动生成,我们现在需要做的就是了解系统默认生成的拷贝构造函数和赋值运算符重载函数和我们自己写的有什么区别?(默认拷贝构造和默认赋值运算符重载是一样的,所以一起讲)
但是像栈的类就是需要自己定义
此时程序会崩溃,为什么呢?
所以栈的类要实现自己的深拷贝的拷贝构造和operator=
注意:operator中只有=是默认成员函数,其他的像>、<等都是需要自己定义的
6、创建Date类
需要实现的目标:
a. 判断两日期是否==、<、>、<=、>=、!=
b. 日期加天数、减天数,两日期相减计算中间有多少天(两日期相加是没有意义的)
c. 日期前置++、--,后置++、--
class Date
{
public:int GetMonthDay(int year, int month)//获取每个月的天数,以判断日期是否合法{//这里用static修饰,因为会创建多个Date类对象,防止每个对象都要创建一次数组static int monthDays[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;return monthDays[month];}//在构造函数中,需要检查一下创建日期是否合法//这里也充分体现了封装的好处,成员变量都是private的,不封装时谁都可以改,封装之后都是通过构造函数创建的,可以保证都是合法的Date(int year = 0, int month = 1, int day = 1){if (year >= 0&& month >= 1 && month <= 12&& day >= 1 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}else{cout << "日期不合法" << endl;}}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}//因为是自定义类型传参,所以用引用接收inline bool operator==(const Date& d)//bool operator==(Date* this,const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}inline bool operator>(const Date& d)//因为后面经常复用,所以用内联,其实成员函数都是内联的{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;return false;}bool operator>=(const Date& d){//第一种方法,与>类似//年小就是小,年相等月小就是小,年月相等,日>=就是>=//但是这样子不太好,因为重复度过高,若类的成员变量增加了,要修改的地方会很多/*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;return false;*///第二种方法,利用前面的==和>return *this > d || *this == d;}bool operator<(const Date& d){return !(*this >= d);}bool operator<=(const Date& d){return !(*this > d);}bool operator!=(const Date& d){return !(*this == d);}Date operator+(int day)//d1+10,这里是不改变d1的,这里不能引用返回,因为里面返回的是临时变量{//Date ret(*this);//先用d1拷贝构造一个ret//ret._day += day;//while (ret._day > GetMonthDay(ret._year, ret._month))//{// //日期不合法,就需要按月进位// ret._day -= GetMonthDay(ret._year, ret._month);// ret._month++;// if (ret._month == 13)// {// ret._year++;// ret._month = 1;// }//}//return ret;//此时完全可以复用Date ret(*this);//先用d1拷贝构造一个retret += day;return ret;}Date& operator += (int day)//d1+=10,这里是对外部的d1修改,不是临时变量,所以可以传引用返回{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 operator-(int day)//Date d2 = d1 - 10;{//Date ret(*this);//先用d1拷贝构造一个ret//ret._day -= day;//while (ret._day <= 0)//{// ret._month--;// if (ret._month == 0)// {// ret._month = 12;// ret._year--;// }// ret._day += GetMonthDay(ret._year, ret._month);//}//return ret;//复用Date ret(*this);//先用d1拷贝构造一个retret -= day;return ret;}Date& operator -= (int day){if (day < 0){return *this += -day;}_day -= day;while (_day <= 0){_month--;if (_month == 0){_month = 12;_year--;}_day += GetMonthDay(_year, _month);}return *this;}Date& operator++()//默认是前置++{/*_day++;if (_day > GetMonthDay(_year, _month)){_day = 1;_month++;if (_month == 13){_month = 1;_year++;}}return *this;*///复用*this += 1;return *this;}// 后置++,为了构成函数重载,编译器加了一个参数,可以不用传参的// ++d1 -> d1.operator++(&d1) d1++ -> d1.operator(&d1,0),这里也不一定是0,就是一个int类型的值// 区别是,前置返回的是加之后的,后置返回的加之前的,无论是调用前置还是后置,都确实加1了,只是返回的不一样// 注意:前置++可以用引用返回,而后置++不能用引用返回Date operator++(int)//这里int后面加不加i都可以{Date tmp(*this);*this += 1;return tmp;}//自定义类型用前置++、--更好,减少拷贝Date& operator--()//默认是前置--{/*_day--;if (_day <= 0){_month--;if (_month == 0){_month = 12;_year--;}_day += GetMonthDay(_year, _month);}return *this;*/*this -= 1;return *this;}Date operator--(int)//这里int后面加不加i都可以{Date tmp(*this);*this -= 1;return tmp;}int operator-(const Date& d){//int ret = 0;//if (_year > d._year)//{// int y = _year - 1;// while (y > d._year)// {// for (int i = 1; i <= 12; i++)// {// ret += GetMonthDay(y, i);// }// y--;// }//循环出来之后,ret是两年之间隔着的那一年的天数,如2024 4 4和 2020 9 10,出循环的ret就是2021,2022,2023这三年的天数总和// ret += _day;// for (int i = _month - 1; i > 0; i--)// {// ret += GetMonthDay(_year, i);// }// ret += GetMonthDay(d._year, d._month) - d._day;// for (int i = d._month + 1; i < 13; i++)// {// ret += GetMonthDay(d._year, i);// }// return ret;//}//else if (_year < d._year)//{// int y = d._year - 1;// while (y > _year)// {// for (int i = 1; i <= 12; i++)// {// ret += GetMonthDay(y, i);// }// y--;// }// ret += d._day;// for (int i = d._month - 1; i > 0; i--)// {// ret += GetMonthDay(d._year, i);// }// ret += GetMonthDay(_year, _month) - _day;// for (int i = _month + 1; i < 13; i++)// {// ret += GetMonthDay(_year, i);// }// return -ret;//}//else// return ret;// 也可以复用前面的重载int flag = 1;//flag用来标记最后结果是正还是负Date max = *this;Date min = d;//虽然d是const Date*,但是这里仍然可以直接Date,因为这里是赋值,不是引用if (*this < d){max = d;min = *this;flag = -1;}int n = 0;//让小的加,看加多少次等于大的while (min != max){++min;//自定义类型用前置++更好,减少拷贝++n;}return n * flag;}
private:int _year;int _month;int _day;
};
7、const成员
7.1 对象调用const成员函数
此时会发现函数f1中没办法调用Print,因为f1中的d是只读的,而Print中的this指针是可读可写的,属于权限的放大,所以会报错
需要修改成下面这样
7.2 成员函数调用const成员函数
此时会发现,f1中调用f2可以,f4中调用f3不行。因为f1中的this指针是可读可写的,而f2中的this只可读的,属于权限的缩小,故可以。f4中的this指针是只可读的,而f3中的this指针是可读可写的,属于权限的放大,所以不行。
请思考下面的几个问题:
1. const对象可以调用非const成员函数吗?不行,权限放大
2. 非const对象可以调用const成员函数吗?可以,权限缩小
3. const成员函数内可以调用其它的非const成员函数吗?不行,权限放大
4. 非const成员函数内可以调用其它的const成员函数吗?可以,权限缩小
8、取地址及const取地址操作符重载
8.1 取地址操作符
6个默认成员函数中有一个是取对象地址的
对于这个默认成员函数,也可以自定义实现
8.2 const取地址操作符
此时会发现,d1,d2调用了自定义的取地址函数,而d3没有调用,因为d3是const的
但是这两个默认成员函数通常不会重载,除非某个类不想让别人拿到对象的地址
9、用const修改Date类
根据我们前面创建的Date类,会发现f1中的程序会出现错误,为什么呢?
因为在前面创建的Date类中,两日期相减的运算符重载的this指针是可读可写的,而f1中的d1和d2都是只可读的,属于权限的放大,属于会报错
形参的引用一般加const:a.防止被修改
b. 传参时,实参是const或非const都可以传
this是否加const(即是否给成员函数加const):只要成员函数中不需要修改成员变量,最好 都加上const
Date.h
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:int GetMonthDay(int year, int month) const;Date(int year, int month, int day);//构造函数不能加const,因为里面修改了成员变量//这里声明与定义分离,缺省参数应该放在定义哪里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) const;Date& operator += (int day);//不能加const,因为修改了成员变量Date operator-(int day) const;Date& operator -= (int day);//不能加const,因为修改了成员变量Date& operator++();//不能加const,因为修改了成员变量Date operator++(int);//不能加const,因为修改了成员变量Date& operator--();//不能加const,因为修改了成员变量Date operator--(int);//不能加const,因为修改了成员变量int operator-(const Date& d) const;
private:int _year;int _month;int _day;
};
Date.cpp
#include"Date.h"
int Date::GetMonthDay(int year, int month) const
{static int monthDays[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;return monthDays[month];
}
Date::Date(int year = 0, int month = 1, int day = 1)
{if (year >= 0&& month >= 1 && month <= 12&& day >= 1 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}else{cout << "日期不合法" << endl;}
}
void Date::Print() const
{cout << _year << "-" << _month << "-" << _day << endl;
}
bool Date::operator==(const Date& d) const
{return _year == d._year&& _month == d._month&& _day == d._day;
}
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;return 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);
}
bool Date::operator!=(const Date& d) const
{return !(*this == d);
}
Date Date::operator+(int day) const
{Date ret(*this);ret += day;return ret;
}
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) const
{Date ret(*this);ret -= day;return ret;
}
Date& Date::operator -= (int day)
{if (day < 0){return *this += -day;}_day -= day;while (_day <= 0){_month--;if (_month == 0){_month = 12;_year--;}_day += GetMonthDay(_year, _month);}return *this;
}
Date& Date::operator++()
{*this += 1;return *this;
}
Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;
}
Date& Date::operator--()
{*this -= 1;return *this;
}
Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}
int Date::operator-(const Date& d) const
{int flag = 1;Date max = *this;Date min = d;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;}return n * flag;
}