最新类和对象(中)----第二部分(1),2024年最新BAT大厂面试基础题集合

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号

函数原型:返回值类型 operator操作符(参数列表)

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型或者枚举类型的操作数
  • 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义,我们就不能将其改为-
  • 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的 操作符有一个默认的形参this,限定为第一个形参
  • .*::sizeof?:. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

第一种:全局的operator

//全局的operator==
class Date
{
public:
	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;
	int _month;
	int _day;
};
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1_day == d2._day;
}
int main()
{
	Date d1;
	Date d2(d1);
	//1.第一种调用方式
	if (operator== (d1, d2))
	{
		cout << "==" << endl;
	}
	//2.第二种调用方式
	if (d1 == d2)//编译器会处理成对应重载运算符调用if(operator== (d1, d2))
	{
		cout << "==" << endl;
	}
	return 0;
}

我们运行之后上面的程序会报错,因为我们在类外访问了private成员变量,这是非法的。有三种修改方式:

  • 将private改为public。但是这种方法是非常不推荐的,因为我们一般会将数据设为私有的,不想被使用者知道或者进行修改。
  • 采用接口的方式
class Date
{
public:
	Date(int year = 1,int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	int GetYear()
	{
		return _year;
	}
	int GetMonth()
	{
		return _year;
	}
	int GetDay()
	{
		return _year;
	}
private:
	int _year;
	int _month;
	int _day;
};

bool operator==(const Date& d1, const Date& d2)
{
	return d1.GetYear() == d2.GetYear()
		&& d1.GetMonth() == d2.GetMonth()
		&& d1.GetDay() == d2.GetDay();
}
//在上面的这种写法中会出现错误,为什么?
//下面来进行分析:
//此时d1和d2是const Date& d1类型的,调用的时候会将其地址传过去,所以传过去的类型应该是const Date\*
//而this指针的类型是Date \* const,所以不能发生上述类型转换,属于权限的放大,应该像下面这样写:
bool operator==(Date& d1, Date& d2)//将const去掉就好
{
	return d1.GetYear() == d2.GetYear()
		&& d1.GetMonth() == d2.GetMonth()
		&& d1.GetDay() == d2.GetDay();
}
//或者像下面这样进行修改
void Print()const 
{
	cout << _year << "-" << _month << "-" << _day << endl;
}
int GetYear()const 
{
	return _year;
}
int GetMonth()const 
{
	return _year;
}
int GetDay()const 
{
	return _year;
}
//上面的代码相当于下面的代码
void Print(const Date\* const this)
{
	cout << _year << "-" << _month << "-" << _day << endl;
}
int GetYear(const Date\* const this)
{
	return _year;
}
int GetMonth(const Date\* const this)
{
	return _year;
}
int GetDay(const Date\* const this)
{
	return _year;
}
//这样进行修改之后,再执行上面报错的操作就是权限的缩小了,这是被编译器所允许的

总结:建议成员对象函数中不修改成员变量的函数,都建议在成员函数的后面加上一个const,进而使this指针的类型变成const Date *

  • 采用友元的方式(此处不作详解,会破坏封装)。

第二种:类中的operator

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	bool operator==(const Date& d)//因为类中的函数自动传了this指针
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	//1.第一种调用方式
	if (d1.operator== (d2))//
	{
		cout << "==" << endl;
	}
	//2.第二种调用方式
	if (d1 == d2)//编译器会处理成if(d1.operator(&d1, d2))
	{
		cout << "==" << endl;
	}
	return 0;
}

问:如果类里面的和全局的operator都同时存在,那么优先调用哪一个?

答:优先调用类里面的!

练习写日期类<的重载。

bool operator<(const Date& d)
{
	if (_year < d._year
		|| (_year == d._year && _month < d._month)

		|| (_year == d._year && _month == d._month && _day < d._day))
	{
		return true;
	}
	else
		return false;
}

5.2 赋值运算符重载

首先先对拷贝构造和赋值运算符进行区分。

Date d1(2022, 5, 17);
Date d2(d1);//拷贝构造--一个存在的对象去初始化另一个要创建的对象
Date d3 = d1;//这是拷贝构造--一个已经存在的对象去初始化另一个已经存在的对象
Date d3;//这条语句执行之后,d3已经存在
d3 = d1;//赋值重载--两个已经存在的对象之间赋值

