21-x 第二十一章概要与测验(todo)

在本章中,我们探讨了与运算符重载相关的主题,包括重载的类型转换,以及与复制构造函数相关的主题。


摘要

运算符重载是函数重载的一种变体,允许您为类重载运算符。重载运算符时,应尽可能保持其含义与原始运算符的意图一致。若运算符应用于自定义类时含义不明确或不够直观,请改用命名函数。

运算符可作为普通函数、友元函数或成员函数进行重载。以下经验法则可帮助确定最适合特定场景的形式:

  • 若重载赋值运算符(=)、下标运算符([])、函数调用运算符(())或成员选择运算符(->),请采用成员函数形式。
  • 若重载一元运算符,请采用成员函数形式。
  • 若重载二元运算符且修改左操作数(如 operator+=),尽可能采用成员函数形式。
  • 若重载二元运算符且不修改左操作数(如 operator+),可采用普通函数或友元函数形式。

类型转换可重载为转换函数,用于显式或隐式地将类转换为其他类型。

复制构造函数是一种特殊构造函数,用于从同类型对象初始化新对象。其用途包括:同类型对象的直接/统一初始化、复制初始化(如 Fraction f = Fraction(5,3)),以及按值传递/返回参数时。

若未显式提供复制构造函数,编译器将自动生成。编译器生成的复制构造函数采用成员逐项初始化方式,即每个成员都从原始成员初始化。即使存在副作用,复制构造函数仍可能为优化而被省略,因此不应依赖其实际执行。

构造函数默认被视为转换构造函数,即编译器会将其用于隐式转换其他类型的对象为本类对象。可在构造函数前添加显式关键字避免此行为。还可删除类内的函数(包括复制构造函数和重载赋值运算符)。若删除的函数被调用,编译器将报错。

重载赋值运算符可实现向本类的赋值操作。若未提供重载赋值运算符,编译器将自动生成默认实现。重载赋值运算符必须包含自赋值检查(除非该情况自然发生,或采用复制交换模式)。

新手程序员常混淆赋值运算符与复制构造函数的使用场景,其实规则很明确:

  • 若复制操作需要先创建新对象(包括按值传递或返回对象),则使用复制构造函数。
  • 若复制操作无需创建新对象,则使用赋值运算符。

编译器默认提供的复制构造函数和赋值运算符执行成员逐项初始化或赋值,即浅拷贝。若类涉及动态内存分配,这可能导致多个对象指向同一块内存区域,从而引发问题。此时需显式定义深度拷贝操作。更优方案是避免自行管理内存,直接使用标准库中的类。


测验时间

问题 #1

假设 Point 是类,point 是该类的实例,对于以下运算符应使用普通/友元函数还是成员函数重载?

a) point + point

显示解答

二元运算符operator+ 最佳实现方式是作为普通函数/友元函数。

b) -point

显示解答

一元运算符operator-的最佳实现方式是作为成员函数。

c) std::cout << point

显示解答

operator<< 必须作为普通函数/友元函数实现。

d) point = 5;

显示解答

`operator=` 必须作为成员函数实现。

问题 #2

编写名为Average的类,用于记录传入的所有整数的平均值。使用两个成员:第一个成员应为std::int32_t类型,用于记录当前已处理数字的总和;第二个成员用于记录已处理的数字数量。通过除法运算可得出平均值。

a) 编写以下程序所需的所有函数:

int main()
{
	Average avg{};
	std::cout << avg << '\n';

	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 calls chained together
	std::cout << avg << '\n'; // (4 + 8 + 24 - 10 + 6 + 10) / 6 = 7

	Average copy{ avg };
	std::cout << copy << '\n';

	return 0;
}

并产生结果:

0
4
6
12
6.5
7
7

image

显示答案

#include <iostream>
#include <cstdint> // for fixed width integers

class Average
{
private:
	std::int32_t m_total{ 0 }; // the sum of all numbers we've seen so far
	int m_numbers{ 0 }; // the count of numbers we've seen so far

public:
	Average()
	{
	}

	friend std::ostream& operator<<(std::ostream& out, const Average& average)
	{
        // Handle case where we haven't seen any numbers yet
		if (average.m_numbers == 0)
		{
			out << 0;
			return out;
		}

		// Our average is the sum of the numbers we've seen divided by the count of the numbers we've seen
		// We need to remember to do a floating point division here, not an integer division
		out << static_cast<double>(average.m_total) / average.m_numbers;

		return out;
	}

