10

我正在开发一个用于教育目的的简单数学库,并且我已经实现了一个struct代表Rational Number的。显示结构核心字段的非常基本的代码是:

public struct RationalNumber
{
    private readonly long numerator;
    private readonly long denominator;
    private bool isDefinitelyCoprime;
    private static RationalNumber zero = 0;

    public RationalNumber(long numerator, long denominator)
    {
        this.numerator = numerator;
        this.denominator = denominator;
        this.isDefinitelyCoprime = false;
    }

    ...
}

目前我正在实现一个RationalMatrix,正如您可能已经猜到的那样,它将由RationalNumber类型化元素组成。

我正在为其创建静态构建器的有用矩阵是身份矩阵。代码如下:

public static RationalMatrix GetIdentityMatrix(int dimension)
{
    RationalNumber[,] values = new RationalNumber[dimension, dimension];

    for (int i = 0; i < dimension; i++)
       values[i, i] = 1;

    return new RationalMatrix(values);
}

问题是这不起作用,因为 my 的默认值RationalNumber不是,0/1而是0/0一种特殊的值(不确定形式)。

显然,一种解决方案很简单,只需将方法更改为:

public static RationalMatrix GetIdentityMatrix(int dimension)
{
    RationalNumber[,] values = new RationalNumber[dimension, dimension];

    for (int i = 0; i < dimension; i++)
       for (int j = i+1 ; j < dimension; j++)
       {
           values[i, i] = 1;
           values[i, j] = RationalNumber.Zero;
           values[j, i] = RationalNumber.Zero;
       }

       return new RationalMatrix(values);
}

但这在某种程度上似乎是一种浪费,因为我基本上将整个数组的值初始化了两次。我有点认为以某种方式将默认值设为RationalNumberequal会更优雅0/1。如果 RationalNumber 是 a ,这将很容易做到class,但是当它是 a 时,我想不出办法来做到这一点struct。我是否遗漏了一些明显的东西,或者没有办法避免将其 0/0作为我的默认值?

我想指出我根本不关心代码性能(如果这是我的瓶颈,那么我已经远远超出了我的目标)。我只是想知道是否有一些构造(我不知道)允许您在struct.

编辑:错别字

编辑2:扩大问题范围

好的,似乎没有办法struct根据我有限的 C# 知识从我得到的输入和我自己的结论中强加任意默认值。

有人可以告诉我为什么结构必须以这种方式运行吗?是出于某种原因,还是因为没有人想到指定选项来定义默认值而以这种方式实现?

4

4 回答 4

5

如果您不必区分不确定的 0/0 和其他 0/N 值,则可以将所有 0/N 视为零。也就是说,所有零都相等,这是有道理的(0/2 等于 0/1),并且所有除以零都是相等的,所以 1/0 == 2/0。

public struct RationalNumber : IEquatable<RationalNumber>
{
    private readonly long numerator;
    private readonly long denominator;

    public RationalNumber(long numerator, long denominator)
    {
        this.numerator = numerator;
        this.denominator = denominator;
    }

    public bool IsZero 
    { 
       get { return numerator == 0; }
    }

    public bool IsInvalid 
    { 
       get { return denominator == 0 && numerator != 0; }
    }

    public bool Equals(RationalNumber r)
    {
       if (r.IsZero && IsZero)
         return true;
       if (r.IsInvalid && IsInvalid)
         return true;
       return denominator == r.denominator && numerator == r.numerator;
    }

    public bool Equals(object o)
    {
       if (!(o is RationalNumber))
         return false;
       return Equals((RationalNumber)o);
    }

    public int GetHashCode()
    {
       if (IsZero) 
         return 0;
       if (IsInvalid)
         return Int32.MinValue;
       return ((float)numerator/denominator).GetHashCode();
    }
}   
于 2013-05-03T13:07:44.443 回答
1

您不能拥有分配默认值的无参数构造函数。技术原因是 yourstruct是 的子类System.ValueType,并且System.ValueType()is protected,因此不能被覆盖。

你能得到的最接近的可能是大卫赫弗曼的解决方案:

/// <summary>
/// The denominator is stored in this altered form, because all struct fields initialize to 0 - and we want newly created RationalNumbers to be 0/1 more often than 0/0.
/// </summary>
private int _denominatorMinusOne;
public int Denominator 
{ 
    get { return _denominatorMinusOne + 1; } 
    set { _denominatorMinusOne = value -1; } 
}

