Lesson 03 -- 类和对象(二)
1. 类的6个默认成员函数
用户没有显示写,编译器会自动生成的成员函数。有构造、析构、拷贝构造、赋值重载、取地址重载(分为普通对象和const对象取地址,默认的就够用了)
2. 构造函数
2.1 概念
是一个特殊的成员函数,名字和类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次
2.2 特性
不是开空间创建对象,而是初始化对象。没有返回值;对象实例化时会自动调用匹配的;构造函数可以重载;如果没有显示定义,编译器会自动生成一个无参的默认构造函数,显示写了就不再生成
C++把类型分为内置类型和自定义类型。内置类型就是数据类型int等,自定义类型就是class等,编译器对内置类型不处理,自定义类型会调用它的默认构造函数。但是内置类型成员变量可以在类中声明时给出默认值。
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;// 这里不是初始化,而是给缺省值
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
默认构造函数有三类:不写编译器自动生成的;自己写的全缺省的;自己写的无参的构造函数。特点就是不传参数就可以调用的,但是默认构造只允许存在一个。
3. 析构函数
3.1 概念
不是完成对对象本身的销毁,而是对象中资源的清理工作
3.2 特性
无参数无返回值类型;一个类只能有一个析构函数,若没有显示写,就自动生成默认的析构函数,析构函数不能重载;当对象生命周期结束时,会自动调用析构函数;内置类型不处理,对自定义类型调用它的析构函数;如果类型类中没有申请资源,析构函数可以不写,直接使用编译器默认生成的就可以;如果有资源申请,就一定要写,否则会造成资源泄露。
先定义的后析构,后定义的先析构
A aa3(3);// A是一个类
void f()
{
static A aa0(0);
A aa1(1);
A aa2(2);
static A aa4(4);
}
// 构造顺序:3 0 1 2 4 1 2
// 析构顺序:~2 ~1 ~2 ~1 ~4 ~0 ~3
int main()
{
f();
f();
return 0;
}
4. 拷贝构造函数
4.1 概念
拷贝构造函数,只有单个形参,该形参是对本类类型对象的引用,一般用const修饰,在已存在的类类型对象创建新对象时由编译器自动调用
4.2 特性
- 拷贝构造函数是构造函数的一个重载形式
- 参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
调用拷贝构造需要先传参,传参又是拷贝构造 - 若为显示定义,会生成默认拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝或值拷贝。内置类型按照浅拷贝,自定义类型调用它的拷贝构造函数
- 类中如果没有涉及资源申请,拷贝构造可以不写,但是一旦涉及到了资源申请,就一定要写,否则就是浅拷贝。
浅拷贝只会复制,引用类型的属性,仍然指向相同的内存地址;深拷贝就会创建完全独立的副本。浅拷贝一个对象修改会影响另一个,而且会析构两次 - 应用场景:使用已存在对象创建新对象;函数参数类型为类类型对象;函数返回值类型为类类型对象。为了提高效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用
// 传值返回会调用一次拷贝构造
A func3()
{
static A aa(3);
return aa;
}
A& func4()
{
static A aa(4);// 使用引用返回,必须保证数据依旧存在,所以使用static
return aa;
}
int main()
{
func3();
cout << endl << endl;
func4();
return 0;
}
class W
{
public:
W(int x = 0)
{
cout << "W()" << endl;
}
W(const W& w)
{
cout << "W(const W& w)" << endl;
}
W& operator=(const W& w)
{
cout << "W& operator=(const W& w)" << endl;
return *this;
}
~W()
{
cout << "~W()" << endl;
}
};
void f1(W w)
{}
void f2(const W& w)
{}
//int main()
//{
// W w1;
// f1(w1); // 需要调用拷贝构造
// cout << endl ;
//
// f2(w1); // 不会调用拷贝构造
// cout << endl;
//
// f1(W()); // 本来构造+拷贝构造--编译器的优化--直接构造
// // 结论:连续一个表达式步骤中,连续构造一般都会优化 -- 合二为一
// return 0;
//}
W f3()
{
W ret;
return ret;
}
int main()
{
f3(); // 1次构造 1次拷贝
cout << endl << endl;
W w1 = f3(); // 本来:1次构造 2次拷贝 -- 优化:1次构造 1次拷贝
cout << endl << endl;
W w2;
w2 = f3(); // 本来:1次构造 1次拷贝 1次赋值
return 0;
}
W f(W u)
{
W v(u);
W w = v;
return w;
}
int main()
{
W x;
W y = f(f(x)); // 1次构造 7次拷贝
return 0;
}
// int main()
// {
// W x;
// W y = f(x); // 1次构造 4次拷贝
// return 0;
// }
5. 赋值运算符重载
5.1 运算符重载
增强了代码的可读性,作为类成员函数重载时,第一个参数为隐藏的this;
.* :: sizeof ?: .
这五个不能重载
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
5.2 赋值运算符重载
两个存在的对象叫做赋值,创建的时候初始化是拷贝构造
- 格式
- 参数类型:const T&
- 返回值类型:T&,可以提高返回的效率,有返回值是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this:要符合连续赋值的含义
- 赋值运算符只能重载成类的成员函数,不能重载成全局函数
- 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类
型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值
前置++和后置++重载
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 前置++:返回+1之后的结果
// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
// 后置++:
// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
Date d1(2022, 1, 13);
d = d1++; // d: 2022,1,13 d1:2022,1,14
d = ++d1; // d: 2022,1,15 d1:2022,1,15
return 0;
}
流插入、流提取的重载
运算符重载:让自定义类型对象可以用运算符,转换成调用这个函数
函数重载:支持函数名相同的函数同时存在。两者没有关系
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;
}
6. 日期类的实现
一般只有构造析构定义在类内,被当作内联处理,为了方便展示,就将所有函数的实现放在类内
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
class 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)
{
assert(CheckDate());
}
int GetMonthDay(int year, int month) const
{
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;
}
bool CheckDate()
{
if (_year >= 1
&& _month > 0 && _month < 13
&& _day>0 && _day <= GetMonthDay(_year, _month))
{
return true;
}
else
return false;
}
void Print() const
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
bool operator==(const Date& d) const
{
return _year == d._year && _month == d._month && _day == d._day;
}
bool operator!=(const Date& d) const
{
return !(*this == d);
}
bool 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 operator>=(const Date& d) const
{
return (*this > d) || (*this == d);
}
bool operator < (const Date& d) const
{
return !(*this >= d);
}
bool operator<=(const Date& d) const
{
return !(*this > d);
}
Date& operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
Date operator+(int day) const
// 出了作用域就被销毁了,所以不适用传引用返回
{
Date retD = *this;
return retD += day;
}
Date& operator-=(int day)
{
if (day < 0)
return *this += -day;
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month - 1);
}
return *this;
}
Date operator-(int day) const
{
Date retD = *this;
return retD -= day;
}
Date& operator++()// 前置++
{
return *this += 1;
}
Date operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
Date& operator--()
{
return *this -= 1;
}
Date operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;
}
int 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;
}
private:
int _year;
int _month;
int _day;
};
inline ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日";
return out;
}
inline istream& operator>>(istream& in, Date& d)
{
in >> d._day >> d._month >> d._day;
assert(d.CheckDate());
return in;// 返回值支持连续输入输出
}
7. const成员
const修饰类成员函数,实际修饰该成员函数隐含的this,表明在该成员函数中不能对类的任何成员进行修改。
class A
{
public:
void Print() const;// 修饰的就是this指向的内容
}
int main()
{
A d1;
const A d2;
d1.Print();
d2.Print();// 不允许,因为Print的参数是A* const this,会发生权限的放大,所以函数需要const
return 0;
}