	// Because operator+= modifies its left operand, we'll write it as a member
	Average& operator+=(std::int32_t num)
	{
		// Increment our total by the new number
		m_total += num;
		// And increase the count by 1
		++m_numbers;

		// return *this in case someone wants to chain +='s together
		return *this;
	}
};

int main()
{
	Average avg{};
	std::cout << avg << '\n';

	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 calls chained together
	std::cout << avg << '\n';

	Average copy{ avg };
	std::cout << copy << '\n';

	return 0;
}

b) 是否应为该类提供用户定义的复制构造函数或赋值运算符?

显示答案

不。因为在此处使用成员初始化/复制是可行的,使用编译器提供的默认值是可接受的。

c) 为什么使用 std::int32_t 而不是 int?

显示答案

在某些平台上,int 类型可能是 16 位,这意味着我们的 Average 对象的分子值最大只能达到 32,767。使用 std::int32_t 则能保证 32 位整数值,从而为我们提供更宽泛的操作范围。

问题 #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';

	auto& ref{ a }; // we're using this reference to avoid compiler self-assignment errors
	a = ref;

	IntArray b(1);
	b = a;

	a[4] = 7;

	std::cout << b << '\n';

	return 0;
}

该程序应输出:

5 8 2 3 6
5 8 2 3 6

image

显示答案

#include <iostream>
#include <cassert> // for assert

class IntArray
{
private:
	int m_length{ 0 };
	int* m_array{ nullptr };

public:
	explicit IntArray(int length)
		: m_length{ length }
	{
		assert(length > 0 && "IntArray length should be a positive integer");

		m_array = new int[static_cast<std::size_t>(m_length)] {};
	}

	// Copy constructor that does a deep copy
	IntArray(const IntArray& array)
		: m_length{ array.m_length }
	{
		// Allocate a new array
		m_array = new int[static_cast<std::size_t>(m_length)] {};

		// Copy elements from original array to new array
		for (int count{ 0 }; count < array.m_length; ++count)
			m_array[count] = array.m_array[count];
	}

	~IntArray()
	{
		delete[] m_array;
	}

	// If you're getting crazy values here you probably forgot to do a deep copy in your copy constructor
	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];
	}

	// Assignment operator that does a deep copy
	IntArray& operator= (const IntArray& array)
	{
		// self-assignment guard
		if (this == &array)
			return *this;

		// If this array already exists, delete it so we don't leak memory
		delete[] m_array;

		m_length = array.m_length;

		// Allocate a new array
		m_array = new int[static_cast<std::size_t>(m_length)] {};

		// Copy elements from original array to new array
		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() };

	// If you're getting crazy values here you probably forgot to do a deep copy in your copy constructor
	std::cout << a << '\n';

	auto& ref{ a }; // we're using this reference to avoid compiler self-assignment errors
	a = ref;

	IntArray b(1);
	b = a;

	a[4] = 7; // change value in a, b should not change

	// If you're getting crazy values here you probably forgot to do self-assignment check
	// If b ends in 7, you probably forgot to do a deep copy in your copy assignment
	std::cout << b << '\n';

	return 0;
}

----Question #4 start-----

问题 #4

附加题:这道题稍有难度。

浮点数是一种带小数点的数,其小数位数可变。固定点数是一种带小数部分的数,其小数位数固定。

本次测验中,我们将编写一个类来实现具有两位小数(例如 12.34、3.00 或 1278.99)的固定点数。假设该类的取值范围为-32768.99至32767.99,小数部分需能存储任意两位数字,要求避免精度误差并节省存储空间。

步骤 #1

你认为应采用何种成员变量类型来实现小数点后两位的定点数?(请务必阅读答案后再进行后续问题)

显示解决方案

实现定点数的方法多种多样。由于定点数本质上是浮点数的一个子类(其小数位数固定而非可变),使用浮点数似乎是显而易见的选项。但浮点数存在精度问题。当小数位数固定时,我们可以合理地枚举所有可能的分数值(在本例中为0.00至0.99),因此使用存在精度问题的数据类型并非最佳选择。
更优的解决方案是:采用16位有符号整数存储数值的整数部分,同时使用8位有符号整数存储小数部分。

