【问题标题】:Can I create a new operator in C++ and how?我可以在 C++ 中创建一个新的运算符吗?如何创建?
【发布时间】:2012-01-15 12:58:21
【问题描述】:

MATLAB 数组支持矩阵运算和元素运算。例如,M*NM.*N。这是区分两种不同操作的一种非常直观的方法。如果我想在 C++ 中实现类似的操作,我该怎么做?

我也可以创建一个新的运算符.* 吗?如果是的话,谁能给我一些指导?

【问题讨论】:

  • 您的意思是要超载*?或者您想创建一个具有不同符号的运算符?
  • 我想创建一个新的运算符。*(点乘)
  • 看看这个精美的库:github.com/klmr/named-operator,让您可以轻松地制作您的自己的命名运算符。
  • @noɥʇʎԀʎzɐɹƆ 我很好奇,为什么 dalle 链接到的优秀页面如此令人困惑,以至于您觉得有必要提供赏金来寻求新的东西(原始问题没有问)?
  • @noɥʇʎԀʎzɐɹƆ:IMO,你应该问一个新问题。这不是 OP 所要求的。

标签: c++


【解决方案1】:

不,很遗憾,您不能定义新的运算符——您只能重载现有的运算符(有一些重要的例外,例如operator.)。即使这样,对于给定运算符具有非常清晰且无争议的现有语义的类型,重载运算符通常也是一个好主意——例如,任何表现为数字的类型都是重载算术和比较运算符的良好候选者,但是你应该确保operator+ 不会,比如说,减去两个数字。

【讨论】:

  • 那么matlab是如何实现的呢?谢谢!
  • @shangping matlab 不使用 C++ 作为其接口。 matlab 定义了自己的语言。
  • matlab 本身就是一种不同的语言。但它必须使用其他一些语言来实现其基本功能,例如新的运算符。也许我错了?谢谢
  • Matlab 与 C++ 无关。
  • @shangping 你说得对,matlab 是用另一种编程语言实现的,它甚至可能是 C++,也可能是汇编语言。但是当你在matlab中定义一个新的算子时,它不必直接对应实现语言中的一个算子;例如,它可以编写一个它知道在调用该运算符时调用的 C++ 函数,或者它可以编写一个程序集块,使处理器在调用该运算符时跳转到该程序块。这都是实现细节。您可以编写带有两个参数的 C++ 函数并调用它而不是运算符。
【解决方案2】:

这就像定义一个名为(在本例中)operator*() 的函数一样简单(也同样困难!):

Matrix operator*(const Matrix &m1, const Matrix &m2) ...

其中Matrix 是您定义的用于表示矩阵的类。

【讨论】:

  • 这不是我想要的。谢谢
【解决方案3】:

不,你不能超载op.*

[C++03 & C++11: 13.5/3]:以下运算符不能重载:

. .* :: ?:

【讨论】:

  • 别忘了包含sizeof()typeid()
  • @FrancisCugler:没有人忘记他们;该约束(可能)在标准的其他地方列出。我只是引用了指出op.* 不能重载的单个段落,因为这就是 OP 所要求的。
  • 我知道;我是异想天开!
【解决方案4】:

您不能重载.*(参见Lightness' answer 以了解标准文本),但是,有趣的是,您可以重载->*(类似于重载-> 但不能重载@ 987654325@)。如果这足以区分,那就去吧:

struct Int {
    int i;

    Int operator*(Int rhs) const { return Int{i * rhs.i}; }
    Int operator->*(Int rhs) const { return Int{i + rhs.i}; }

    friend std::ostream& operator<<(std::ostream& os, Int rhs) {
        return os << "Int(" << rhs.i << ')';
    }
};

int main() {
    Int five{5};
    Int six{6};

    std::cout << (five * six) << ", " << (five ->* six) << '\n';
}

这将打印Int(30), Int(11)

【讨论】:

  • 赏金不是专门针对.*,但无论如何都是好的答案。
  • @noɥʇʎԀʎzɐɹƆ 赏金没有意义。这是在回答 OP 的问题。
【解决方案5】:

MATLAB 数组支持矩阵运算和元素运算。例如,M*N 和 M.*N。这是区分这两种不同操作的一种非常直观的方法。如果我想在 C++ 中实现类似的操作,我该怎么做?

我也可以创建一个新的运算符 .* 吗?如果是的话,谁能给我一些指导?

至于第一部分,您可以重载大多数运算符,有些则不能重载,C++ 中的运算符列表如下:

  • 算术

    • + (addition)
    • - (subtraction)
    • * (multiplication)
    • / (division)
    • % (modulus)
  • 按位

    • ^ (XOR)
    • | (OR)
    • &amp; (AND)
    • ~ (Complement)
    • &lt;&lt; (Shift Left, Insertion to Stream)
    • &gt;&gt; (Shift Right, Extraction from Stream)
  • 作业

    • = (Assignment)
  • 关系

    • == (Equality)
    • != (Inequality)
    • &gt; (Greater-Than)
    • &lt; (Less-Than)
    • &gt;= (Greater-Than Or Equal-To)
    • &lt;= (Less-Than Or Equal-To)
  • 逻辑

    • ! (NOT)
    • &amp;&amp; (AND)
    • || (OR)
  • 复合赋值

    • += (Addition-Assignment)
    • -= (Subtraction-Assignment)
    • *= (Multiplication-Assignment)
    • /= (Division-Assignment)
    • %= (Modulus-Assignment)
    • &amp;= (AND-Assignment)
    • |= (OR-Assignment)
    • ^= (XOR-Assignment)
    • &lt;&lt;= (Shift-Left Assignment)
    • &gt;&gt;= (Shift-Right Assignment)
  • 增量 - 减量 - 都有两种形式(前缀)和(后缀)

    • ++ (Increment)
    • -- (Decrement)
  • 下标

    • [] (Subscript)
  • 函数调用

    • () (Function Call)
  • 地址、引用、指针

    • operator&amp;()
    • operator*()
    • operator-&gt;()
  • 逗号

    • operator,()
  • 会员参考

    • operator-&gt;()
    • operator-&gt;*()
  • 内存管理

    • new
    • delete
    • new[]
    • delete[]
  • 转化

    • operator "type" () const
  • NON Modifiable Operators - 不能重载的运算符

    • ?: (Conditional - Ternary)
    • . (Member Selection)
    • .* (Member Selection With Pointer To Member)
    • :: (Scope Resolution)
    • sizeof() (Object Size Information)
    • typeid() (Object Type Information)

因此,了解此列表将有助于回答您的问题。你能在 C++ 中创建一个“新运算符”吗?不!如果你想在 C++ 中实现类似的操作;我该怎么做?

您有 4 种选择:重载一个已经存在且可以重载的运算符,编写一个函数或方法来执行您想要执行的计算类型,创建一个模板类型来为您完成工作,或者最后一个这是最不常见的做法,但您也可以编写宏来为您执行这些操作。

有一个只有标头的数学 API 库,它经常与 OpenGL 图形 API 和 OpenGL 的着色器语言 GLSL 一起使用,并且该库具有许多可用于矢量、矩阵、四元数等的功能,以及所有必要的函数和操作可以对他们做。这是指向GLM 的链接,您可以查看他们的文档以及他们的库实现,因为它是一个只有标头的库或 API。这应该让您了解他们如何构建 Vector 和 Matrix 对象以及可以对它们执行的操作。

【讨论】:

  • @noɥʇʎԀʎzɐɹƆ:不,不是。这些技术(这个答案描述了它们!)只是模拟创建一个新的操作符。
  • @LightnessRacesinOrbit 所以,仿真它是
【解决方案6】:

在 C++ 中,有一系列预定义的运算符,其中大部分是可重载的(.* 不是)。此外,any 名称可以用作运算符,例如:

#include <iostream>

// generic LHSlt holder
template<typename LHS, typename OP>
struct LHSlt {
    LHS lhs_;
};

// declare myop as an operator-like construct
enum { myop };

// parse 'lhs <myop' into LHSlt
template<typename LHS>
LHSlt<LHS, decltype(myop)> operator<(const LHS& lhs, decltype(myop))
{
    return { lhs };
}

// declare (int <myop> int) -> int
int operator>(LHSlt<int, decltype(myop)> lhsof, int rhs)
{
    int& lhs = lhsof.lhs_;
    // here comes your actual implementation
    return (lhs + rhs) * (lhs - rhs);
}

// strictly optional
#define MYOP <myop>

int main() {
    std::cout << (5 <myop> 2) << ' ' << (5 MYOP 2);
}

免责声明:严格来说,这被翻译为(5 &lt; myop) &gt; 2,即LHSlt&lt;int, decltype(myop)&gt;(5) &gt; 2。因此,在 C++ 术语中,它不是一个新的“运算符”,但它的使用方式完全相同,即使在 ADL 方面也是如此。此外,如果类型很大,您可能希望存储const T&amp;

请注意,您可以使用任何可以在类外部定义的二元运算符来执行此操作;优先级基于两侧的优先级(&lt;&gt;)。因此,您可以拥有例如*myop*+myop+&lt;&lt;myop&gt;&gt;&lt;myop&gt;|myop| 按优先顺序排列。

如果你想要右关联,它会变得有点棘手。您将需要 RHS 持有者和 LHS 持有者(后者在此处为 LHSlt)并使用周围的运算符,以使右侧的优先级高于左侧的优先级,例如a |myop&gt; b |myop&gt;ca |myop&gt; (b |myop&gt; c)。然后你需要你的类型和你的持有人类型的函数作为 lhs。

【讨论】:

  • 这可能是最接近赏金所寻找的。很遗憾有人悬赏寻找与原始问题不同的东西。
【解决方案7】:

正如其他答案所说,重载operator.* 是不可能的。

但我为您的问题找到了一个很好的解决方案,请查看here

您可以提供 operator-ish 形式的任何方法,例如:

M <matrix_mul> N

【讨论】:

  • 赏金给那些可以一般制作命名运算符的人。 (这是一个提醒)
【解决方案8】:

顺便说一句:我正在寻求回答这个问题的部分内容。我也不寻求在其他有价值的答案中复制所有信息。赏金寻求与所问问题不同的东西,所以我没有回应。

提供矩阵乘法实际上相当简单。由于我不打算描述数据结构来表示矩阵并完全实现对它们的操作和有效性检查,因此我将仅提供框架来说明。

示例 1:operator*() 作为成员函数

class M   // a basic matrix class
{
    public:

          // assume other constructors and members to set things up
       M operator*(const M &rhs) const;
};

M M::operator*(const M &rhs) const
{
       //   implement checks on dimensions, throw an exception if invalid

       M result;
        //  implement the multiplication (typical iterations) and store results in result
       return result;
}

int main()
{
     M a;
     M b;
        // set up elements of a and b as needed
     M c = a*b;    // this relies on M having appropriate constructor(s) to copy or move the result of a*b into c

     M d;
     d = a * b;    //  this relies on M having appropriate operator=() to assign d to the result of a*b
}

上面实现了operator*()作为成员函数。所以,在功能上,c = a*b 等价于c = a.operator*(b)const 限定符表示矩阵乘法 a*b 通常不会更改 ab

示例 2:operator*() 作为非成员函数

现在,operator*() 也可以实现为非成员(可选friend),其骨架看起来像

class M   // our basic matrix class, different operator *
{
    public:

          // assume other constructors and members to set things up
       friend M operator*(const M &lhs, const M &rhs);
};

M operator*(const M &lhs, const M &rhs)
{
       //   implement checks on dimensions, throw an exception if invalid

       M result;
        //  implement the multiplication (typical iterations) and store results in result
       return result;
}

//   same main() as before

请注意,在这种情况下,a*b 现在等同于 operator*(a, b)

如果您想同时使用这两种形式,则需要注意避免歧义。如果提供了operator*() 的两种形式,则它们在c = a*b 之类的语句中都是有效匹配项,并且编译器无法选择一种形式而不是另一种形式。结果是代码无法编译。

示例 3:重载operator*()

还可以重载operator*() - 例如,将矩阵乘以标量。

class M   // a basic matrix class
{
    public:

          // assume other constructors and members to set things up
       M operator*(const M &rhs) const;    // as in first example

       M operator*(double scalar) const;    // member form
       friend M operator*(double scalar, const M &rhs);   // non-member form
};

M M::operator*(double scalar) const
{
       M result;
        //  implement the multiplication (typical iterations) and store results in result
       return result;
}

M operator*(double scalar, const M &m)
{
       M result;
        //  implement the multiplication (typical iterations) and store results in result
       return result;
}

int main()
{
     M a;
     M b;
        // set up elements of a and b as needed
     M c = b * 2.0;    // uses the member form of operator*() above

     M d;
     d = 2.0*a;        //  uses the non-member form of operator*() above
}

以上b*2.0相当于b.operator*(2.0)的调用和2.0*a非会员operator*(2.0, a)的调用。成员形式通常只能用于左侧操作数为M 类型的表达式。因此,如果只提供operator*() 的成员形式,2.0*a 将不起作用。

讨论

除了对上述歧义的担忧之外,在重载运算符时还需要注意其他事项。

  • 无法根据语言规则中的规范更改运算符的优先级或关联性。因此,在表达式a+b*c 中,* 的优先级总是高于+。这也是在 C++ 中重载 ^ 以进行幂运算不是一个好主意的原因,因为 ^ 在 C++ 中的优先级低于 +(作为整数类型的按位运算)。因此,a + b^c 在 C++ 中实际上等同于 (a + b)^c,而不是 a + (b^c)(任何具有代数基础知识的人都会想到)。
  • 该语言指定了一组运算符,并且无法创建新的运算符。例如,C++ 中没有**,因此a ** ba 提升到b 的幂(其他语言可以做到),并且不可能创建一个。
  • 并非所有运算符都可以重载。

在 C++ 中不能重载的运算符之一是 .*。所以不可能像在 Matlab 中那样使用这样的运算符。我通常建议不要尝试使用其他运算符获得相同的效果,因为上述约束会影响这一点(并导致表达式给出违反直觉的行为)。相反,只需提供另一个命名函数来完成这项工作。比如作为成员函数

   class M
   {
       public:
         // other stuff

          M ElementWiseProduct(const M &) const;
   };

【讨论】:

    【解决方案9】:

    大多数答案已经涵盖了哪些运算符是可重载的和不可重载的,但没有人讨论为什么有些是可变的而有些不是。

    以下是我在this stackoverflow answer 中找到的 Bjarne Stroustrup(编写 c++ 的人)的一句话。特别注意第三段。

    当我决定允许 operator-> 重载时,我自然会考虑是否 operator .可以类似地重载。

    当时,我认为以下论点是决定性的:如果 obj 是一个类对象,那么 obj.m 对该对象类的每个成员 m 都有意义。我们尽量不通过重新定义内置操作来使语言可变(尽管出于迫切需要的 = 和一元 & 违反了该规则)。

    如果我们允许重载 .对于 X 类,我们将无法通过正常方式访问 X 的成员;我们必须使用指针和 ->,但 -> 和 & 也可能被重新定义。我想要一种可扩展的语言,而不是可变的。

    这些论点很有分量,但不是结论性的。特别是在 1990 年,Jim Adcock 提议允许 operator 重载。正是 operator -> 的方式。

    A page on his website 补充一点:

    我可以定义自己的运算符吗?

    对不起,没有。这种可能性已被多次考虑,但每次我/我们都认为可能出现的问题超过了可能带来的好处。

    这不是语言技术问题。甚至当我在 1983 年第一次考虑它时,我就知道如何实现它。然而,我的经验是,当我们超越最琐碎的例子时,人们似乎对运算符使用的“明显”含义有微妙的不同看法。一个经典的例子是 a ** b ** c。假设 ** 表示取幂。现在 a ** b ** c 应该表示 (a ** b) ** c 还是 a ** (b ** c)?我认为答案是显而易见的,我的朋友们也同意了——然后我们发现我们没有就哪个决议是显而易见的达成一致。我的猜测是这样的问题会导致细微的错误。

    因此,尽管大多数运算符都可以重载,但人们从未打算在 c++ 中创建任意运算符。

    【讨论】:

      猜你喜欢
      • 2015-06-20
      • 2011-01-13
      • 2010-11-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-23
      相关资源
      最近更新 更多