重载模板 Polynom 类的运算符[]

发布于 2024-12-14 13:38:40 字数 1394 浏览 5 评论 0原文

我正在编写一个模板 Polynom 类,其中 T 是其系数的数字类型。

多项式的系数存储在 std::vector中。系数,其中coefficients[i] 对应于实数多项式中的x^i。 (因此 x 的幂按递增顺序排列)。

保证coefficients向量始终包含至少一个元素。 - 对于零多项式,它是T()

我想重载operator[]来执行以下操作:

  1. 传递给operator[]的索引对应于我们要修改/读取其系数的X的幂。
  2. 如果用户只想读取系数,则应该抛出负索引,对于存储范围内的索引返回coefficients.at(i) - 并且合理 对于所有其他索引返回 0,而不是抛出。
  3. 如果用户想要修改系数,则应该抛出负索引,但让用户自由修改所有其他索引,即使指定的索引大于或等于coefficients.size( )。所以我们想以某种方式调整向量的大小。

我遇到的主要问题如下:

1.

如何区分读情况和写情况?一个人没有给我任何解释,但说写两个版本

const T& operator[] (int index) const;
T& operator[] (int index);

是不够的。但是,我认为编译器在读取情况下更喜欢 const 版本,不是吗?

2.

我想确保系数向量中不存储任何尾随零。因此,我必须以某种方式提前知道,“在我返回系数的可变 T& 之前”,用户想要分配什么值。我知道 operator[] 不会收到第二个参数。

显然,如果这个值不为零(不是 T()),那么我必须调整向量的大小并将适当的系数设置为传递的值。

但我不能提前这样做(在从 operator[] 返回 T& 之前),因为如果要分配的值是 T(),那么,只要我调整大小我提前输入系数向量,它最终会有很多尾随“零”。

当然,我可以检查该类的每个其他函数中是否有尾随零,并在这种情况下将其删除。对我来说这似乎是一个非常奇怪的决定,我希望每个函数都在假设向量的末尾没有零的情况下开始工作,如果它的大小> 1.

您能否建议我尽可能具体地解决这个问题? 我听说过一些关于使用重载的 operator= 编写可隐式转换为 T& 的内部类,但我缺乏详细信息。

预先非常感谢您!

I am writing a template Polynom<T> class where T is the numeric type of its coefficients.

The coefficients of the polynom are stored in an std::vector<T> coefficients, where coefficients[i] corresponds to x^i in a real polynom. (so the powers of x are in increasing order).

It is guaranteed that coefficients vector always contains at least one element. - for a zero polynom it is T().

I want to overload the operator[] to do the following:

  1. The index passed to the operator[] corresponds to the power of X whose coefficient we want to modify / read.
  2. If the user wants to just read the coefficient, it should throw for negative indices, return coefficients.at(i) for indices within the stored range - and reasonably return 0 for all other indices, not throw.
  3. If the user wants to modify the coefficient, it should throw for negative indices, but let user modify all other indices freely, even if the index specified is bigger than or equal to coefficients.size(). So we want to somehow resize the vector.

The main problem I have collided with is as follows:

1.

How do I distinguish between the read case and the write case? One person left me without an explanation but said that writing two versions:

const T& operator[] (int index) const;
T& operator[] (int index);

was insufficient. However, I thought that the compiler would prefer the const version in the read case, won't it?

2.

I want to make sure that no trailing zeros are ever stored in the coefficients vector. So I somehow have to know in advance, "before" I return a mutable T& of my coefficient, what value user wants to assign. And I know that operator[] doesn't receive a second argument.

Obviously, if this value is not zero (not T()), then I have to resize my vector and set the appropriate coefficient to the value passed.

But I cannot do it in advance (before returning a T& from operator[]), because if the value to be assigned is T(), then, provided I resize my coefficients vector in advance, it will eventually have lots of trailing "zeroes".