步骤 #2

编写名为FixedPoint2的类,实现上题推荐方案。若数值的整数部分或小数部分(或两者)为负值,则该数值应视为负数。提供以下程序运行所需的重载运算符和构造函数。当前阶段无需处理小数部分超出范围的情况(>99或<-99)。

#include <cassert>
#include <iostream>

int main()
{
	FixedPoint2 a{ 34, 56 };
	std::cout << a << '\n';
	std::cout << static_cast<double>(a) << '\n';
	assert(static_cast<double>(a) == 34.56);

	FixedPoint2 b{ -2, 8 };
	assert(static_cast<double>(b) == -2.08);

	FixedPoint2 c{ 2, -8 };
	assert(static_cast<double>(c) == -2.08);

	FixedPoint2 d{ -2, -8 };
	assert(static_cast<double>(d) == -2.08);

	FixedPoint2 e{ 0, -5 };
	assert(static_cast<double>(e) == -0.05);

	FixedPoint2 f{ 0, 10 };
	assert(static_cast<double>(f) == 0.1);

	return 0;
}

该程序应输出以下结果:

34.56
34.56

提示:要输出数字,请将其静态转换为双精度类型。

显示解决方案

#include <cassert>
#include <cstdint> // for fixed width integers
#include <iostream>

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 }
	{
		// If either (or both) of the non-fractional and fractional part of the number are negative, the number should be treated as negative
		if (m_base < 0 || m_decimal < 0)
		{
			// Make sure base is negative
			if (m_base > 0)
				m_base = -m_base;
			// Make sure decimal is negative
			if (m_decimal > 0)
				m_decimal = -m_decimal;
		}
	}

	explicit operator double() const
	{
		return m_base + (static_cast<double>(m_decimal) / 100);
	}
};

// This doesn't require access to the internals of the class, so it can be defined outside the class
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';
	std::cout << static_cast<double>(a) << '\n';
	assert(static_cast<double>(a) == 34.56);

	FixedPoint2 b{ -2, 8 };
	assert(static_cast<double>(b) == -2.08);

	FixedPoint2 c{ 2, -8 };
	assert(static_cast<double>(c) == -2.08);

	FixedPoint2 d{ -2, -8 };
	assert(static_cast<double>(d) == -2.08);

	FixedPoint2 e{ 0, -5 };
	assert(static_cast<double>(e) == -0.05);

	FixedPoint2 f{ 0, 10 };
	assert(static_cast<double>(f) == 0.1);

	return 0;
}

步骤 #3

现在处理小数部分超出范围的情况。我们有两种合理策略:

  • 限制小数部分(若>99,则设为99)。
  • 将溢出视为有效(若>99,则减去100并向基数加1)。

本练习中我们将溢出视为有效,这在后续步骤中会很有用。

以下代码应能运行:

#include <cassert>
#include <iostream>

// You will need to make testDecimal a friend of FixedPoint2
// so the function can access the private members of FixedPoint2
bool testDecimal(const FixedPoint2 &fp)
{
    if (fp.m_base >= 0)
        return fp.m_decimal >= 0 && fp.m_decimal < 100;
    else
        return fp.m_decimal <= 0 && fp.m_decimal > -100;
}

int main()
{
	FixedPoint2 a{ 1, 104 };
	std::cout << a << '\n';
	std::cout << static_cast<double>(a) << '\n';
	assert(static_cast<double>(a) == 2.04);
	assert(testDecimal(a));

	FixedPoint2 b{ 1, -104 };
	assert(static_cast<double>(b) == -2.04);
	assert(testDecimal(b));

	FixedPoint2 c{ -1, 104 };
	assert(static_cast<double>(c) == -2.04);
	assert(testDecimal(c));

	FixedPoint2 d{ -1, -104 };
	assert(static_cast<double>(d) == -2.04);
	assert(testDecimal(d));

	return 0;
}

并输出结果:

2.04
2.04

显示解决方案

