1. 前言
今天我们来继续学习C++类与对象(中)的相关知识点。
2. 类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
6个默认成员函数:
初始化和清理:构造函数主要完成初始化工作、析构函数主要完成清理工作。
拷贝赋值:拷贝构造是使用同类对象初始化创建对象、赋值重载主要是把一个对象赋值给另一个对象。
取地址重载:主要是普通对象和const对象取地址,这两个很少会自己实现。
3. 构造函数
构造函数是一个特殊(不能以普通函数的定义和调用规则去理解)的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
特性:
1.函数名与类名相同。
2.无返回值,也不用写void
3.对象实例化时编译器自动调用对应的构造函数。
4.构造函数可以重载。
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2023, 4, 27);
d1.Print();
d2.Print();
return 0;
}
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
C++把类型分成内置类型(基本类型)和自定义类型。
内置类型就是语言提供的数据类型如:int / char…,自定义类型就是我们使用class / struct / union等自己定义的类型。
默认生成的构造函数:1、内置类型成员不做处理,2、自定义类型成员会去调用他的默认构造函数
6.C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
7.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
class Date
{
public:
//Date()
//{
// _year = 1;
// _month = 1;
// _day = 1;
//}
//Date(int year = 1, int month = 1, int day = 1)
//{
// _year = year;
// _month = month;
// _day = day;
//}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 2; //此处不是初始化,是给缺省值
int _month = 2;
int _day = 2;
};
int main()
{
Date d1;
//Date d2(2023, 4, 27);
d1.Print();
//d2.Print();
return 0;
}
4. 析构函数
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
析构函数是特殊的成员函数
特性:
1.析构函数名是在类名前加上字符 ~。
2.无参数无返回值类型。
3.一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
注意:析构函数不能重载、内置类型不处理、自定义类型去调用他的析构函数
4.对象生命周期结束时,C++编译系统系统自动调用析构函数。
5.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
void TestStack()
{
Stack s;
s.Push(1);
s.Push(2);
}
int main()
{
TestStack();
return 0;
}
5. 拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数也是特殊的成员函数
特性:
1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
3.若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。而自定义类型是调用其拷贝构造函数完成拷贝的。
4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,类中如果没有涉及资源申请时,拷贝构造函数是否写都可以,一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
5.拷贝构造函数典型调用场景:
使用已存在对象创建新对象、函数参数类型为类类型对象、函数返回值类型为类类型对象
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//Date(const Date a)错误
Date(const Date& a)//正确
{
_year = a._year;
_month = a._month;
_day = a._day;
}
void Print()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a1(2023, 5, 10);
Date a2(a1);
a1.Print();
a2.Print();
return 0;
}
6. 运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表。
其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5.(.*)(::)(sizeof)(?:)(.) 注意以上5个运算符不能重载
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& a)
{
_year = a._year;
_month = a._month;
_day = a._day;
}
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
void Print()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a1(2023, 5, 10);
Date a2(2024, 5, 10);
//内置类型可以直接使用运算符运算,编译器知道要如何运算
//自定义类型无法直接使用运算法,编译器也不知道要如何运算。想支持,自己实现运算符重载即可
cout << (a1 == a2) << endl;
return 0;
}
6.1 赋值运算符重载
1.参数类型:const T& ,传递引用可以提高传参效率
2.返回值类型:T& ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
3.检测是否自己给自己赋值
4.返回* this :要复合连续赋值的含义
注意:
1.赋值运算符只能重载成类的成员函数不能重载成全局函数
2.赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。如果类中未涉及到资源管理,赋值运算符是否实现都可以,一旦涉及到资源管理则必须要实现。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date& operator=(const Date& a)
{
if (this != &a)
{
_year = a._year;
_month = a._month;
_day = a._day;
}
return *this;
}
void Print()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a1(2023, 5, 10);
Date a2;
a2.Print();
a2 = a1;
a2.Print();
return 0;
}
7. const成员
将const修饰的“成员函数”称之为const成员函数
const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class A
{
public:
A(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
//void Print(const A* const this)
//修饰了this指向的内容,保证了成员函数内部不会修改成员变量,const对象和非const对象都可以调用这个成员函数
void Print()const
{
cout << _year << '-' << _month << '-' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
A a1(2023,5,13);
const A a2(2022, 5, 13);
a1.Print();
a2.Print();
return 0;
}
8. 取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义,编译器默认会生成。
特殊使用场景:不想让别人取到这个类型对象的地址
class A
{
public:
A(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
A* operator&()
{
return nullptr;
}
const A* operator&()const
{
return nullptr;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
A a1;
const A a2;
cout << &a1 << endl;
cout << &a2 << endl;
return 0;
}
9. 日期类对象的完整实现
下面我通过前面所学知识点来实现了一个完整的日期类对象,大家可以通过这个完整的类对象,来和之前所学知识点对比学习。
9.1 头文件
#include <iostream>
using namespace std;
class Date
{
// 友元函数 -- 这个函数内部可以使用Date对象访问私有保护成员
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
//全缺省的构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//获取某个月的天数
int 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 += 1;
}
return day;
}
//打印日期
void Print()const;
//赋值运算符重载
Date& operator=(const Date& d);
//==运算符重载
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);
//日期+天数
Date operator+(int day)const;
//日期-=天数
Date& operator-=(int day);
//日期-天数
Date operator-(int day)const;
//日期前置++和后置++
//前置++和后置++直接按特性重载,无法区分,所以要特殊处理,使用函数重载区分,后置++重载增加一个int参数跟前置构成函数重载进行区分
Date& operator++();
Date operator++(int);
//日期前置--和后置--
Date& operator--();
Date operator--(int);
//日期-日期
int operator-(const Date& d)const;
private:
int _year;
int _month;
int _day;
};
//流插入重载
inline ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
//流提取重载
inline istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
9.2 源文件
#include "Date.h"
void Date::Print()const
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
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
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Date::operator!=(const Date& d)const
{
return (!(*this == d));
}
bool Date::operator>(const Date& d)const
{
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 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);
}
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 > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
Date Date::operator+(int day)const
{
Date ret = *this;
return ret += day;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day < 0)
{
_month--;
if (_month < 1)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day)const
{
Date ret = *this;
return ret -= day;
}
Date& Date::operator++()
{
return *this += 1;
}
Date Date::operator++(int)
{
Date ret = *this;
*this += 1;
return ret;
}
Date& Date::operator--()
{
return *this -= 1;
}
Date Date::operator--(int)
{
Date ret = *this;
*this -= 1;
return ret;
}
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;
}
9.3 测试代码
int main()
{
Date d1(2023, 5, 13);
Date d2(2022, 5, 13);
Date d3 = d1;
d3.Print();
cout << (d1 == d2) << endl;
cout << (d1 != d2) << endl;
cout << (d1 == d3) << endl;
cout << (d1 < d2) << endl;
cout << (d1 > d2) << endl;
cout << (d1 >= d3) << endl;
cout << (d1 <= d3) << endl;
(d1 + 4000).Print();
(d1 - 4000).Print();
(++d1).Print();
(--d1).Print();
cout << (d1 - d2) << endl;
(d1 + 40000).Print();
(d1 - 40000).Print();
cout << d1 << endl;
cin >> d1;
cout << d1 << endl;
return 0;
}
测试结果如下:
文章来源:https://www.toymoban.com/news/detail-443022.html
10. 结尾
本篇博客所讲内容我认为是C++类与对象板块乃至C++初学阶段最重要的地方,6个默认成员函数必须掌握,并能自己实现一个完整的简单的类对象,此处一定要多复习,多思考。
最后,感谢各位大佬的耐心阅读和支持,觉得本篇文章写的不错的朋友可以三连关注支持一波,如果有什么问题或者本文有错误的地方大家可以私信我,也可以在评论区留言讨论,再次感谢各位。文章来源地址https://www.toymoban.com/news/detail-443022.html
到了这里,关于【C++】——类与对象(中)+日期类对象的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!