C++必修:类与对象(三)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++学习
贝蒂的主页:Betty’s blog

1. 隐式类型转换

在学习C语言时我们就明白,当我们进行赋值时,如果赋值两边的类型不同时就可能发生隐式类型转换

1.1. 内置类型

在发生隐式类型转换时,如果都是内置类型就会先开辟一个临时变量,再将右操作数强制类型转换为左操作数的类型,最后用这个临时变量对左操作数进行赋值。注意:这个临时变量具有常性,不可修改。

img

int main()
{
	double j = 1.1;
	int i = j;//隐式类型转换
	int& a = j;//error
	const int& b = j;//ok
	return 0;
}

因为临时变量具有常性,所以无法被修改。如果赋值给普通引用就会造成权限的放大,所以只能用常引用。

1.2. 自定义类型

如果将一个内置赋值给自定义类型,那么编译器也会先创造一个自定义类型的**临时变量,**然后用这个内置类型调用构造函数对临时变量初始化,最后用这个临时变量对左操作数进行拷贝构造。。注意:这个临时变量也具有常性,不可修改。

img

class Date
{
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;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1 = 2023;//发生隐式类型转换
	Date d2 = { 2023,2 };
	d1.Print();
	d2.Print();
	return 0;
}

1.3. explicit关键字

用explicit修饰构造函数,将会禁止构造函数的隐式转换。

class Date
{
public:
	explicit 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;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1 = 2023;//发生隐式类型转换
	Date d2 = { 2023,2 };
	d1.Print();
	d2.Print();
	return 0;
}

img

2. 类的静态成员

2.1. 定义

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数

class A
{
public:
    static int Print()//静态成员函数
	{
		cout << "Print()" << endl;
	}
private:
	static int _a;//静态成员变量
};

2.2. 注意

  1. 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
  2. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。所以可以通过类名::静态成员或者 对象.静态成员来访问。
class A
{
public:
	static void Print()//静态成员函数
	{
		cout << "Print()" << endl;
	}
private:
	static int _a;//静态成员变量
};
int main()
{
	A a;
	a.Print();
	A::Print();
	return 0;
}

img

  1. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。
int A::_a = 1;//类外定义
  1. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员。比如说:静态函数就无法访问非静态函数。
	static void Print()//静态成员函数
	{
		cout << "Print()" << endl;
		Add(1, 2);//无法访问非静态成员
	}
	int Add(int x, int y)
	{
		return x + y;
	}

但是非静态成员函数能访问静态成员函数。

	static void Print()//静态成员函数
	{
		cout << "Print()" << endl;
	}
	int Add(int x, int y)
	{
		return x + y;
		Print();
	}

3. 友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

友元分为:友元函数和友元类。

3.1. 友元函数

有时在类外我们需要访问类中的数据,但由于访问限定符的限制并不能访问私有成员。这时如果一定要访问的话就需要借助我们的友元函数,它的用法十分简单,只用在类中加入friend+该函数的声明

class Date
{
	friend void Print(const 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;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Print(const Date&d)
{
	cout << d._year << "-" << d._month << "-" << d._day << endl;
}

当然我们也能通过在类中声明获取私有元素的返回函数实现:

class Date
{
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 GetYear()const
	{
		return _year;
	}
	int GetMonth()const
	{
		return _month;
	}
	int GetDay()const
	{
		return _day;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Print(const Date& d)
{
	cout << d.GetYear() << "-" << d.GetMonth() << "-" << d.GetDay() << endl;
}

注意:

  1. 友元函数是可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用原理相同

3.2. 友元类

除了友元函数外,还有一种友元类。友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。与友元函数用法类似:在一个类中声明friend+class+类名

class Time
{
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

注意:

  1. **友元关系是单向的,不具有交换性。**比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  2. **友元关系不能传递。**如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  3. 友元关系不能继承。在继承位置再给大家详细介绍。

4. 内部类

4.1. 定义

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。并且内部类就是外部类的友元类。

class A
{
public:
	class B//B是A的友元
	{
	private:
		int _m;
	};
private:
	int _a;
	int _b;
};

4.2. 注意

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象 / 类名。
class A
{
private:
	static int k;
	int h;
public:
	class B // B天生就是A的友元
	{
	public:
		void foo()
		{
			cout << k << endl;//OK
		}
	};
};
int A::k = 1;
int main()
{
	A::B b;
	b.foo();
	return 0;
}

img

  1. 外部类的大小与内部类的大小没有关系,sizeof(外部类) = 外部类。
class A
{
private:
	int k;
	int h;
public:
	class B // B天生就是A的友元
	{
	public:
		int _a;
	};
};
int main()
{
	A a;
	cout << sizeof(a) << endl;
	return 0;
}

img

5. 匿名对象

匿名对象与C语言中的匿名结构体类似,只有类名,作用域只在匿名对象声明的一行。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		cout << "Date" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
		_year = _month = _day = 0;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date();//匿名对象
	Date d;
	return 0;
}

img

但是我们也能通过引用延长匿名对象的生命周期。

int main()
{
	const Date& dc = Date();//匿名对象也具有常性
	Date d;
	return 0;
}

img

6. 编译器的一些优化

下面我们将介绍一些编译器对自定义类型常见的优化,我们还是以日期类举例:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		cout << "Date" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
		_year = _month = _day = 0;
	}
private:
	int _year;
	int _month;
	int _day;
};
  • 直接构造+拷贝构造=>直接构造

img

  • 直接构造+拷贝构造+拷贝构造=>直接构造

img

  • 直接构造+直接构造+拷贝构造+赋值重载=>直接构造+直接构造

img

7. 日期类的模拟实现

下面让我们来实现一个功能比较丰富的日期类。

7.1. 项目功能

  1. 日期之间的比较(>,<,>=,<=,==,!=)。
  2. 日期的递增与递减。
  3. 日期+天数(+,+=)。
  4. 日期-天数(-,-=)。
  5. 日期-日期。
  6. 流输入与流输出。
  7. 打印日期。

7.2. 功能实现

7.2.1. 构造与析构函数

在写构造函数之前我们需要写一个函数判断当前日期是否合法。

int Date:: GetMonDay(int year, int month)//或许当月天数
{
	int months[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = months[month];
	if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
	{
		day++;
	}
	return day;
}

构造函数与拷贝构造与赋值重载。注意:缺省参数在定义时不需要书写

Date::Date(int year, int month, int day)//构造
{
	assert(month > 0 && month < 13);
	int days = GetMonDay(year, month);
	if (day >= 0 && day <= days)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "输入日期不合法" << endl;
	}
}
Date::Date(const Date& d)//拷贝构造
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
Date& Date::operator=(const Date& d)//赋值重载
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

析构函数:

Date::~Date()
{
	_year = _month = _day = 0;
}
7.2.2. 两个日期之间的比较
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) 
	{
		return true;
	}
	else if (_year == d._year && _month > d._month) 
	{
		return true;
	}
	else if (_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);
}
7.2.3. 自增与自减

