Lesson 03 -- 类和对象(二)

本文详细介绍了C++中的类和对象,包括默认成员函数、构造函数(包括概念、特性)、析构函数、拷贝构造函数、赋值运算符重载(包括前置++、后置++和流操作),以及日期类的实现,展示了如何通过重载运算符提升代码可读性并处理对象间的复制和修改操作。
摘要由CSDN通过智能技术生成

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 特性

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
    在这里插入图片描述
    调用拷贝构造需要先传参,传参又是拷贝构造
  3. 若为显示定义,会生成默认拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝或值拷贝。内置类型按照浅拷贝,自定义类型调用它的拷贝构造函数
  4. 类中如果没有涉及资源申请,拷贝构造可以不写,但是一旦涉及到了资源申请,就一定要写,否则就是浅拷贝。
    浅拷贝只会复制,引用类型的属性,仍然指向相同的内存地址;深拷贝就会创建完全独立的副本。浅拷贝一个对象修改会影响另一个,而且会析构两次
  5. 应用场景:使用已存在对象创建新对象;函数参数类型为类类型对象;函数返回值类型为类类型对象。为了提高效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用
// 传值返回会调用一次拷贝构造
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 赋值运算符重载

两个存在的对象叫做赋值,创建的时候初始化是拷贝构造

  1. 格式
  • 参数类型:const T&
  • 返回值类型:T&,可以提高返回的效率,有返回值是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this:要符合连续赋值的含义
  1. 赋值运算符只能重载成类的成员函数,不能重载成全局函数
  2. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类
    型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值

前置++和后置++重载

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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值