Of course I can check for trailing zeroes in every other function of the class and remove them in that case. Seems a very weird decision to me, and I want every function to start working in assumption that there are no zeroes at the end of the vector if its size > 1.

Could you please advise me as concrete solution as possible to this problem?
I heard something about writing an inner class implicitly convertible to T& with overloaded operator=, but I lack the details.

Thank you very much in advance!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

晌融 2024-12-21 13:38:40

您可以尝试的一个选项(我尚未测试过):

template<typename T>
class MyRef{
private:
   int index;
   Polynom<T>*p;
public:
    MyRef(int index, Polynom<T>*p) : index(index), p(p) { }

    MyRef<T>& operator=(T const&t); //and define these appropriately
    T operator T() const;         
};

并定义:

    MyRef<T> operator[](int index){
        return MyRef<T>(index, this);
    }

这样,当您为“引用”分配一个值时,它应该可以访问多项式中的所有所需数据,并采取适当的操作。

我对您的实现不够熟悉,因此我将给出一个非常简单的动态数组的示例,其工作原理如下:

  • 您可以毫无顾虑地从任何int索引读取;先前未写入的元素应读取为 0
  • 当您写入超出当前分配数组末尾的元素时,该元素将被重新分配,并且新分配的元素将初始化为 0
#include <cstdlib>
#include <iostream>
using namespace std;

template<typename T>
class my_array{
private:
    T* _data;
    int _size;

    class my_ref{

        private:
            int index;
            T*& obj;
            int&size;
        public:
            my_ref(T*& obj, int&size, int index)
                : index(index), obj(obj), size(size){}

            my_ref& operator=(T const& t){

                if (index>=size){    
                    obj = (T*)realloc(obj, sizeof(T)*(index+1) );
                    while (size<=index)
                        obj[size++]=0;
                }
                obj[index] = t;

                return *this;
            }

            //edit:this one should allow writing, say, v[1]=v[2]=v[3]=4;
            my_ref& operator=(const my_ref&r){              
                operator=( (T) r);
                return *this;
            }

            operator T() const{
                return (index>=size)?0:obj[index];
            }

    };

public:
    my_array() : _data(NULL), _size(0) {}

    my_ref operator[](int index){
        return my_ref(_data,_size,index);
    }

    int size() const{ return _size; }

};

int main(){

    my_array<int> v;

    v[0] = 42;
    v[1] = 51;
    v[5] = 5; v[5]=6;
    v[30] = 18;

    v[2] = v[1]+v[5];
    v[4] = v[8]+v[1048576]+v[5]+1000;

    cout << "allocated elements: " <<  v.size() << endl;
    for (int i=0;i<31;i++)
        cout << v[i] << " " << endl;

    return 0;
}

这是一个非常简单的示例,目前的形式效率不高,但它应该证明这一点。

最终,您可能希望重载 operator& 以允许 *(&v[0] + 5) = 42; 等内容正常工作。对于此示例,您可以让 operator& 给出一个 my_pointer,它定义 operator+ 对其 index 进行算术运算字段并返回一个新的 my_pointer。最后,您可以重载 operator*() 以返回到 my_ref

