综合测验
在本章中,我们探讨了与运算符重载相关的主题,以及重载的类型转换以及与拷贝构造函数相关的主题。
Summary:
运算符重载是函数重载的一种变体,它允许您为类重载运算符。当运算符重载时,运算符的意图应尽可能接近运算符的初始意图。如果应用于自定义类的运算符的含义不清晰直观,请改用命名函数。
操作符可以作为普通函数,友元函数或成员函数重载。以下经验法则可帮助您确定哪种形式最适合给定情况:
#如果您正在重载赋值(=),下标([]),函数调用(())或成员选择( - >),请将其作为成员函数执行。
#如果您正在重载一元运算符,请将其作为成员函数执行。
#如果你正在重载修改其左操作数的二元运算符(例如operator + =),那么如果可以的话,就这样做。
#如果您正在重载不修改其左操作数的二元运算符(例如operator +),请将其作为普通函数或友元函数执行。
#可以重载Typecasts来提供转换函数,这些函数可以用于显式或隐式地将类转换为另一种类型。
拷贝构造函数是一种特殊类型的构造函数,用于从同一类型的另一个对象初始化对象。拷贝构造函数用于从相同类型的对象进行直接/统一初始化,复制初始化(Fraction f =Fraction(5,3)),以及按值传递或返回参数时。
如果您不提供拷贝构造函数,编译器将为您创建一个。编译器提供的副本构造函数将使用成员初始化,这意味着副本的每个成员都是从原始成员初始化的。可以省略拷贝构造函数以进行优化,即使它具有副作用,因此不要依赖于实际执行的拷贝构造函数。
构造函数被认为是默认情况下转换构造函数,这意味着编译器将使用它们隐式地将其他类型的对象转换为类的对象。您可以通过在构造函数前使用explicit关键字来避免这种情况。您还可以删除类中的函数,包括拷贝构造函数和重载赋值运算符(如果需要)。如果将调用已删除的函数,这将导致编译器错误。
赋值运算符可以重载以允许分配给您的类。如果您没有提供重载赋值运算符,编译器将为您创建一个。重载的赋值运算符应始终包含自赋值检查。
新的程序员经常在使用赋值运算符和复制构造函数时混淆,但它相当简单:
#如果必须在复制之前创建新对象,则使用复制构造函数(注意:这包括按值传递或返回对象)。
#如果在复制发生之前不必创建新对象,则使用赋值运算符。
#默认情况下,编译器提供的复制构造函数和赋值运算符执行成员初始化或赋值,这是一个浅表副本。如果您的类#动态分配内存,这可能会导致问题,因为多个对象最终将指向相同的已分配内存。在这种情况下,您需要明确定义这些以进行深层复制。更好的是,如果可以并且使用标准库中的类,请避免自己进行内存管理。
Quiz Time
1)假设Point是一个类,而point是该类的一个实例,你应该为以下运算符使用普通/友元或成员函数重载吗?
1a)point+point
1b) - point
1c)std :: cout << point
1d)point = 5;
解决方案
1a)二元运算符+最好实现为普通/友元函数。
1b)一元运算符 - 最好作为成员函数实现。
1c)operator <<必须实现为普通/友元函数。
1d)operator =必须作为成员函数实现。
2)编写一个名为Average的类,它将跟踪传递给它的所有整数的平均值。使用两个成员:第一个应该是int32_t类型,用于跟踪到目前为止您看到的所有数字的总和。第二个应该是int8_t类型,用于跟踪到目前为止你看过多少个数字。您可以将它们分开以找到您的平均值。
2a)编写以下程序运行所需的所有功能:
int main()
{
Average avg;
avg += 4;
std::cout << avg << '\n'; // 4 / 1 = 4
avg += 8;
std::cout << avg << '\n'; // (4 + 8) / 2 = 6
avg += 24;
std::cout << avg << '\n'; // (4 + 8 + 24) / 3 = 12
avg += -10;
std::cout << avg << '\n'; // (4 + 8 + 24 - 10) / 4 = 6.5
(avg += 6) += 10; // 2 链式调用
std::cout << avg << '\n'; // (4 + 8 + 24 - 10 + 6 + 10) / 6 = 7
Average copy = avg;
std::cout << copy << '\n';
return 0;
}
并产生结果:
4
6
12
6.5
7
7
提示:请记住,int8_t通常是作为char的typedef,所以std :: cout会相应地对待它。
解决方案
#include <iostream>
#include <cstdint> // 固定宽度正数
class Average
{
private:
int32_t m_total = 0; // 对输入的数字求和
int8_t m_numbers = 0; //输入数字的个数
public:
Average()
{
}
friend std::ostream& operator<<(std::ostream &out, const Average &average)
{
//平均值是目前所有输入数字之和除以输入数字的个数
// 我们需要记住在这里进行浮点除法,而不是整数除法
out << static_cast<double>(average.m_total) / average.m_numbers;
return out;
}
// 因为operator + =修改了它的左操作数,所以我们将它写成一个成员
Average& operator+=(int num)
{
// 将心输入的数字加到前面的和数上
m_total += num;
// 并且count加一
++m_numbers;
// 返回*this是为了之后要链接+ =
return *this;
}
};
int main()
{
Average avg;
avg += 4;
std::cout << avg << '\n';
avg += 8;
std::cout << avg << '\n';
avg += 24;
std::cout << avg << '\n';
avg += -10;
std::cout << avg << '\n';
(avg += 6) += 10; // 2 c链式调用
std::cout << avg << '\n';
Average copy = avg;
std::cout << copy << '\n';
return 0;
}
2b)这个类是否需要显式的拷贝构造函数或赋值运算符?
解决方案
答:不可以。因为在这里使用成员初始化/复制很好,所以使用编译器提供的默认值是可以接受的。
3)从头开始编写自己的整数数组类IntArray(不要使用std :: array或std :: vector)。用户应在创建数组时传入数组的大小,并且应该动态分配数组。使用assert语句来防止错误数据。创建使以下程序正常运行所需的任何构造函数或重载运算符:
#include <iostream>
IntArray fillArray()
{
IntArray a(5);
a[0] = 5;
a[1] = 8;
a[2] = 2;
a[3] = 3;
a[4] = 6;
return a;
}
int main()
{
IntArray a = fillArray();
std::cout << a << '\n';
IntArray b(1);
a = a;
b = a;
std::cout << b << '\n';
return 0;
}
这个程序应该打印:
5 8 2 3 6
5 8 2 3 6
解决方案
#include <iostream>
#include <cassert> // for assert
class IntArray
{
private:
int m_length = 0;
int *m_array = nullptr;
public:
IntArray(int length):
m_length(length)
{
assert(length > 0 && "IntArray length should be a positive integer");
m_array = new int[m_length] { 0 };
}
// 拷贝执行深层复制的构造函数
IntArray(const IntArray &array):
m_length(array.m_length)
{
//分配一个新的array
m_array = new int[m_length];
// 将元素从原始数组复制到新数组
for (int count = 0; count < array.m_length; ++count)
m_array[count] = array.m_array[count];
}
~IntArray()
{
delete[] m_array;
}
//如果你在这里获得莫名其妙值,你可能忘了在你的复制构造函数中做一个深层复制
friend std::ostream& operator<<(std::ostream &out, const IntArray &array)
{
for (int count = 0; count < array.m_length; ++count)
{
out << array.m_array[count] << ' ';
}
return out;
}
int& operator[] (const int index)
{
assert(index >= 0);
assert(index < m_length);
return m_array[index];
}
// 执行深层复制的赋值运算符
IntArray& operator= (const IntArray &array)
{
// self-assignment guard
if (this == &array)
return *this;
//如果此数组已存在,请将其删除,以免我们泄漏内存
delete[] m_array;
m_length = array.m_length;
// 分配一个新的array
m_array = new int[m_length];
// 将元素从原始数组复制到新数组
for (int count = 0; count < array.m_length; ++count)
m_array[count] = array.m_array[count];
return *this;
}
};
IntArray fillArray()
{
IntArray a(5);
a[0] = 5;
a[1] = 8;
a[2] = 2;
a[3] = 3;
a[4] = 6;
return a;
}
int main()
{
IntArray a = fillArray();
// 如果你在这里获得莫名其妙值,你可能忘了在你的复制构造函数中做一个深层复制
std::cout << a << '\n';
IntArray b(1);
a = a;
b = a;
// 如果你在这里获得了莫名其妙值,你可能忘了在你的赋值算子中做一个深层复制
// 或者你忘记了自我赋值检查
std::cout << b << '\n';
return 0;
}
4)额外的任务(有兴趣可以做一下):这个有点棘手。浮点数是带小数的数字,小数点后的位数可以是变量。固定点数是具有小数分量的数字,其中小数部分中的位数是固定的。
在这个检测中,我们将编写一个类来实现具有两个小数位的固定点数(例如12.34,3.00或1278.99)。假设类的范围应该是-32768.99到32767.99,小数组件应该保持任何两位数,我们不希望精度错误,并且我们想要节省空间。
4a)您认为我们应该使用哪种类型的成员变量来实现小数点后2位数的固定点数?(在继续下一个问题之前,请务必阅读答案)
解决方案
答:有许多不同的方法来实现固定点数。因为固定点数基本上是浮点数的子例(其中十进制之后的位数是固定的而不是变量),使用浮点数似乎是一个明显的选择。但浮点数有精确问题。使用固定数量的十进制数字,我们可以合理地枚举所有可能的小数值(在我们的例子中,.00到.99),因此使用具有精度问题的数据类型不是最佳选择。更好的解决方案是使用16位有符号整数来保存数字的非小数部分,使用8位有符号整数来保存小数部分。
4b)编写一个名为FixedPoint2的类,它实现了上一个问题的推荐解决方案。如果数字的非小数部分和小数部分中的任何一个(或两个)都是负数,则应将该数字视为负数。提供运行以下程序所需的重载运算符和构造函数:
int main()
{
FixedPoint2 a(34, 56);
std::cout << a << '\n';
FixedPoint2 b(-2, 8);
std::cout << b << '\n';
FixedPoint2 c(2, -8);
std::cout << c << '\n';
FixedPoint2 d(-2, -8);
std::cout << d << '\n';
FixedPoint2 e(0, -5);
std::cout << e << '\n';
std::cout << static_cast<double>(e) << '\n';
return 0;
}
该程序应该产生结果:
34.56
-2.08
-2.08
-2.08
-0.05
-0.05
提示:虽然最初看起来最初可能起作用更多,但是将数字的非小数部分和小数部分都用相同的符号存储是有帮助的(例如,如果数字为正数则为正数,如果数字为负数则均为负数) 。这使得以后更容易做数学。
提示:要输出您的号码,首先将其转换为双倍。
解决方案
#include <iostream>
#include <cstdint> //对于固定宽度整数
class FixedPoint2
{
private:
std::int16_t m_base; // here's our non-fractional part
std::int8_t m_decimal; // here's our factional part
public:
FixedPoint2(std::int16_t base = 0, std::int8_t decimal = 0)
: m_base(base), m_decimal(decimal)
{
// We should handle the case where decimal is > 99 or < -99 here
// but will leave as an exercise for the reader
// If either the base or decimal or negative
if (m_base < 0.0 || m_decimal < 0.0)
{
// Make sure base is negative
if (m_base > 0.0)
m_base = -m_base;
// Make sure decimal is negative
if (m_decimal > 0.0)
m_decimal = -m_decimal;
}
}
operator double() const
{
return m_base + static_cast<double>(m_decimal) / 100;
}
friend std::ostream& operator<<(std::ostream &out, const FixedPoint2 &fp)
{
out << static_cast<double>(fp);
return out;
}
};
int main()
{
FixedPoint2 a(34, 56);
std::cout << a << '\n';
FixedPoint2 b(-2, 8);
std::cout << b << '\n';
FixedPoint2 c(2, -8);
std::cout << c << '\n';
FixedPoint2 d(-2, -8);
std::cout << d << '\n';
FixedPoint2 e(0, -5);
std::cout << e << '\n';
std::cout << static_cast<double>(e) << '\n';
return 0;
}
4c)现在添加一个带双精度的构造函数。您可以使用round()函数(包含在头文件cmath中)对数字(小数点左侧)进行舍入。
提示:通过静态将double转换为整数,可以得到double的非小数部分
提示:要获得double的小数部分,首先需要将非小数部分清零。使用整数值执行此操作。
提示:您可以通过乘以10将小数点右侧的数字移动到小数点的左侧。您可以将它移动两位数乘以100。
以下程序应该运行:
int main()
{
FixedPoint2 a(0.01);
std::cout << a << '\n';
FixedPoint2 b(-0.01);
std::cout << b << '\n';
FixedPoint2 c(5.01); // stored as 5.0099999... so we'll need to round this
std::cout << c << '\n';
FixedPoint2 d(-5.01); // stored as -5.0099999... so we'll need to round this
std::cout << d << '\n';
return 0;
}
该程序应该产生结果
0.01
-0.01
5.01
-5.01
解决方案
#include <iostream>
#include <cstdint> // 对于固定宽度整数
#include <cmath> // round()
class FixedPoint2
{
private:
std::int16_t m_base; // 这是我们的非小数部分
std::int8_t m_decimal; // 这是我们的小数部分
public:
FixedPoint2(std::int16_t base = 0, std::int8_t decimal = 0)
: m_base(base), m_decimal(decimal)
{
// 我们应该处理小数大于 99或小于-99的情况
// 但是会留给读者作为练习
// 如果是基数或小数或负数
if (m_base < 0.0 || m_decimal < 0.0)
{
// 确保基数为负数
if (m_base > 0.0)
m_base = -m_base;
// 确保小数为负数
if (m_decimal > 0.0)
m_decimal = -m_decimal;
}
}
FixedPoint2(double d)
{
// 首先,我们需要获得非小数分量
// 我们可以通过将double转换为整数来实现
m_base = static_cast<int16_t>(d); // truncates fraction
//现在我们需要获得小数部分:
// 1) d - m_base只留下分数部分
// 2) 我们可以乘以100来将数字移动到小数点的左边
// 3) 然后我们可以round这个
// 4) 最后,静态强制转换为整数以删除任何额外的小数
m_decimal = static_cast<std::int8_t>(round((d - m_base) * 100));
}
operator double() const
{
return m_base + static_cast<double>(m_decimal) / 100;
}
friend std::ostream& operator<<(std::ostream &out, const FixedPoint2 &fp)
{
out << static_cast<double>(fp);
return out;
}
};
int main()
{
FixedPoint2 a(0.01);
std::cout << a << '\n';
FixedPoint2 b(-0.01);
std::cout << b << '\n';
FixedPoint2 c(5.01); // 存储为5.0099999 ...所以我们需要round这个
std::cout << c << '\n';
FixedPoint2 d(-5.01); // 存储为-5.0099999 ...所以我们需要round这个
std::cout << d << '\n';
return 0;
}
4d)重载运算符==,运算符>>,运算符 - (一元)和运算符+(二进制)。
应运行以下程序:
void testAddition()
{
std::cout << std::boolalpha;
std::cout << (FixedPoint2(0.75) + FixedPoint2(1.23) == FixedPoint2(1.98)) << '\n'; // 两者都正数,没有十进制溢出
std::cout << (FixedPoint2(0.75) + FixedPoint2(1.50) == FixedPoint2(2.25)) << '\n'; // 两者都是正数,带十进制溢出
std::cout << (FixedPoint2(-0.75) + FixedPoint2(-1.23) == FixedPoint2(-1.98)) << '\n'; // 均为负数,无十进制溢出
std::cout << (FixedPoint2(-0.75) + FixedPoint2(-1.50) == FixedPoint2(-2.25)) << '\n'; // 均为负数,带十进制溢出
std::cout << (FixedPoint2(0.75) + FixedPoint2(-1.23) == FixedPoint2(-0.48)) << '\n'; // 第二个负数,没有十进制溢出
std::cout << (FixedPoint2(0.75) + FixedPoint2(-1.50) == FixedPoint2(-0.75)) << '\n'; // 第二个负数,可能的十进制溢出
std::cout << (FixedPoint2(-0.75) + FixedPoint2(1.23) == FixedPoint2(0.48)) << '\n'; // 第一个负数,没有十进制溢出
std::cout << (FixedPoint2(-0.75) + FixedPoint2(1.50) == FixedPoint2(0.75)) << '\n'; //第一个负数,可能的十进制溢出
}
int main()
{
testAddition();
FixedPoint2 a(-0.48);
std::cout << a << '\n';
std::cout << -a << '\n';
std::cout << "Enter a number: "; // 输入5.678
std::cin >> a;
std::cout << "You entered: " << a << '\n';
return 0;
}
并产生输出:
true
true
true
true
true
true
true
true
-0.48
0.48
Enter a number:5.678
You entered:5.68
**提示:**通过利用双重投射,添加结果并转换回FixedPoint2,将两个FixedPoint2添加到一起。
**提示:**对于operator >>,使用双构造函数创建一个FixedPoint2类型的匿名对象,并将其分配给您的FixedPoint2函数参数
解决方案
#include <iostream>
#include <cstdint> // 对于固定宽度整数
#include <cmath> // round()
class FixedPoint2
{
private:
std::int16_t m_base; // 这是我们的非小数部分
std::int8_t m_decimal; // 这是我们的小数部分
public:
FixedPoint2(std::int16_t base = 0, std::int8_t decimal = 0)
: m_base(base), m_decimal(decimal)
{
// 我们应该处理小数大于 99或小于-99的情况
// 但是会留给读者作为练习
// 如果是基数或小数或负数
if (m_base < 0.0 || m_decimal < 0.0)
{
// 确保基数为负数
if (m_base > 0.0)
m_base = -m_base;
// 确保小数为负数
if (m_decimal > 0.0)
m_decimal = -m_decimal;
}
}
FixedPoint2(double d)
{
// 首先,我们需要获得非小数分量
// 我们可以通过将double转换为整数来实现
m_base = static_cast<int16_t>(d); // truncates fraction
//现在我们需要获得小数部分:
// 1) d - m_base只留下分数部分
// 2) 我们可以乘以100来将数字移动到小数点的左边
// 3) 然后我们可以round这个
// 4) 最后,静态强制转换为整数以删除任何额外的小数
m_decimal = static_cast<std::int8_t>(round((d - m_base) * 100));
}
operator double() const
{
return m_base + static_cast<double>(m_decimal) / 100;
}
friend bool operator==(const FixedPoint2 &fp1, const FixedPoint2 &fp2)
{
return (fp1.m_base == fp2.m_base && fp1.m_decimal == fp2.m_decimal);
}
friend std::ostream& operator<<(std::ostream &out, const FixedPoint2 &fp)
{
out << static_cast<double>(fp);
return out;
}
friend std::istream& operator >> (std::istream &in, FixedPoint2 &fp)
{
double d;
in >> d;
fp = FixedPoint2(d);
return in;
}
friend FixedPoint2 operator+(const FixedPoint2 &fp1, const FixedPoint2 &fp2)
{
return FixedPoint2(static_cast<double>(fp1) + static_cast<double>(fp2));
}
FixedPoint2 operator-()
{
return FixedPoint2(-m_base, -m_decimal);
}
};
void testAddition()
{
std::cout << std::boolalpha;
std::cout << (FixedPoint2(0.75) + FixedPoint2(1.23) == FixedPoint2(1.98)) << '\n'; // 两者都正数,没有十进制溢出
std::cout << (FixedPoint2(0.75) + FixedPoint2(1.50) == FixedPoint2(2.25)) << '\n'; // 两者都是正数,带十进制溢出
std::cout << (FixedPoint2(-0.75) + FixedPoint2(-1.23) == FixedPoint2(-1.98)) << '\n'; // 均为负数,无十进制溢出
std::cout << (FixedPoint2(-0.75) + FixedPoint2(-1.50) == FixedPoint2(-2.25)) << '\n'; // 均为负数,带十进制溢出
std::cout << (FixedPoint2(0.75) + FixedPoint2(-1.23) == FixedPoint2(-0.48)) << '\n'; // 第二个负数,没有十进制溢出
std::cout << (FixedPoint2(0.75) + FixedPoint2(-1.50) == FixedPoint2(-0.75)) << '\n'; // 第二个负数,可能的十进制溢出
std::cout << (FixedPoint2(-0.75) + FixedPoint2(1.23) == FixedPoint2(0.48)) << '\n'; // 第一个负数,没有十进制溢出
std::cout << (FixedPoint2(-0.75) + FixedPoint2(1.50) == FixedPoint2(0.75)) << '\n'; //第一个负数,可能的十进制溢出
}
int main()
{
testAddition();
FixedPoint2 a(-0.48);
std::cout << a << '\n';
std::cout << -a << '\n';
std::cout << "Enter a number: "; // enter 5.678
std::cin >> a;
std::cout << "You entered: " << a << '\n';
return 0;
}