1.概念
类中有六个默认成员函数,在一个空类中,编译器会自动生成这六个默认成员函数(默认构造函数,析构函数,拷贝构造函数,移动构造函数,拷贝赋值运算符和移动赋值运算符)
构造和析构我们之前讲过,我们来看拷贝构造和运算符重载
2.拷贝构造
拷贝构造只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),然后用已存在的类类型对象创建新对象,由编译器调用。
特征:拷贝构造是一种特殊的成员函数,拷贝构造函数是构造函数的一个重载形式,拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器会直接报错,因为会引发无穷递归调用。
class Date
{
public:
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
//正确写法
Date(const Date& date);
//Date(const Date date);这样写,在调用拷贝构造的时候,会一直调用下去
//Date d1(d) 因为当不加&,调用拷贝构造函数时,编译器会尝试创建d,并将d作为参数传给d1
//在参数传递的过程中,如果参数为const Date date(并非const Date& date)
//那么编译器会先创建一个Date对象,即date, 然后将d的副本传给构造函数
//此过程会导致拷贝构造函数递归调用,因为在创建date的时候,需要调用拷贝构造
//不加&,每次调用的时候都需要一个新的Date对象作为参数,新的有创建的新的,直到栈溢出
private:
int _year;
int _month;
int _day;
};
若为显示定义拷贝构造,编译器会自动生成默认的拷贝构造函数,默认的拷贝构造函数对象按内存存储按字节完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
class Time
{
public:
Time(int hour = 1, int minute = 1, int second = 1)
{
_hour = hour;
_minute = minute;
_second = second;
}
Time(const Time& time)
{
_hour = time._hour;
_minute = time._minute;
_second = time._second;
std::cout << "Time(const Time & time)" << std::endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date_d
{
private:
//自定义类型
Time t;
//内置类型
int _year;
int _month;
int _day;
};
void teseTime()
{
Date_d d;
//用已存在d拷贝构造d1,此处调用Date_d拷贝构造函数
//未显示定义,编译器会自动生成默认拷贝构造
Date_d d1(d);
}
int main()
{
//testClass();
teseTime();
return 0;
}
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝,而自定义类型是调用其拷贝构造函数完成拷贝的。
一个疑问,既然编译器自动生成的拷贝构造能完成我们的工作,那么还需要我们手动实现吗?
当然需要,编译器只能实现简单的浅拷贝,对于内存拷贝这种深拷贝,并无法实现。
/* 模版(泛型编程) */
template <class T>
class Stack
{
public:
Stack(size_t capacity = 4)
:_capacity(capacity)
,_size(0)
{
_array = new T[_capacity];
}
~Stack()
{
delete[] _array;
}
void push(const T& data)
{
if (_capacity == _size)
{
size_t newCapacity = _capacity * 2;
T* newArray = new T[newCapacity];
for (size_t i = 0; i < _size; i++)
{
newArray[i] = _array[i];
}
delete[] _array;
_array = newArray;
_capacity = newCapacity;
}
_array[_size++] = data;
}
void print()
{
for (int i = 0; i < _size; i++)
{
std::cout << _array[i] << std::endl;
}
}
private:
T* _array;
size_t _size;
size_t _capacity;
};
void testStack()
{
Stack <int>s;
s.push(1);
s.push(2);
s.push(3);
s.push(4);
s.push(5);
s.print();
Stack <int>s1(s);
}
我们完全使用编译器自动生成的浅拷贝,将会得到以下错误
在我们调用构造函数创建s的时候,申请了8个元素的空间,里面存了5个元素(1,2,3,4,5)
生成s1的时候,使用s拷贝构造,而Stack类并没有显示定义拷贝构造函数,则编译器会给Stack类生成一份默认的拷贝构造函数,默认拷贝构造函数是值拷贝,即将s的内容原封不动的拷贝给s1,因此s和s1指向同一块空间,
当程序退出时,s和s1要销毁,s1先销毁,调用s1的析构,将空间释放了,到s的时候,因已经释放过一次,一块内存无法多次释放,最后我们的程序就会崩溃掉。
注:类中无涉及资源申请,可写可不写拷贝构造
拷贝构造的调用场景:1.使用已存在对象创建新对象。2,函数参数类型为类类型对象。3,函数返回值类型为类类型对象
class Date_1
{
public:
Date_1(int year, int minute, int day)
{
std::cout << "Date_1(int,int,int):" << this << std::endl;
}
Date_1(const Date_1& d)
{
std::cout << "Date_1(const Date_1& d):" << this << std::endl;
}
~Date_1()
{
std::cout << "~Date_1():" << this << std::endl;
}
private:
int _year;
int _month;
int _day;
};
Date_1 Test(Date_1 d)
{
Date_1 temp(d);//拷贝构造
return temp;//返回时生成临时对象(拷贝构造)
}
int main()
{
Date_1 d1(2022, 1, 13);//构造
Test(d1);//传值是调用拷贝构造生成d
return 0;
}
为提高效率,尽量使用&
3.赋值重载
c++为了增加代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
关键字operator后面接所需要重载的运算符符号
返回类型:返回值operator操作符(参数列表)
注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整形+,不能改变其含义
作为成员函数重载,其形参看起来比操作数数目少1,因为成员函数的第一参数为隐式类型this
. * :: sizeof ?:无法重载
#pragma once
#include <iostream>
class Date
{
public:
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 获取某年某月的天数
int GetMonthDay(int year, int month) const;
// 拷贝构造函数
// d2(d1)
//正确写法
Date(const Date& date);
//Date(const Date date);这样写,在调用拷贝构造的时候,会一直调用下去
//Date d1(d) 因为当不加&,调用拷贝构造函数时,编译器会尝试创建d,并将d作为参数传给d1
//在参数传递的过程中,如果参数为const Date date(并非const Date& date)
//那么编译器会先创建一个Date对象,即date, 然后将d的副本传给构造函数
//此过程会导致拷贝构造函数递归调用,因为在创建date的时候,需要调用拷贝构造
//不加&,每次调用的时候都需要一个新的Date对象作为参数,新的有创建的新的,直到栈溢出
//重载流插入和流提取
friend std::ostream& operator<<(std::ostream& out, const Date& obj);
friend std::istream& operator>>(std::istream& in, Date& obj);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析构函数
~Date();
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day) const;
// 日期-天数
Date operator-(int day) const;
// 日期-=天数
Date& operator-=(int day);
// 前置++
Date& operator++();
// 后置++
Date operator++(int) const;
// 后置--
Date operator--(int) const;
// 前置--
Date& operator--();
// >运算符重载
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;
// 日期-日期 返回天数
int operator-(const Date& d);
int toDays() const;
private:
int _year;
int _month;
int _day;
};
#include "date.h"
/* 全缺省参数在声明处定义即可 */
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
std::cout << "Date(int year, int month, int day)" << std::endl;
}
/* 因成员函数第一个参数始终为this无法改变,所以使用友元声明(friend)*/
std::ostream& operator<<(std::ostream& out, const Date& obj)
{
out << obj._year << "/" << obj._month << "/" << obj._day << std::endl;
return out;
}
std::istream& operator>>(std::istream& in, Date& obj)
{
in >> obj._year >> obj._month >> obj._day;
return in;
}
/* 拷贝构造函数 */
Date::Date(const Date& d)
{
_day = d._day;
_month = d._month;
_year = d._year;
std::cout << "Date(const Date& d)" << std::endl;
}
/* 析构函数(日期类没有需要释放的内存)*/
Date::~Date()
{
std::cout << "~Date()" << std::endl;
}
/* 运算符重载 */
Date& Date::operator=(const Date& d)
{
std::cout << "Date & Date::operator=(const Date & d)" << std::endl;
if (this == &d)
{
return *this;
}
_day = d._day;
_month = d._month;
_year = d._year;
return *this;
}
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_month = 1;
_year++;
}
}
return *this;
}
int Date::GetMonthDay(int year, int month)const
{
if (month < 1 || month > 12)
{
std::cout << "错误日期" << std::endl;
return -1;
}
int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0))
{
days[2] += 1;
}
return days[month];
}
Date Date::operator+(int day)const
{
Date d(*this);
d += day;
return d;
}
Date& Date::operator-= (int day)
{
while (day > 0)
{
if (_day > day)
{
_day -= day;
day = 0;
}
else
{
day -= _day;
_month--;
if (_month < 1)
{
_month = 12;
_year--;
}
_day = GetMonthDay(_year, _month);
}
}
return *this;
}
Date Date::operator-(int day)const
{
Date d(*this);
d -= day;
return d;
}
Date& Date::operator++()
{
_day++;
if (_day > GetMonthDay(_year, _month))
{
_month++;
_day = 1;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
Date Date::operator++(int)
{
Date d(*this);
++_day;
return d;
}
Date& Date::operator--()
{
_day--;
if (_day < GetMonthDay(2024, 2))
{
_month--;
if (_month < 1)
{
_year--;
_month = 12;
}
_day = GetMonthDay(2024, 2);
}
return *this;
}
Date Date::operator--(int)
{
Date d(*this);
--_day;
return d;
}
bool Date::operator>(const Date& d) const
{
if (_year > d._year) return true;
if (_year < d._year) return false;
if (_month > d._month) return true;
if (_month < d._month) return false;
return _day > d._day;
}
bool Date::operator==(const Date& d)const
{
if (d._year != _year ||
d._month != _month||
d._day != _day)
{
return false;
}
return true;
}
bool Date::operator >= (const Date& d)const
{
if (*this > d || *this == d)
{
return true;
}
return false;
}
bool Date::operator < (const Date& d)const
{
if (*this >= d)
{
return false;
}
return true;
}
bool Date::operator <= (const Date& d)const
{
if (*this > d)
{
return false;
}
return true;
}
bool Date::operator != (const Date& d)const
{
if (*this == d)
{
return false;
}
return true;
}
int Date::toDays() const
{
/* 计算当前年+月+日的总天数 */
int days = _year * 365 + _day;
for (int i = 0; i < _month - 1; ++i)
{
days += GetMonthDay(_year, i + 1);
}
// 加上闰年天数
days += (_year / 4) - (_year / 100) + (_year / 400);
return days;
}
int Date::operator-(const Date& d)
{
return toDays() - d.toDays();
}
对于赋值运算符和拷贝构造的区别:赋值运算符是给已经存在的对象进行赋值操作,而拷贝构造是用于创建一个新的对象时候进行赋值操作。
注:赋值运算符,只能重载为成员函数,不能重载为全局函数(因赋值运算符如果不显示实现,编译器会自动生成一个默认的,此时用户再类外自己实现一个全局的赋值运算符重载,就会和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数)
关于前置++(--)和后置++(--)它们均为一元运算符,为了区分它们后置的都加了一个参数(int),该参数没有实际意义,只为了构成函数重载区分前后置,
关于const成员函数,我们将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表示在给成员函数,无法对类的成员进行修改。