前文提要
本文全文以日期类为例
先简单写一个日期类
class Data{
public:
private:
int _year;
int _month;
int _day;
};
且补充一个小知识
数据类型的划分
内置类型:是编程语言提供的基本数据类型,例如整数、浮点数、字符、布尔值
自定义类型:是开发人员根据应用程序的需求定义的类型,它可以由内置类型组成,也可以由其他自定义类型组成。自定义类型可以通过类、结构体、枚举等方式进行定义。
默认成员函数
默认成员函数是一些 当我们什么都没有编写时,也会自动在类中生成的函数
构造函数
是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成
员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
特征
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
由上述三条可知构造函数的基本特质
先写一个简单的构造函数
//函数名与类名相同 且 无返回值
Date()
{
_year = 0;
_month = 0;
_day = 0;
}
- 构造函数可以重载。
构造函数可以支持重载的化,便可以支持各种各样的初始化
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//全缺省的构造
Date(int year = 0, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
//半缺省的构造
Date(int year, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
由于构造函数是对象实例化时编译器自动调用
虽然无参的构造函数与全缺省的构造函数满足函数重载的要求。
但不满足构造函数的要求,全缺省与无参的函数会引起歧义
当函数调用不传参数时,编译器无法识别调用哪个构造函数
默认构造函数
是一个在创建对象时被自动调用的特殊的构造函数。它通常没有任何参数,并且不执行任何特定的初始化操作。默认构造函数的作用是初始化对象的成员变量,确保对象在创建时处于一个合理的状态。
无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
默认构造函数由编译器自动生成的构造函数,是一个无参的构造函数
构造函数中对于成员对象的处理根据成员对象的类型分为两类
内置类型不处理
自定义类型调用自定义类型的默认构造函数
也因此,建议每个函数都提供默认构造函数。
此外,因内置类型不予处理,c++也时常被人诟病
在C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
故而,整体上我们日期类的构造函数
Date(int year = 1900, int month = 1, int day = 1)
// 拷贝构造函数Date::Date(int year , int month, int day)
{
_year = year;
_month = month;
_day = day;
}
析构函数
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特征
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重
载 - 对象生命周期结束时,C++编译系统系统自动调用析构函数。
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;
- 有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类
回到我们的例子来
Date没有申请空间,析构函数是可以不写的
拷贝构造
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
按定义可知,拷贝构造中有且只含有一个参数->类类型对象
如果直接传类类型对象本身,传值的过程本身就是一次类对象的拷贝
也不可避免的会引起无穷递归调用
-
若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的 -
拷贝构造函数典型调用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
回到例子上来
日期类没有设计资源申请,默认的拷贝函数也足以满足需求
赋值重载
赋值重载也可以是一种特殊的默认成员函数,不手动书写时,会在类中自动生成。具体在操作符重载中细说
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数。也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
原型
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: .注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
赋值运算符的重载
格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义
特征
- 赋值运算符只能重载成类的成员函数不能重载成全局函数
赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的
赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了
2.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝 (与默认拷贝构造类似)
回到例子上来,与拷贝构造同理
前置++ 与 后置++
前置++和后置++都是一元运算符
为了让前置++与后置++形成能正确重载
C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1
以日期类未例
// 前置++
Date& operator++()
{
return (*this) += 1;
}
// 后置++
Date operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
日期类的实现
Date.cpp
#include"Data.h"
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
static int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = days[month];
if ((month == 2) && (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0))
{
day++;
}
return day;
}
// 全缺省的构造函数
Date::Date(int year , int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// d2(d1)
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date.h
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month);
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
// 析构函数
~Date()
{
};
// 日期+=天数
Date& operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
void days()
{
int day = _day;
for (int i = 1; i < _month; i++)
{
day += GetMonthDay(_year, i);
}
cout << "侯稼澍" << _year << ":" << _month << ":" << _day << "为第" << day << "天" << endl;
}
void print()
{
cout << _year << " " << _month << " " << _day << endl;
}
// 日期+天数
Date operator+(int day)
{
Date tmp = *this;
return tmp += day;
}
// 日期-天数
Date operator-(int day)
{
Date tmp = *this;
return tmp -= day;
}
// 日期-=天数
Date& operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// 前置++
Date& operator++()
{
return (*this) += 1;
}
// 后置++
Date operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
// 后置--
Date operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;
}
// 前置--
Date& operator--()
{
return *this -= 1;
}
// >运算符重载
bool operator>(const Date& d)
{
if ((_year > d._year)
|| (_year == d._year && _month > d._month)
|| (_year == d._year && _month == d._month && _day > d._day))
return true;
else
return false;
}
// ==运算符重载
bool operator==(const Date& d)
{
return ((_year == d._year) && (_month == d._month) && (_day == d._day));
}
// >=运算符重载
bool operator >= (const Date& d)
{
return (*this > d) || (*this == d);
}
// <运算符重载
bool operator < (const Date& d)
{
return !(*this >= d);
}
// <=运算符重载
bool operator <= (const Date& d)
{
return (*this < d) || (*this == d);
}
// !=运算符重载
bool operator != (const Date& d)
{
return !(*this == d);
}
// 日期-日期 返回天数
int operator-(const Date& d)
{
int count = 0;
Date big = *this;
Date small = d;
if (small > big)
{
small = *this;
big = d;
}
while (big != small)
{
small++;
count++;
}
return count;
}
private:
int _year;
int _month;
int _day;
};