我非常感谢您提供有关样式,清洁度或其他任何很好了解的帮助。
(我知道
using namespace std;
很难理解,但是在我们的课程中是必需的。)#include <iostream>
using namespace std;
class Fraction {
int numerator, denominator;
private:
int gcd(int a, int b) {
while (a != b) {
if (a > b)
a -= b;
else
b -= a;
}
return a;
}
public:
Fraction (int, int);
double decimal();
string fraction();
Fraction add(Fraction);
Fraction subtract(Fraction);
Fraction multiply(Fraction);
Fraction divide(Fraction);
};
int main() {
Fraction f = {2,3};
cout << f.decimal() << endl;
Fraction k = {1,4};
Fraction d = {1,3};
Fraction result = k.add(d).add(f);
cout << result.fraction() << endl;
return 0;
}
Fraction::Fraction(int n, int d) {
numerator = n / gcd(n, d);
denominator = d / gcd(n, d);
}
string Fraction::fraction() {
return to_string(numerator) + "/" + to_string(denominator);
}
double Fraction::decimal() {
return (double)numerator / (double)denominator;
}
Fraction Fraction::add(Fraction f) {
return Fraction(numerator * f.denominator + f.numerator * denominator, denominator * f.denominator);
}
Fraction Fraction::subtract(Fraction f) {
return Fraction(numerator * f.denominator - f.numerator * denominator, denominator * f.denominator);
}
Fraction Fraction::multiply(Fraction f) {
return Fraction(numerator * f.numerator, denominator * f.denominator);
}
Fraction Fraction::divide(Fraction f) {
return Fraction(numerator * f.denominator, denominator * f.numerator);
}
#1 楼
您希望获得一些样式和清洁方面的建议,因此您可以在该方面进行一些改进:您的课程中不要包括
gcd
,因为它实际上是完全无关的。而是为其编写一个单独的非类函数。命名您的参数。诸如
Fraction add(Fraction);
之类的内容通常暗示该参数将被忽略,即使用户理解此处不是这种情况,仍不清楚参数实际代表什么。包括所有使用的标头。您的代码当前缺少
#include <string>
。 进行一些错误检查,并确保参数有效。例如,当有人将
d
的0传递给构造函数时会发生什么?您将陷入无限循环,因为您的gcd
函数无法验证其参数是否为0。避免C样式转换。 C ++提供了各种具有明确定义用途的转换,而C转换包含许多不同的转换,其中某些转换可能是您不希望的。在您的情况下,请不要写
(double)numerator
;改写static_cast<double>(numerator)
(当然,与denominator
相同)。不要让
add
,subtract
,multiply
和divide
代替,而应该重载相应的运算符(例如operator+
的add
)。您还应该考虑添加更多运算符,例如operator+=
,operator-=
等。不要在简单的
std::endl
就足够了的情况下使用'\n'
(几乎总是这样)。原因是std::endl
还会刷新底层缓冲区,这在执行大量IO时会严重损害性能,并且很少需要。fraction
对于返回类的字符串表示形式的函数来说是一个坏名字。例如,您可以将其命名为to_string
,甚至可以选择实现explicit operator std::string()
。您可以放心地在
return 0;
的末尾省略main
,编译器可以自动添加它。当前,您的构造函数两次计算
gcd(n, d)
。您应该计算一次该值,然后将集合numerator = n / gcd_value
和denominator = d / gcd_value
相除。您应该考虑向构造函数的第二个参数中添加默认值
1
,以方便地从整数构造分数,即Fraction::Fraction(int n, int d = 1);
...
Fraction f {3}; //f represents the fraction 3/1
不需要时,请勿将单个字符视为字符串。根据您的情况,将
"/"
中的'/'
替换为fraction
。 operator+
中有一个std::string
可以处理单个char
。修复
Fraction (int, int)
中不必要的空格。从定义其他方法的方式来看,它应该改为Fraction(int, int)
(我想这只是一个错字)。编辑:正如JPhi1618在评论中指出的那样,最好将值分配给对象时,不要养成依赖于隐式构造的习惯。相反,应该使用显式构造和统一初始化。因此,我将点11的代码示例的最后一行从
Fraction f = 3;
更改为其当前表示形式。评论
\ $ \ begingroup \ $
谢谢!您的回答非常深入和有益。它对我有很大帮助。
\ $ \ endgroup \ $
–正在加载...
17-10-19在17:02
\ $ \ begingroup \ $
@loading ...非常欢迎!请询问您是否还有任何不清楚的地方。
\ $ \ endgroup \ $
–本·斯特凡(Ben Steffan)
17-10-19在17:02
\ $ \ begingroup \ $
还有std :: gcd。无需重新实现它。
\ $ \ endgroup \ $
–Rakete1111
17-10-19在18:24
\ $ \ begingroup \ $
@ Rakete1111我完全不知道。谢谢!
\ $ \ endgroup \ $
–本·斯特凡(Ben Steffan)
17-10-19在18:59
\ $ \ begingroup \ $
如何进行模板而不是int,以便我们可以使用long或long long的分数?那么理智地处理负分子和分母又如何呢?
\ $ \ endgroup \ $
– David Schwartz
17-10-19在22:40
#2 楼
Ben Staffen的好答案,只是想添加:在传递复杂类型时,最好使用const引用。它可能与此类无关,但是随着类型的构造变得更加复杂/昂贵,const-refs会对性能产生巨大的影响。
默认情况下,使用类将成员设为私有,因此您拥有的第一个私有说明符不必要。
#3 楼
Ben Steffan建议用add
代替operator+
并添加operator+=
。这样做是一个好主意,但是遵循以下特定模式是明智的:将operator+=
实现为Fraction& Fraction::operator+=(Fraction)
(即成员函数),并将operator+
实现为自由函数inline Fraction operator+(Fraction lhs, Fraction rhs) {
lhs += rhs;
return lhs;
}
由于这依赖于公众
operator+=
,所以不必成为朋友。[编辑]
并对此进行扩展,mvds正确地怀疑
decimal
是正确的。通常将其拼写为operator double
。您将要创建explicit
,这样就不会隐式转换为double
。例如。如果没有explicit
,则std::cout << Fraction{4}
将打印4.0000
,因为它使用了隐式转换。#4 楼
计算时不要害怕引入局部变量。例如,您的加法和减法函数有很长的一行。将它们写为以下内容会更清楚:Fraction Fraction::add(Fraction f) {
int addNumerator = numerator * f.denominator + f.numerator * denominator;
int addDenomenator = denominator * f.denominator;
return Fraction(addNumerator, addDenomenator);
}
这有两件事。它为中间值提供了有意义的名称,以便人们可以更轻松地进行计算。当您开始调试程序并逐行进行调试时,现在分别定义了计算的各个部分,因此可以轻松检查它们。任何现代的编译器都将足够聪明,可以在使用优化进行编译后删除局部变量的使用。
您有一个简单的计算和一个简短的类,因此这似乎有些多余,但更多复杂的情况可能会有所帮助。
评论
\ $ \ begingroup \ $
对于更多的Internet点,这些中间变量也应为const。
\ $ \ endgroup \ $
–user31517
17-10-20在20:40
\ $ \ begingroup \ $
@雪人,有趣。我认为我在实践中从未见过这种情况,编译器也将弄清楚这一点。
\ $ \ endgroup \ $
–JPhi1618
17-10-20在20:41
\ $ \ begingroup \ $
应该弄清楚了。我了解C ++的一件事是从不承担任何责任。
\ $ \ endgroup \ $
–user31517
17-10-20在20:42
#5 楼
除了其他答案之外,使用
const
关键字强制不变性。这可以用于不更改对象状态的功能的签名。 double decimal() const;
在c ++中传递对象(不会被函数改变)的首选方式是const reference例如。 Fraction add(const Fraction &f)
。这比按值传递便宜,因为不需要复制构造对象。只能在const对象上调用
const
函数。因此,通过声明一个函数const in,然后可以在诸如void print(const Fraction &f);
之类的函数主体中调用当前类的实现是不可变的,没有任何成员函数以任何方式更改内容。如果需要这样做,请执行
numerator
和denominator
const
。为了强制执行不变性,请在每个方法调用上创建一个新对象。不幸的是,对象创建不是免费的。取而代之的是,我让调用者决定是要新对象还是修改现有对象。
当前,分数的简化是由构造函数处理的。如果对象是不可变的,这很好。但是,我将创建一个
simplify()
方法来执行此简化。这使得逻辑可以在多个地方重用。然后,构造函数可能成为Fraction(int n, int d) : numerator(n), denominator(d) { simplify(); }
正如其他人提到的,c ++的工作方式是重载运算符,而不是编写诸如
add()
之类的函数。将运算符+=
,-=
,*=
和/=
定义为成员函数,然后根据这些定义非成员+
,-
,*
和/
。 Fraction& operator+=(const Fraction &f)
{
numerator += f.numerator * denominator;
denominator += f.denominator * nominator;
simplify();
return *this;
}
Fraction operator+(Fraction l, const Fraction &r)
{
l += r;
return l;
}
l
是复制构造的,因为它按值传递,而传递的对象保持不变。该功能不会更改r
,因此它也是const
。
评论
这里的十进制函数是什么“十进制”?@mvds它似乎返回小数的十进制/浮点值的事实:对于1/4为0.25。
@TripeHound返回一个(二进制)浮点数,代表0.25(十进制),但不等于“ 0.25”。 “十进制”一词通常表示以10为底,此处不涉及底数(2除外)。如果返回字符串或与基数10有关的某种数据类型,情况将有所不同。
@mvds我(略)明白你的意思:双精度不是“十进制”,但是“分数”与“十进制”似乎是一个足够普通的概念(加上浮点数几乎从未显示在任何东西上的事实除了base-10以外),调用函数十进制是合理的。我承认,像toDouble这样的技术在技术上可能更正确...
@TripeHound“足够普通了” ...这就是微妙的错误和技术债务进入现实世界代码的方式:了解编程但并不完全了解编程内容的程序员。如果添加了一个hex()函数,该函数返回一个字符串十六进制表示形式,binary()或octal(),将会发生什么?我想它们会很“普通”,并且会与原有的decimal()函数完全不匹配。强制转换运算符在这里会更适合。