对于日期类赋值重载的实现:

void operator=(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

我们此处写的并不够全面,为什么?因为按照正常的赋值语法来说,a = b = c这样的操作是合法的,赋值表达式是有返回值的,但是我们上面实现的返回值类型为void,所以不支持连续赋值,所以我们还要进行改进:

Date& operator=(const Date& d)//传引用返回是为了提高效率,避免拷贝
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}

此时我们还要考虑一种情况,就是自己给自己进行赋值,这样的行为是没有什么意义的,所以我们要避免这样的操作。

下面就是改进后的赋值运算符重载(标准版

Date& operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return \*this;
}

注意:当我们没有写赋值重载函数的时候,我们在代码中写的赋值操作就会自动执行拷贝构造函数,来完成赋值操作。

赋值运算符主要有以下五点:

  1. 参数类型
  2. 返回值
  3. 检测是否自己给自己赋值
  4. 返回*this
  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。

问:那么编译器生成的默认赋值重载函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?

答:答案是有必要的,比如涉及到管理非栈区空间的时候,就会涉及到深拷贝的问题,此时就需要我们自己来实现赋值重载函数。

6. const成员

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this 指针,表明在该成员函数中不能对类的任何成员进行修改。

image-20220522140550118

我们知道this指针的类型是Date* const ,在成员函数的后面加上const之后,this指针的类型就变成了const Date* const

下面有四个问题:

  1. const对象可以调用非const成员函数吗?
    不行,这属于权限的放大!
  2. 非const对象可以调用const成员函数吗?
    可以,这属于权限的缩小!
  3. const成员函数内可以调用其它的非const成员函数吗?
    这个是什么意思呢?我们用代码来举例:
bool Date::operator==(const Date& d)
{
    Print();//这就是在成员函数内调用其它的成员函数
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

上面的例子就可以演示在成员函数内调用其它的成员函数,本质上其实就是通过this指针进行调用的,即this->Print

所以上面的问题是不可以的,因为const成员函数内的this指针的类型是const Date* const ,而非const成员函数内的this指针的类型是Date *const,属于权限的放大,所以是不可以的!
4. 非const成员函数内可以调用其它的const成员函数吗?
可以,属于权限的缩小。

7.取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

class Date
{
public:
	Date(int year,int month,int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date\* operator&()//普通的取地址操作符重载
	{
		return this;
	}
	const Date\* operator&()const//const取地址操作符重载
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

注意:此处有一个最佳匹配原则:

假如我们像下面这样进行定义变量:

Date d1(2022, 5, 22);//会优先调用普通的取地址操作符重载----最佳匹配
const Dare d2(2022, 5, 23);//会优先调用const取地址操作符重载----最佳匹配
//如果我们只定义了const取地址操作符重载,那么上面的两种都只会调用const取地址操作符重载

问:假如我们只定义了普通的取地址运算符重载,但是我们想对上面的d2变量进行取地址操作,那么编译器会如何进行处理呢?

答:此时编译器会调用默认的const取地址操作符重载,然后进行取地址操作。即不会调用普通的取地址操作符重载,当然,也无法成功调用。

结论:上面的两种默认成员函数只要没写,都会生成默认的操作符重载函数。

那么,既然编译器能够自动生成,那么我们是否还要写这个重载呢?

有时候是必须写的,比如我们不想让别人知道我们定义变量的地址,我们就可以像下面这样写:

class Date
{
public:
	Date(int year,int month,int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date\* operator&()//普通的取地址操作符重载
	{
		return nullptr;
	}
	const Date\* operator&()const//const取地址操作符重载
	{
		return nullptr;
	}
private:
	int _year;
	int _month;
	int _day;
};

这样即使别人进行取地址运算符操作得到的也只是空指针了!

8.<<(流插入)和>>(流提取)运算符重载

在实现之前,我们先了解以下cin和cout这两个对象。

image-20220522152545286

从图中可以看到,cin是istream类型的对象,cout是ostream类型的对象。

我们能够使用cin和cout对内置类型的对象进行流插入和流提取操作,且编译器能够自动判断类型,这是因为编译器已经对内置类型进行了函数重载:

image-20220522153204950

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void operator<<(std::ostream& out)
	{
		out << _year << "-" << _month << "-" << _day << endl;
	}
	
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022, 5, 22);
	const Date d2(2022, 5, 23);
	cout << d1;
	return 0;
}

此时我们发现程序无法通过,为什么?

我们首先来清楚cout调用的时候是什么样的:

cout << i;
cout.operator<< (i);//流插入是cout的成员函数,cout是ostream类型的对象

我们上面实现的是下面这样的:

cout << d1;//这样写的意思是cout.operator<<(d1);这跟我们的实现不相符合,因为我们实现的第一个参数是this指针,第二个参数才是out对象
d1 << cout;//这样写是可以的
d1.operator<<(cout);//这样写跟上一行代码是一样的

我们可以对上面的写法进行改进,将<<运算符重载写成全局的:

void operator<<(std::ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
}

接下来就是解决在类外无法访问类中的成员变量的问题,有两种解决方案:

  1. 采用接口的方式(通过public里的接口函数来得到类中的成员变量)
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int GetYear()const
	{
		return _year;
	}
	int GetMonth()const
	{
		return _month;
	}
	int GetDay()const
	{
		return _day;
	}
private:
	int _year;
	int _month;
	int _day;
};
void operator<<(std::ostream& out, const Date& d)
{
	out << d.GetYear() << "-" << d.GetMonth() << "-" << d.GetDay() << endl;
}

  1. 友元的方式
class Date
{
public:
	friend void operator<<(std::ostream& out, const Date& d);//该函数在类外可以访问到类内的成员变量
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};
void operator<<(std::ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
}
int main()
{
	Date d1(2022, 5, 22);
	const Date d2(2022, 5, 23);
	cout << d1;//可以被编译器进行转化为:operator(cout,d1)
	return 0;
}

问:我们定义的函数的参数是否必须是out?可以是其它标识符嘛?

答:不需要必须是out,也可以是其它的标识符,不用cout的原因是因为命名污染,因为我们已经引入了cout。此处的out只是一个函数内形参。

问:为什么我在.h文件里面定义了全局函数的时候,发现程序出现了链接错误?

答:因为.h文件在其它几个.cpp文件中展开,编译之后在其它.cpp文件中形成符号表的时候都会出现,所以链接时编译器不知道链接哪一个,所以会出现链接错误。

问:我们常常将类的定义放在.h文件中,在多个.cpp文件中包含,那么在类中定义的成员函数为什么就不会出现链接错误?

答:在类中定义的成员函数被默认为是内联函数,即使有的函数很长,但是编译器会将它的属性默认为是内联函数,即不会通过符号表的形式进行链接,编译器自己会进行处理。

问:到底什么情况下要通过符号表的形式去链接寻找函数的定义?

答:在当前的.cpp文件中只有函数的声明,但是却没有函数的定义,只有这种情况下才去寻找,比如我们在.cpp文件中有了函数的声明,但是没有函数的定义,这个时候会通过符号表链接的方式进行寻找。但是像类的成员函数的情况,我们将.h文件包含到.cpp文件后,就有了函数的定义,即使被处理成了内联函数,但是我们仍然能通过那个函数对应的符号找到函数的地址(地址在符号表形成的时候形成)。

上面的代码仍然是不完善的,我们看下面的表达式:

cout << i1 << i2 << i3 << endl;

我们刚才写的无法实现像这样的功能,所以仍然需要进行完善:

cout的运算符重载:

class Date
{
public:
	friend std::ostream& operator<<(std::ostream& out, const Date& d);//该函数在类外可以访问到类内的成员变量
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;


![img](https://img-blog.csdnimg.cn/img_convert/46541eb4ddae241377a9393aae214a1d.png)
![img](https://img-blog.csdnimg.cn/img_convert/199a44ed4cbe550aea7f33c07c66b3ea.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

的地址(地址在符号表形成的时候形成)。
> 
> 
> 


上面的代码仍然是不完善的,我们看下面的表达式:



cout << i1 << i2 << i3 << endl;


我们刚才写的无法实现像这样的功能,所以仍然需要进行完善:


**cout的运算符重载:**



class Date
{
public:
friend std::ostream& operator<<(std::ostream& out, const Date& d);//该函数在类外可以访问到类内的成员变量
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}

private:
int _year;
int _month;

[外链图片转存中…(img-tvkaF2xG-1715880340752)]
[外链图片转存中…(img-QRzuJ6dg-1715880340753)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值