为了区分前置与后置的区别,C++规定在重载时前置并不需要额外的参数,后置需要一个额外的int参数。

Date& Date::operator++()//前置++
{
	*this += 1;
	return *this;
}
Date Date::operator++(int)//后置++
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
Date& Date::operator--()//前置--
{
	*this -= 1;
	return *this;
}
Date Date::operator--(int)//后置--
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}
7.2.4. 日期与天数直接的计算
Date& Date::operator+=(int day)//+=
{
	if (day < 0)
	{
		*this -= -day;
		return *this;
	}
	_day += day;
	while (_day > GetMonDay(_year, _month))
	{
		_day -= GetMonDay(_year, _month);
		++_month;
		if (_month > 12)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}
Date Date::operator+(int day)const//+
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}
Date& Date::operator-=(int day)//-=
{
	if (day < 0)
	{
		*this += -day;
		return *this;
	}
	while (day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonDay(_year, _month);
	}
	return *this;
}
Date Date::operator-(int day)const//-
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}
int Date::operator-(const Date& d)const//相差
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}//求最大
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}

	return flag * n;
}
7.2.5. 打印
void Date::Print()const//打印
{
	cout<< _year << "/" << _month << "/" << _day;
}
7.2.6. 流插入与流提取

因为重载后的运算符顺序与参数顺序相同,为了符合习惯,我们只好在类外定义。并且因为函数较短,可以声明为了内联函数。

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;
}

7.3. 完整代码

7.3.1. Date.h
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
	friend inline ostream& operator<<(ostream& out, const Date& d);
	friend inline istream& operator>>(istream& in, Date& d);
public:
	int GetMonDay(int year, int month);//或许当月天数
	Date(int year = 1, int month = 1, int day = 1);//构造
	Date(const Date& d);//拷贝构造
	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;//-
	Date& operator++();//前置++
	Date operator++(int);//后置++
	Date& operator--();//前置--
	Date operator--(int);//后置--
	int operator-(const Date& d)const;//相差
	void Print()const;//打印
	~Date();
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;
}
7.3.2. Date.cpp
#include"Date.h"
int Date:: GetMonDay(int year, int month)//或许当月天数
{
	int months[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = months[month];
	if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
	{
		day++;
	}
	return day;
}
Date::Date(int year, int month, int day)//构造
{
	assert(month > 0 && month < 13);
	int days = GetMonDay(year, month);
	if (day >= 0 && day <= days)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "输入日期不合法" << endl;
	}
}
Date::Date(const Date& d)//拷贝构造
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
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) 
	{
		return true;
	}
	else if (_year == d._year && _month > d._month) 
	{
		return true;
	}
	else if (_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)
	{
		*this -= -day;
		return *this;
	}
	_day += day;
	while (_day > GetMonDay(_year, _month))
	{
		_day -= GetMonDay(_year, _month);
		++_month;
		if (_month > 12)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}
Date Date::operator+(int day)const//+
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}
Date& Date::operator-=(int day)//-=
{
	if (day < 0)
	{
		*this += -day;
		return *this;
	}
	while (day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonDay(_year, _month);
	}
	return *this;
}
Date Date::operator-(int day)const//-
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}
Date& Date::operator++()//前置++
{
	*this += 1;
	return *this;
}
Date Date::operator++(int)//后置++
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
Date& Date::operator--()//前置--
{
	*this -= 1;
	return *this;
}
Date Date::operator--(int)//后置--
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}
int Date::operator-(const Date& d)const//相差
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}//求最大
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}

	return flag * n;
}
void Date::Print()const//打印
{
	cout<< _year << "/" << _month << "/" << _day;
}
Date::~Date()
{
	_year = _month = _day = 0;
}
  • 127
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 78
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 78
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值