#include <cassert>
#include <cstdint> // for fixed width integers
#include <iostream>

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 }
	{
		// If either (or both) of the non-fractional and fractional part of the number are negative, the number should be treated as negative
		if (m_base < 0 || m_decimal < 0)
		{
			// Make sure base is negative
			if (m_base > 0)
				m_base = -m_base;
			// Make sure decimal is negative
			if (m_decimal > 0)
				m_decimal = -m_decimal;
		}

		// If decimal is out of bounds (in either direction),
		// adjust the decimal so it's in bounds,
		// and adjust base to account for the number of units removed from the decimal
		// h/t to reader David Pinheiro for simplifying this
		m_base += m_decimal / 100;    // integer division
		m_decimal = m_decimal % 100;  // integer remainder
	}

	explicit operator double() const
	{
		return m_base + (static_cast<double>(m_decimal) / 100);
	}

	friend bool testDecimal(const FixedPoint2 &fp);
};

// This doesn't require access to the internals of the class, so it can be defined outside the class
std::ostream& operator<<(std::ostream& out, const FixedPoint2& fp)
{
	out << static_cast<double>(fp);
	return out;
}

// You will need to make testDecimal a friend of FixedPoint2
// so the function can access the private members of FixedPoint2
bool testDecimal(const FixedPoint2 &fp)
{
	if (fp.m_base >= 0)
		return fp.m_decimal >= 0 && fp.m_decimal < 100;
	else
		return fp.m_decimal <= 0 && fp.m_decimal > -100;
}

int main()
{
	FixedPoint2 a{ 1, 104 };
	std::cout << a << '\n';
	std::cout << static_cast<double>(a) << '\n';
	assert(static_cast<double>(a) == 2.04);
	assert(testDecimal(a));

	FixedPoint2 b{ 1, -104 };
	assert(static_cast<double>(b) == -2.04);
	assert(testDecimal(b));

	FixedPoint2 c{ -1, 104 };
	assert(static_cast<double>(c) == -2.04);
	assert(testDecimal(c));

	FixedPoint2 d{ -1, -104 };
	assert(static_cast<double>(d) == -2.04);
	assert(testDecimal(d));

	return 0;
}

第4步

现在添加一个接受双精度浮点数的构造函数。以下程序应能运行:

#include <cassert>
#include <iostream>

int main()
{
	FixedPoint2 a{ 0.01 };
	assert(static_cast<double>(a) == 0.01);

	FixedPoint2 b{ -0.01 };
	assert(static_cast<double>(b) == -0.01);

	FixedPoint2 c{ 1.9 }; // make sure we handle single digit decimal
	assert(static_cast<double>(c) == 1.9);

	FixedPoint2 d{ 5.01 }; // stored as 5.0099999... so we'll need to round this
	assert(static_cast<double>(d) == 5.01);

	FixedPoint2 e{ -5.01 }; // stored as -5.0099999... so we'll need to round this
	assert(static_cast<double>(e) == -5.01);

	// Handle case where the argument's decimal rounds to 100 (need to increase base by 1)
	FixedPoint2 f { 106.9978 }; // should be stored with base 107 and decimal 0
	assert(static_cast<double>(f) == 107.0);

	// Handle case where the argument's decimal rounds to -100 (need to decrease base by 1)
	FixedPoint2 g { -106.9978 }; // should be stored with base -107 and decimal 0
	assert(static_cast<double>(g) == -107.0);

	return 0;
}

建议:此题稍有难度,请分三步完成。首先处理双精度参数可直接表示的情况(即上文变量a至c)。随后更新代码以处理双精度参数存在舍入误差的情况(变量d和e)。变量f和g应由前一步添加的溢出处理机制自动处理。

所有情况:显示提示

提示:将小数点右侧的数字乘以10,即可将其移至小数点左侧。乘以100则可移动两位。

变量a至c:显示提示

提示:可通过将双精度浮点数静态转换为整数来获取其整数部分。要获取小数部分,可减去基数部分。

变量d和e:显示提示

提示:你可以使用 std::round() 函数(包含在头文件 <cmath> 中)对小数点左侧的数字进行四舍五入,并使用 std::trunc() 函数对数字取舍入(向零方向舍入)。

显示解决方案

