面试题52:什么是操作符重载,它的用途是什么
操作符重载是 C++ 中的一个重要概念,它允许程序员重新定义或重载已有的操作符,使其能够用于用户自定义的数据类型。这种重载的目的是为了使得用户自定义的数据类型能够像内置类型一样方便地进行运算。
具体来说,操作符重载的用途包括:
扩展运算符的适用范围:通过重载,可以使同一运算符作用于不同类型的数据时导致不同类型的行为,从而扩展 C++ 中提供的运算符的适用范围,以用于类所表示的抽象数据类型。
简化代码:操作符重载可以使代码更加简洁易懂。通过为自定义类型重载操作符,可以像使用内置类型一样使用这些操作符,而无需定义额外的函数来完成相同的操作。
提高代码可读性:操作符重载可以将概括性的抽象操作符具体化,便于外部调用而无需知晓内部具体运算过程,从而提高代码的可读性。
面试题53:如何使用友元函数重载操作符
友元函数重载操作符一般是应用在非成员函数重载的情况下,这种重载通常用于定义两个类之间的操作符行为。非成员函数重载操作符通常被声明为友元函数,以便它们可以访问类的私有和保护成员。非成员函数重载操作符通常用于定义二元操作符的行为,这些操作符作用于类的两个对象。
如下是一个非成员函数重载 + 操作符的样例:
#include <iostream> class MyClass
{
public:// 构造函数 MyClass(int val1,int val2) : m_val1(val1), m_val2(val2) {}// 重载 + 操作符为非成员函数 friend MyClass operator+(const MyClass& lhs, const MyClass& rhs);int getVal1() const { return m_val1; }int getVal2() const { return m_val2; }private:int m_val1;int m_val2;
};// 非成员函数重载 + 操作符
MyClass operator+(const MyClass& objLeft, const MyClass& objRight)
{return MyClass(objLeft.m_val1 + objRight.m_val1, objLeft.m_val2 + objRight.m_val2);
}int main()
{MyClass obj1(1, 2);MyClass obj2(3, 4);MyClass obj = obj1 + obj2;printf("obj.getVal1() : %d\n", obj.getVal1());printf("obj.getVal2() : %d\n", obj.getVal2());return 0;
}
上面代码的输出为:
obj.getVal1() : 4
obj.getVal2() : 6
在这个例子中, + 操作符被重载为一个非成员函数,并且被声明为 MyClass 类的友元。这意味着它可以访问 MyClass 类的私有成员 m_val1 和 m_val2 。非成员函数 operator+ 接受两个 MyClass 对象作为参数,并返回一个新的 MyClass 对象,该对象是两个输入对象对应成员的和。
由于 operator+ 是友元函数,因此它可以在类的外部定义,但仍然可以访问类的私有和保护成员。这允许为两个 MyClass 对象定义加法操作,而无需将这些对象作为类的成员函数传递。
使用友元函数重载非成员函数操作符的一个优点是,当操作符作用于不同类型的对象时,可以定义其行为。例如,可以重载 + 操作符,以便它可以将一个 MyClass 对象和一个 double 值相加。这无法使用类的成员函数重载实现,因为成员函数的第一个参数总是类的对象。
面试题54:重载操作符时,应该注意哪些事项
在 C++ 中重载操作符时,需要注意一些常见的错误和使用误区。以下是一些注意事项:
(1)不要过度使用操作符重载
操作符重载应该用于提供直观、自然的语法,而不是为了模仿语言本身不支持的操作。不要滥用操作符重载来创建含义模糊或令人困惑的接口。
(2)保持操作符的语义一致性
当重载一个操作符时,应该尽量保持该操作符的语义与它在其他上下文中的含义一致。例如,重载 +
操作符时,它应该表示加法操作,而不是其他任何不相关的操作。
(3)避免重载常用操作符
重载像&&
、||
、!
等常用逻辑操作符可能会导致代码可读性和可维护性下降。这些操作符在C++中有特定的含义,重载它们可能会导致混淆。
(4)注意返回类型
重载操作符时,要注意返回类型。例如,一元操作符(如 +
、 -
)通常返回操作数的类型,而二元操作符(如 +
、 -
、 *
、 /
)通常返回一个新的对象,该对象表示操作的结果。
(5)友元函数与成员函数的选择:根据需要选择将操作符重载为友元函数还是成员函数。友元函数允许访问类的私有和保护成员,这对于非成员操作符重载通常是有用的。然而,如果操作符的行为强烈依赖于类的状态,则将其作为成员函数可能更为合适。
(6)处理异常和错误
在重载的操作符中,要确保正确处理异常和错误。如果操作符的实现可能抛出异常,那么应该捕获这些异常并适当地处理它们。
(7)考虑对称性和交换性
对于一些二元操作符(如==
、!=
、<
、>
等),确保它们的行为是对称的,并且满足交换律(如果适用)。例如,如果a == b
为真,那么b == a
也应该为真。
面试题55:如何重载前缀自增与后缀自增操作符
重载前缀自增与后缀自增操作符允许自定义类型的自增行为。这些操作符的重载通常用于类,这些类代表可以递增的值,例如计数器或迭代器。如下为样例代码:
注意后缀自增操作符需要先保存当前值
#include <iostream>
#include <vector> class MyClass
{
public:// 构造函数 MyClass(int val = 0) : m_val(val) {}// 重载前缀自增操作符 ++ MyClass& operator++() {++m_val;return *this;}// 重载后缀自增操作符 ++ MyClass operator++(int) {MyClass temp = *this;++m_val;return temp;}int getVal() const { return m_val; }private:int m_val;};int main()
{MyClass obj(10); // 使用前缀自增 ++obj;printf("after ++obj , obj.getVal() : %d\n", obj.getVal());// 使用后缀自增 obj++;printf("after obj++ , obj.getVal() : %d\n", obj.getVal());return 0;
}
上面代码的输出为:
after ++obj , obj.getVal() : 11
after obj++ , obj.getVal() : 12
在上面代码中,MyClass 类有一个私有成员 m_val,用于存储计数器的当前值。前缀自增和自减操作符直接修改 m_val 并返回 *this 的引用,这样可以进行链式操作。后缀自增操作符则先保存当前值,然后修改 m_val ,并返回修改之前的值。注意,后缀操作符的参数是一个未使用的整数,这只是一个标记,用于区分前缀和后缀版本的操作符。
面试题56:能否重载所有的 C++ 操作符
在C++中,大部分操作符都是可以被重载的,但是有一些特殊的操作符是不能被重载的。以下是一些常见的可重载和不可重载的操作符列表:
可重载的操作符:
- 算术操作符:
+
、-
、*
、/
、%
- 关系操作符:
==
、!=
、>
、<
、>=
、<=
- 逻辑操作符:
&&
、||
、!
- 位操作符:
&
、|
、^
、~
、<<
、>>
- 赋值操作符:
=
、+=
、-=
、*=
、/=
、%=
、&=
、|=
、^=
、<<=
、>>=
- 下标操作符:
[]
- 前缀和后缀操作符:
++
、--
- 函数调用操作符:
()
- 逗号操作符:
,
- 内存管理操作符:
new
、new[]
、delete
、delete[]
不可重载的操作符: - 三目操作符:
? :
- 成员访问操作符:
.
- 成员指针访问操作符:
.*
- 作用域解析操作符:
::
- 条件操作符:
?:
sizeof
操作符typeid
操作符alignof
操作符noexcept
操作符( C++11 起)
需要注意的是,虽然大部分操作符都可以被重载,但是重载操作符时应该谨慎使用,避免创建可能导致混淆或误解的代码。同时,重载的操作符应该保持其原有的语义和用法,不应该改变其原有的含义、优先级和结合性。
面试题57:重载操作符[]时,应该考虑哪些因素
在C++中重载操作符[]
时,应该考虑以下几个因素:
(1)返回类型:应该决定 []
操作符应该返回什么类型的值。对于大多数类型,这可能是一个引用到成员变量的引用,这样可以通过它来读取或修改该值。
(2)边界检查:需要考虑是否应该在 []
操作符中进行边界检查。这通常取决于的类是如何设计的。例如,如果正在设计一个代表数组的类,那么需要在 []
操作符中进行边界检查以防止数组越界。然而,如果正在设计一个代表无限序列的类(如某些数学库中的序列类),那么一般不需要进行边界检查。
(3)异常处理:如果的 []
操作符可能会失败(例如,由于边界检查失败),那么需要决定应该如何处理这种失败。可能希望抛出一个异常,或者可能希望返回一个特殊的值(如 null 或默认值)来表示失败。
(4)常量版本:一般需要为 []
操作符提供一个常量版本(即一个接受 const 对象的版本)。这将允许在常量对象上使用 []
操作符,而不会违反 const
正确性。
(5)性能: []
操作符通常被期望有很高的性能,因为它在 C++ 中被广泛使用。因此,需要确保的实现尽可能高效。这可能意味着需要在某些情况下放弃某些安全检查,以提高性能。
(6)一致性: []
操作符的行为应该与其他类似的操作符或方法保持一致。例如,如果的类有一个 at() 方法用于访问元素并进行边界检查,那么 []
操作符的行为应该与 at() 方法相似,但可能在性能上有所不同。
总的来说,重载 []
操作符需要仔细考虑的类的设计、使用场景和性能要求。需要权衡各种因素,以提供一个既强大又易用的接口。
如下为样例代码:
#include <iostream>
#include <vector> class MyClass
{
public:// 构造函数 MyClass(int size) : m_datas(size,0) {}// 重载下标操作符 [] int& operator[](int index){if (index < 0 || index >= m_datas.size()) {throw std::out_of_range("out of range");}return m_datas[index];}// 重载下标操作符的常量版本,返回常量引用 const int& operator[](int index) const{if (index < 0 || index >= m_datas.size()) {throw std::out_of_range("out of range");}return m_datas[index];}private:std::vector<int> m_datas;};int main()
{MyClass myArray(10); // 创建一个大小为10的数组// 使用下标操作符设置和获取值 myArray[2] = 2;printf("myArray[2] = %d\n", myArray[2]);return 0;
}
上面代码的输出为:
myArray[2] = 2