空类:也就是什么成员都没有的类。
但事实上,空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
概念:
构造函数:创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次
#include
using namespace std;
class Date
{
public:
// 以下构造函数只能存在一个!!!
// 无参默认构造函数
Date()
{
_year = 1900;
_month = 1;
_day = 1;
}
// 全缺省默认构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 编译器生成的默认构造函数
// C++11补丁新增内置类型成员变量(int char double 指针....)在类中声明时可以给默认值,此处仍是声明不是定义!!!
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
// 以下测试函数能通过编译吗?
void Test()
{
Date d1;
}
int main()
{
Test();
return 0;
}
特性:
概念:
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器在栈区上完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作,一般清理的是堆上malloc开辟出来的空间
#include
using namespace std;
class stack
{
public:
void Push(const int& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 半缺省构造函数
stack(int capacity = 10)
{
cout << "构造" << endl;
_array = (int*)malloc(10 * sizeof(int));
if (_array == nullptr)
{
perror("malloc fail");
exit(-1);
}
_size = 0;
_capacity = capacity;
}
// 析构函数
~stack()
{
cout << "析构" << endl;
free(_array);
_array = NULL;
_size = 0;
_capacity = 0;
}
// 拷贝构造函数:深拷贝
stack(const stack& st)
{
cout << "拷贝构造" << endl;
_array = (int*)malloc(st._capacity * sizeof(int));
if (_array == nullptr)
{
perror("malloc fail");
exit(-1);
}
_size = st._size;
_capacity = st._capacity;
}
private:
int* _array;
int _size;
int _capacity;
};
int main()
{
stack st1;
stack st2(st1);
return 0;
}
概念:
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰,防止原本对象被修改),在创建对象时,可以创建一个与已存在对象一某一样的新对象
特性:
函数名是类名,无返回值类型,参数只有一个且必须是类对象的引用,使用传值方式编译器会报错,因为会引发无穷递归调用
拷贝构造函数是构造函数的一个重载形式
若未显式定义拷贝构造函数,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数根据对象按内存大小存储,按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
类中如果没有涉及资源申请时,拷贝构造函数写不写都可以,一旦涉及到资源申请时,则拷贝构造函数一定要写成深拷贝的,否则默认拷贝构造函数为浅拷贝
// 拷贝构造:浅拷贝
#include
using namespace std;
class Date
{
public:
~Date()
{
cout << "~Data()" << endl;
}
Date(int year = 2024, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Data()" << endl;
}
/*
拷贝构造函数:浅拷贝
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
cout << "Data(Date& d)" << endl;
}
*/
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
int main()
{
// c++规定自定义类型都会调用拷贝构造,传引用传参就不会调用拷贝构造了,否则会无限递归
Date d1(2024,1,28);
Date d2(d1);
return 0;
}
#include
using namespace std;
class stack
{
public:
void Push(const int& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 半缺省构造函数
stack(int capacity = 10)
{
_array = (int*)malloc(10 * sizeof(int));
if (_array == nullptr)
{
perror("malloc fail");
exit(-1);
}
_size = 0;
_capacity = capacity;
cout << "构造" << endl;
}
// 析构函数
~stack()
{
free(_array);
_array = NULL;
_size = 0;
_capacity = 0;
}
// 拷贝构造函数:深拷贝
stack(const stack& st)
{
_array = (int*)malloc(st._capacity * sizeof(int));
if (_array == nullptr)
{
perror("malloc fail");
exit(-1);
}
_size = st._size;
_capacity = st._capacity;
}
private:
int* _array;
int _size;
int _capacity;
};
int main()
{
stack st1;
stack st2(st1);
return 0;
}
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似
.* :: sizeof ?: .
注意以上5个运算符不能重载赋值运算符重载
赋值运算符只能重载成类的成员函数不能重载成全局函数?
为什么运算符重载可以重载成全局函数呢?
Date.h:
#pragma once
#include
#include
using namespace std;
// 日期类实现
class Date
{
public:
Date(int year = 2024, int month = 1, int day = 1);
~Date();
Date(const Date& d); // 同类型对象进行初始化
// 获取某年某月的天数
int GetMonthDay(int year, int month) const // 直接在类中定义相当于inline内联展开(此函数我们后续需要频繁调用)
{
assert(month > 0 && month < 13);
static int monthday[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };// 数组放到静态区,增加程序效率
// 1.四年一润百年不润 2.四百年一润
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
{
return 29;
}
return monthday[month];
}
/*
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,
实际修饰该成员函数隐含的this指针指向的内容,表明在该成员函数中不能对类的任何成员进行修改
*/
void Print() const
{
cout << _year << '-' << _month << '-' << _day << endl;
}
/*
1. 运算符重载成全局的就需要成员变量是公有的,问题来了,封装性如何保证?友元解决或者干脆重载成类的成员函数
2. 赋值运算符只能重载成类的成员函数不能重载成全局函数
*/
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=(const Date& d);
Date operator+(int day) const;
Date& operator+=(int day);
Date operator-(int day) const;
Date& operator-=(int day);
// 前置++运算符重载
Date& operator++();
// 后置++运算符重载
Date operator++(int);
// 前置--运算符重载
Date& operator--();
// 后置--运算符重载
Date operator--(int);
// 流插入 << 重载:返回值目的为了支持连续性
friend ostream& operator<<(ostream& out, const Date& d);
// 流提取 >> 重载:返回值目的为了支持连续性
friend istream& operator>>(istream& in, Date& d);
private:
int _year;
int _month;
int _day;
};
Date.cpp:
#include"Date.h"
// 声明和定义分离
// 构造函数
Date::Date(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
// 深度拷贝构造函数
Date::Date(const Date& d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
// 析构函数
Date::~Date()
{
this->_year = 2024;
this->_month = 1;
this->_day = 1;
}
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)
{
if (_month > d._month)
{
return true;
}
else if (_month == d._month)
{
if (_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=(const Date& d)
{
if (this != &d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this;
}
Date Date::operator+(int day) const
{
Date tmp = *this;// 赋值重载拷贝防止原来的日期被改变
tmp._day = tmp._day + day;
int monthday = Date::GetMonthDay(tmp._year, tmp._month);
if (tmp._day <= monthday)
{
return tmp;
}
while (tmp._day > monthday)
{
monthday = Date::GetMonthDay(tmp._year, tmp._month);
tmp._day = tmp._day - monthday;
if (tmp._month < 12)
{
tmp._month++;
}
else
{
tmp._month = 1;
tmp._year++;
}
}
return tmp;
}
Date& Date::operator+=(int day)
{
this->_day = this->_day + day;
int monthday = Date::GetMonthDay(this->_year, this->_month);
if (this->_day <= monthday)
{
return *this;
}
while (this->_day > monthday)
{
monthday = Date::GetMonthDay(this->_year, this->_month);
this->_day = this->_day - monthday;
if (this->_month < 12)
{
this->_month++;
}
else
{
this->_month = 1;
this->_year++;
}
}
return *this;
}
Date Date::operator-(int day) const
{
Date tmp = *this;// 赋值重载拷贝防止原来的日期被改变
tmp._day = tmp._day - day;
int monthday = Date::GetMonthDay(tmp._year, tmp._month);
if (tmp._day > 0)
{
return tmp;
}
while (tmp._day <= 0)
{
if (tmp._month == 1)
{
tmp._month = 12;
tmp._year--;
}
else
{
tmp._month--;
}
monthday = Date::GetMonthDay(tmp._year, tmp._month);
tmp._day = tmp._day + monthday;
}
return tmp;
}
Date& Date::operator-=(int day)
{
this->_day = this->_day - day;
int monthday = Date::GetMonthDay(this->_year, this->_month);
if (this->_day > 0)
{
return *this;
}
while (this->_day <= 0)
{
if (this->_month == 1)
{
this->_month = 12;
this->_year--;
}
else
{
this->_month--;
}
monthday = Date::GetMonthDay(this->_year, this->_month);
this->_day = this->_day + monthday;
}
return *this;
}
// 前置++
Date& Date::operator++()
{
this->_day++;
int monthday = Date::GetMonthDay(this->_year, this->_month);
if (this->_day > monthday)
{
this->_month++;
this->_day = this->_day - monthday;
if (this->_month > 12)
{
this->_month = 1;
this->_year++;
}
}
// 返回的是对象++后的全新的对象
return *this;
}
// 后置++
Date Date::operator++(int)// 注意为何返回值区分了对象本身和对象的引用,在后置++中我们返回的是tmp这个局部变量因此只能返回局部对象的拷贝而不能返回引用
{
Date tmp = *this;
this->_day++;
int monthday = Date::GetMonthDay(this->_year, this->_month);
if (this->_day > monthday)
{
this->_month++;
this->_day = this->_day - monthday;
if (this->_month > 12)
{
this->_month = 1;
this->_year++;
}
}
//返回的是对象++之前的对象的拷贝
return tmp;
}
// 前置--
Date& Date::operator--()
{
this->_day--;
if (this->_day > 0)
{
return *this;
}
else
{
this->_month--;
if (this->_month == 0)
{
this->_month = 12;
this->_year--;
}
int monthday = Date::GetMonthDay(this->_year, this->_month);
this->_day = this->_day + monthday;
return *this;
}
}
// 后置--
Date Date::operator--(int)
{
Date tmp = *this;
this->_day--;
if (this->_day > 0)
{
return tmp;
}
else
{
this->_month--;
if (this->_month == 0)
{
this->_month = 12;
this->_year--;
}
int monthday = Date::GetMonthDay(this->_year, this->_month);
this->_day = this->_day + monthday;
return tmp;
}
}
// 友元函数
//
// 流插入 << 重载
ostream& operator<<(ostream& out, const Date& d)
{
cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
// 流提取 >> 重载
istream& operator>>(istream& in, Date& d)
{
cout << "请依次输入年月日:" << endl;
in >> d._year >> d._month >> d._day;
}
将const修饰的“成员函数”称之为const成员函数,const修饰类的成员函数,实际修饰该成员函数隐含的this指针指向的内容,表明在该成员函数中不能对类的任何成员进行修改
读函数:建议加const
写函数:谨慎加const
// 读函数
void Print() const
{
cout << _year << '-' << _month << '-' << _day << endl;
}
// 写函数
// 构造函数
Date::Date(int year, int month, int day) const //错误const加入
{
this->_year = year;
this->_month = month;
this->_day = day;
}
总之,const的调用满足权限可以缩小但不可以放大,也就是说非const修饰的对象或函数可以调用const修饰的对象或函数,但const修饰的对象或函数不可以调用非const修饰的对象或函数。
这样写ok么?
// 日期类实现
class Date
{
public:
Date(int year = 2024, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//流插入<<重载成为成员函数
void operator<<(ostream& out)
{
cout << this->_year << "年" << this->_month << "月" << this->_day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024,2,1);
//错误运行;
cout<
本质原因是什么呢?
解释:
<<
作为成员函数重载,this指针占据了第一个参数,意味着日期类(Date)对象必须是左操作数,因此我们要设法让cout作为第一个参数,因此为了实现这个操作符重载,我们不能将它写为成员函数,应当写为全局函数,但是当我们将函数写成全局函数后面临它无法访问私有,因此此处我们要么私有公开为公有,要么使用友元,并且注意全局函数不能在.h头文件中定义,否则链接时候会发生重定义。
第一种方式:
// 流输出 << 重载为全局函数,并且将类对象成员私有公开
void operator<<(ostream& out,const Date& d)
{
cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}
第二种方式:
设置Get,Set函数获取私有成员的值(不想破坏封装,可以替换掉友元)
第三种方式:
友元函数
friend void operator<<(ostream& out, const Date& d);
问题来了:我们实现的流插入如何支持连续插入呢?cout<<......<<......<<.......<
与赋值类似,不过结合性顺序相反:
cout<
cout<
流插入:
friend ostream& operator<<(ostream& out, const Date& d);
ostream& operator<<(ostream& out, const Date& d)
{
cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
为什么C++要支持流插入和自定义重载流插入呢?因为C语言中的
printf
函数无法支持自定义类型直接通过printf
输出,而流插入就很好的解决了所有对象的打印问题,无论是内置类型还是自定义类型。
流提取:
friend istream& operator>>(istream& in, Date& d);
istream& operator>>(istream& in, Date& d)
{
cout << "请依次输入年月日:" << endl;
in >> d._year >> d._month >> d._day;
}
这两个运算符一般不需要重载,直接取得对象的地址即可,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,一般不需要自己重写