#include <cassert>
#include <cmath>
#include <cstdint> // for fixed width integers
#include <iostream>

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 }
	{
		// If either (or both) of the non-fractional and fractional part of the number are negative, the number should be treated as negative
		if (m_base < 0 || m_decimal < 0)
		{
			// Make sure base is negative
			if (m_base > 0)
				m_base = -m_base;
			// Make sure decimal is negative
			if (m_decimal > 0)
				m_decimal = -m_decimal;
		}

		// If decimal is out of bounds (in either direction),
		// adjust the decimal so it's in bounds,
		// and adjust base to account for the number of units removed from the decimal
		// h/t to reader David Pinheiro for simplifying this
		m_base += m_decimal / 100;    // integer division
		m_decimal = m_decimal % 100;  // integer remainder
	}

	// We'll delegate to the prior constructor so we don't have to duplicate the negative number and overflow handling logic
	FixedPoint2(double d) :
		FixedPoint2(
			static_cast<std::int16_t>(std::trunc(d)),
			static_cast<std::int8_t>(std::round(d * 100) - std::trunc(d) * 100)
		)
	{
	}

	explicit operator double() const
	{
		return m_base + (static_cast<double>(m_decimal) / 100);
	}
};

// This doesn't require access to the internals of the class, so it can be defined outside the class
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';
	assert(static_cast<double>(a) == 0.01);

	FixedPoint2 b{ -0.01 };
	assert(static_cast<double>(b) == -0.01);

	FixedPoint2 c{ 1.9 }; // make sure we handle single digit decimal
	assert(static_cast<double>(c) == 1.9);

	FixedPoint2 d{ 5.01 }; // stored as 5.0099999... so we'll need to round this
	assert(static_cast<double>(d) == 5.01);

	FixedPoint2 e{ -5.01 }; // stored as -5.0099999... so we'll need to round this
	assert(static_cast<double>(e) == -5.01);

	// Handle case where the argument's decimal rounds to 100 (need to increase base by 1)
	FixedPoint2 f { 106.9978 }; // should be stored with base 107 and decimal 0
	assert(static_cast<double>(f) == 107.0);

	// Handle case where the argument's decimal rounds to -100 (need to decrease base by 1)
	FixedPoint2 g { -106.9978 }; // should be stored with base -107 and decimal 0
	assert(static_cast<double>(g) == -107.0);

	return 0;
}

第5步

重载运算符==、运算符>>、运算符-(一元)和运算符+(二元)。

以下程序应能运行:

#include <cassert>
#include <iostream>

int main()
{
	assert(FixedPoint2{ 0.75 } == FixedPoint2{ 0.75 });    // Test equality true
	assert(!(FixedPoint2{ 0.75 } == FixedPoint2{ 0.76 })); // Test equality false

	// Test additional cases -- h/t to reader Sharjeel Safdar for these test cases
	assert(FixedPoint2{ 0.75 } + FixedPoint2{ 1.23 } == FixedPoint2{ 1.98 });    // both positive, no decimal overflow
	assert(FixedPoint2{ 0.75 } + FixedPoint2{ 1.50 } == FixedPoint2{ 2.25 });    // both positive, with decimal overflow
	assert(FixedPoint2{ -0.75 } + FixedPoint2{ -1.23 } == FixedPoint2{ -1.98 }); // both negative, no decimal overflow
	assert(FixedPoint2{ -0.75 } + FixedPoint2{ -1.50 } == FixedPoint2{ -2.25 }); // both negative, with decimal overflow
	assert(FixedPoint2{ 0.75 } + FixedPoint2{ -1.23 } == FixedPoint2{ -0.48 });  // second negative, no decimal overflow
	assert(FixedPoint2{ 0.75 } + FixedPoint2{ -1.50 } == FixedPoint2{ -0.75 });  // second negative, possible decimal overflow
	assert(FixedPoint2{ -0.75 } + FixedPoint2{ 1.23 } == FixedPoint2{ 0.48 });   // first negative, no decimal overflow
	assert(FixedPoint2{ -0.75 } + FixedPoint2{ 1.50 } == FixedPoint2{ 0.75 });   // first negative, possible decimal overflow

	FixedPoint2 a{ -0.48 };
	assert(static_cast<double>(a) == -0.48);
	assert(static_cast<double>(-a) == 0.48);

	std::cout << "Enter a number: "; // enter 5.678
	std::cin >> a;
	std::cout << "You entered: " << a << '\n';
	assert(static_cast<double>(a) == 5.68);

	return 0;
}

显示提示