One option you could try (I haven't tested this):

template<typename T>
class MyRef{
private:
   int index;
   Polynom<T>*p;
public:
    MyRef(int index, Polynom<T>*p) : index(index), p(p) { }

    MyRef<T>& operator=(T const&t); //and define these appropriately
    T operator T() const;         
};

and define:

    MyRef<T> operator[](int index){
        return MyRef<T>(index, this);
    }

This way when you assign a value to the "reference" it should have access to all the needed data in the polynomial, and take the appropriate actions.

I am not familiar enough with your implementation, so I'll instead give an example of a very simple dynamic array that works as follows:

  • you can read from any int index without concern; elements not previously written to should read off as 0;
  • when you write to an element past the end of the currently allocated array, it is reallocated, and the newly allocated elements are initialized to 0.
#include <cstdlib>
#include <iostream>
using namespace std;

template<typename T>
class my_array{
private:
    T* _data;
    int _size;

    class my_ref{

        private:
            int index;
            T*& obj;
            int&size;
        public:
            my_ref(T*& obj, int&size, int index)
                : index(index), obj(obj), size(size){}

            my_ref& operator=(T const& t){

                if (index>=size){    
                    obj = (T*)realloc(obj, sizeof(T)*(index+1) );
                    while (size<=index)
                        obj[size++]=0;
                }
                obj[index] = t;

                return *this;
            }

            //edit:this one should allow writing, say, v[1]=v[2]=v[3]=4;
            my_ref& operator=(const my_ref&r){              
                operator=( (T) r);
                return *this;
            }

            operator T() const{
                return (index>=size)?0:obj[index];
            }

    };

public:
    my_array() : _data(NULL), _size(0) {}

    my_ref operator[](int index){
        return my_ref(_data,_size,index);
    }

    int size() const{ return _size; }

};

int main(){

    my_array<int> v;

    v[0] = 42;
    v[1] = 51;
    v[5] = 5; v[5]=6;
    v[30] = 18;

    v[2] = v[1]+v[5];
    v[4] = v[8]+v[1048576]+v[5]+1000;

    cout << "allocated elements: " <<  v.size() << endl;
    for (int i=0;i<31;i++)
        cout << v[i] << " " << endl;

    return 0;
}

It's a very simple example and not very efficient in its current form but it should prove the point.

Eventually you might want to overload operator& to allow things like *(&v[0] + 5) = 42; to work properly. For this example, you could have that operator& gives a my_pointer which defines operator+ to do arithmetic on its index field and return a new my_pointer. Finally, you can overload operator*() to go back to a my_ref.

意中人 2024-12-21 13:38:40

解决这个问题的办法是代理类(下面是未经测试的代码):

template<typename T> class Polynom
{
public:
   class IndexProxy;
   friend class IndexProxy;
   IndexProxy operator[](int);
   T operator[](int) const;
   // ...
private:
   std::vector<T> coefficients;
};

template<typename T> class Polynom<T>::IndexProxy
{
public:
  friend class Polynom<T>;
  // contrary to convention this assignment does not return an lvalue,
  // in order to be able to avoid extending the vector on assignment of 0.0
  T operator=(T const& t)
  {
    if (theIndex >= thePolynom.coefficients.size())
      thePolynom.coefficients.resize(theIndex+1);
    thePolynom.coefficients[theIndex] = t;
    // the assignment might have made the polynom shorter
    // by assigning 0 to the top-most coefficient
    while (thePolynom.coefficients.back() == T())
      thePolynom.coefficients.pop_back();
    return t;
  }
  operator T() const
  {
    if (theIndex >= thePolynom.coefficients.size())
      return 0;
    return thePolynom.coefficients[theIndex];
  }
private:
  IndexProxy(Polynom<T>& p, int i): thePolynom(p), theIndex(i) {}
  Polynom<T>& thePolynom;
  int theIndex;
}

template<typename T>
  Polynom<T>::IndexProxy operator[](int i)
  {
    if (i < 0) throw whatever;
    return IndexProxy(*this, i);
  }

template<typename T>
  T operator[](int i)
{
  if (i<0) throw whatever;
  if (i >= coefficients.size()) return T();
  return coefficients[i];
}

显然上面的代码没有优化(尤其是赋值运算符显然有优化的空间)。

The solution to this is a proxy class (untested code follows):

template<typename T> class Polynom
{
public:
   class IndexProxy;
   friend class IndexProxy;
   IndexProxy operator[](int);
   T operator[](int) const;
   // ...
private:
   std::vector<T> coefficients;
};

template<typename T> class Polynom<T>::IndexProxy
{
public:
  friend class Polynom<T>;
  // contrary to convention this assignment does not return an lvalue,
  // in order to be able to avoid extending the vector on assignment of 0.0
  T operator=(T const& t)
  {
    if (theIndex >= thePolynom.coefficients.size())
      thePolynom.coefficients.resize(theIndex+1);
    thePolynom.coefficients[theIndex] = t;
    // the assignment might have made the polynom shorter
    // by assigning 0 to the top-most coefficient
    while (thePolynom.coefficients.back() == T())
      thePolynom.coefficients.pop_back();
    return t;
  }
  operator T() const
  {
    if (theIndex >= thePolynom.coefficients.size())
      return 0;
    return thePolynom.coefficients[theIndex];
  }
private:
  IndexProxy(Polynom<T>& p, int i): thePolynom(p), theIndex(i) {}
  Polynom<T>& thePolynom;
  int theIndex;
}

template<typename T>
  Polynom<T>::IndexProxy operator[](int i)
  {
    if (i < 0) throw whatever;
    return IndexProxy(*this, i);
  }

template<typename T>
  T operator[](int i)
{
  if (i<0) throw whatever;
  if (i >= coefficients.size()) return T();
  return coefficients[i];
}

Obviously the code above is not optimized (especially the assignment operator has clearly room for optimization).

薯片软お妹 2024-12-21 13:38:40

您无法通过运算符重载来区分读取和写入。您能做的最好的事情就是区分 const 设置和非 const 设置中的使用,这就是您的代码片段的作用。所以:

Polynomial &poly = ...;

poly[i] = 10;  // Calls non-const version
int x = poly[i];  // Calls non-const version

const Polynomial &poly = ...;

poly[i] = 10;   // Compiler error!
int x = poly[i]  // Calls const version

这听起来像是你两个问题的答案,因此,是有单独的 setget 函数。

You cannot distinguish between read and write with operator overloads. The best you can do is distinguish between usage in a const setting and a non-const setting, which is what your code snippet does. So:

Polynomial &poly = ...;

poly[i] = 10;  // Calls non-const version
int x = poly[i];  // Calls non-const version

const Polynomial &poly = ...;

poly[i] = 10;   // Compiler error!
int x = poly[i]  // Calls const version

It sounds like the answer to both your questions, therefore, is to have separate set and get functions.

浅浅淡淡 2024-12-21 13:38:40

我看到您的问题有两种解决方案:

  1. 不要将系数存储在 std::vector 中,而是将它们存储在 std::map< /代码>。这样您将只存储非零系数。您可以创建自己的基于 std::map 的容器,该容器将消耗存储在其中的零。这样,您还可以为具有较大 n 的 x^n 形式的多项式节省一些存储空间。

  2. 添加一个内部类来存储索引(幂)和系数值。您将从 operator[] 返回对此内部类实例的引用。内部类将覆盖operator=。在重写的 operator= 中,您将获取存储在内部类实例中的索引(幂)和系数,并将它们刷新到存储系数的 std::vector 中。< /p>

I see two solutions to your problem:

  1. Instead of storing the coefficients in a std::vector<T> store them in a std::map<unsigned int, T>. This way you will ever only store non-zero coefficients. You could create your own std::map-based container that would consume zeros stored into it. This way you also save some storage for polynomials of the form x^n with large n.

  2. Add an inner class that will store an index (power) and coefficient value. You would return a reference to an instance of this inner class from operator[]. The inner class would overwrite operator=. In the overridden operator= you would take the index (power) and coefficient stored in inner class instance and flush them to the std::vector where you store your coefficients.

秋叶绚丽 2024-12-21 13:38:40

这是不可能的。我能想到的唯一方法是提供一个特殊的成员函数来添加新系数。

编译器通过查看 Polynom 的类型来决定 const 和非 const 版本,而不是通过检查执行的操作类型关于返回值。

This is not possible. The only way I can think of is to provide a special member-function for adding new coefficients.

The compiler decides between the const and non-const version by looking at the type of Polynom, and not by checking what kind of operation is performed on the return-value.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文