然后你可以Denominator在你的代码中正常引用,特殊的存储格式将是透明的——你只能通过查看字段声明或检查默认构造函数行为来判断。

你可以做一些事情,比如用参数调用构造函数,或者创建一个RationalNumberFactory类来为你生成零 - 但这些都不能解决你循环遍历矩阵的每个元素而不仅仅是对角线的问题,因为你不能指定构造函数数组初始值设定项将使用。

实际上,new RationalNumber[100][100]约定不仅仅是一种编码简写,它的运行速度也比调用构造函数 10000 次还要快。这就是为什么System.ValueType()首先制作protected的部分原因。请参阅:为什么我不能在 .NET 中为结构定义默认构造函数?

循环遍历矩阵的每个元素提供了清晰的优势,但使用“怪异”减一解决方案不仅减少了您必须运行的代码量,而且还提高了性能。因此,您可以将此作为对其有利的有力论据。

于 2014-02-25T19:32:43.260 回答
0

在可能的情况下,最好设计结构,以便字段值的任何组合都具有定义的语义。如果不这样做,结构通常将无法防止通过不正确线程化的代码构造格式错误的实例,并且此类实例无法在正确线程化的代码中导致不正确的行为。例如,如果一个有理类型存储位置的分子和分母值被认为是肯定互质的,并且该位置在一个线程中被复制,而它的值在另一个线程中被更改,则执行复制的线程可以接收一个实例分子和分母不是互质的,但旗帜说它们是互质的。由于不变量的破坏,接收该实例的其他代码可能会以奇怪而奇怪的方式失败;

这种情况可以通过使用不可变类对象来保存有理数,并具有包含对此类对象的私有引用的有理数值类型来补救。当它的私有引用为空时,包装器类型将使用默认实例,如果不是,则使用包装器实例。如果私有引用是抽象类型并且有几个满足不同标准的派生类型,这种方法可以提供一些潜在的效率改进。例如,可以有一个派生RationalSmallInteger,其唯一字段是 an Int32,而 aRationalLongInteger其唯一字段是 an Int64Denominator这两种类型的属性总是返回 1)。一个可能有分母为非零但被验证为与分子互质的类型或不是的类型;后一种类型可以持有一个对分子和分母保证互质的实例的初始空引用。在以下情况下,这种行为可以提高效率:

RationalNumber r1 = new RationalNumber(4,6);
RationalNumber r2 = r1;
RationalNumber r3 = r1.ReducedForm();
RationalNumber r4 = r2.ReducedForm();

第一条语句将设置 r1 的私有字段以引用一个RationalNumber.Int32by32Nonreduced实例。第二个会将 r2 的私有字段设置为指向同一个实例。第三条语句将生成一个新Int32by32Reduced实例并存储对前一个Int32by32Nonreduced实例中的引用以及 r3 的私有字段中的引用。第四个将从前者获取上述引用Int32by32Reduced并将其存储到 r4 的私有字段中。请注意,只需要一次归约操作。相比之下,如果RationalNumber是一个在内部保存其值的结构,则第四条语句将无法重用第三条执行的归约结果。

于 2013-05-24T16:11:25.783 回答
0

为结构提供默认构造函数会很好:

public RationalNumber()
{
    this.numerator = 0;
    this.denominator = 1;
    this.isDefinitelyCoprime = false;
}

但是,这是不允许的。你也不能有实例初始化器。

答案很简单,您必须接受内部字段必须初始化为零,但这并不意味着必须遵循该行为。

    public struct Rational
    {
        private int _numerator;
        private int _denominator;
        public Rational(int numerator, int denominator)
        {
            // Check denominator is positive.
            if(denominator < 0){
                  denominator *= -1; 
                  numerator *= -1;
            }
            _numerator = numerator;
            _denominator = denominator== 0? -1:
                denominator;
        }
        public int Numerator
        {
            get { return _numerator; }
        }
        public int Denominator
        {
            get { return 
                _denominator == 0?1:
                _denominator == -1?0:
                _denominator; }
        }
    }

(注意:实际上我很惊讶地发现结构中不能有静态初始化器!)

于 2013-05-03T12:50:48.310 回答