提示:通过双重类型转换将两个FixedPoint2相加,将结果相加后再转换回FixedPoint2。这种方法能优雅地处理溢出的十进制数。

显示提示

提示:对于运算符>>,请使用双构造函数创建类型为FixedPoint2的匿名对象,并将其赋值给FixedPoint2函数参数。

显示答案

#include <cassert>
#include <cmath>
#include <cstdint> // for fixed width integers
#include <iostream>

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 }
	{
		// If either (or both) of the non-fractional and fractional part of the number are negative, the number should be treated as negative
		if (m_base < 0 || m_decimal < 0)
		{
			// Make sure base is negative
			if (m_base > 0)
				m_base = -m_base;
			// Make sure decimal is negative
			if (m_decimal > 0)
				m_decimal = -m_decimal;
		}

		// If decimal is out of bounds (in either direction),
		// adjust the decimal so it's in bounds,
		// and adjust base to account for the number of units removed from the decimal
		// h/t to reader David Pinheiro for simplifying this
		m_base += m_decimal / 100;    // integer division
		m_decimal = m_decimal % 100;  // integer remainder
	}

	FixedPoint2(double d) :
		FixedPoint2(
			static_cast<std::int16_t>(std::trunc(d)),
			static_cast<std::int8_t>(std::round(d * 100) - std::trunc(d) * 100)
		)
	{
	}

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

	FixedPoint2 operator-() const
	{
		// Cast to double, make the double negative, then convert back to FixedPoint2
		// h/t to reader EmtyC for this version
		return FixedPoint2{ -static_cast<double>(*this) };
	}
};

// This doesn't require access to the internals of the class, so it can be defined outside the class
std::ostream& operator<<(std::ostream& out, const FixedPoint2& fp)
{
	out << static_cast<double>(fp);
	return out;
}

std::istream& operator>>(std::istream& in, FixedPoint2& fp)
{
	double d{};
	in >> d;
	fp = FixedPoint2{ d };

	return in;
}

FixedPoint2 operator+(const FixedPoint2& fp1, const FixedPoint2& fp2)
{
	return FixedPoint2{ static_cast<double>(fp1) + static_cast<double>(fp2) };
}

int main()
{
	assert(FixedPoint2{ 0.75 } == FixedPoint2{ 0.75 });    // Test equality true
	assert(!(FixedPoint2{ 0.75 } == FixedPoint2{ 0.76 })); // Test equality false

	// Test additional cases -- h/t to reader Sharjeel Safdar for these test cases
	assert(FixedPoint2{ 0.75 } + FixedPoint2{ 1.23 } == FixedPoint2{ 1.98 });    // both positive, no decimal overflow
	assert(FixedPoint2{ 0.75 } + FixedPoint2{ 1.50 } == FixedPoint2{ 2.25 });    // both positive, with decimal overflow
	assert(FixedPoint2{ -0.75 } + FixedPoint2{ -1.23 } == FixedPoint2{ -1.98 }); // both negative, no decimal overflow
	assert(FixedPoint2{ -0.75 } + FixedPoint2{ -1.50 } == FixedPoint2{ -2.25 }); // both negative, with decimal overflow
	assert(FixedPoint2{ 0.75 } + FixedPoint2{ -1.23 } == FixedPoint2{ -0.48 });  // second negative, no decimal overflow
	assert(FixedPoint2{ 0.75 } + FixedPoint2{ -1.50 } == FixedPoint2{ -0.75 });  // second negative, possible decimal overflow
	assert(FixedPoint2{ -0.75 } + FixedPoint2{ 1.23 } == FixedPoint2{ 0.48 });   // first negative, no decimal overflow
	assert(FixedPoint2{ -0.75 } + FixedPoint2{ 1.50 } == FixedPoint2{ 0.75 });   // first negative, possible decimal overflow

	FixedPoint2 a{ -0.48 };
	assert(static_cast<double>(a) == -0.48);
	assert(static_cast<double>(-a) == 0.48);

	std::cout << "Enter a number: "; // enter 5.678
	std::cin >> a;
	std::cout << "You entered: " << a << '\n';
	assert(static_cast<double>(a) == 5.68);

	return 0;
}
----Question #4 end -----
posted @ 2026-01-26 18:02  游翔  阅读(1)  评论(0)    收藏  举报