注:答案是按照特定的顺序给出的,但由于许多用户根据投票而不是给出答案的时间对答案进行排序,以下是答案的索引,以最有意义的顺序排列:

C中运算符重载的一般语法++C语言中运算符重载的三个基本规则++成员与非成员之间的决定常见操作员过载赋值运算符输入和输出运算符函数调用运算符比较运算符算术运算符阵列订阅指针类类型的运算符转换运算符重载新建和删除

(注意:这是Stack Overflow的C++FAQ的一个条目。如果你想批评以这种形式提供FAQ的想法,那么在meta上发布这一切的帖子就是这样做的地方。该问题的答案在C++聊天室中进行监控,FAQ想法最初就在那里开始,所以你的答案很可能会被提出该想法的人阅读。)


当前回答

简而言之,我将提及一些要点,这些要点是我在过去一周学习Python和C++、oops和其他东西时提出的,因此如下所示:

运算符的Arity不能被进一步修改为原来的值!重载运算符只能有一个默认参数,而函数调用运算符不能使用该参数。只有内置运算符可以重载,其余的不能!

有关更多信息,您可以参考以下链接,该链接将您重定向到GeekforGeeks提供的文档。

https://www.geeksforgeeks.org/g-fact-39/

其他回答

简而言之,我将提及一些要点,这些要点是我在过去一周学习Python和C++、oops和其他东西时提出的,因此如下所示:

运算符的Arity不能被进一步修改为原来的值!重载运算符只能有一个默认参数,而函数调用运算符不能使用该参数。只有内置运算符可以重载,其余的不能!

有关更多信息,您可以参考以下链接,该链接将您重定向到GeekforGeeks提供的文档。

https://www.geeksforgeeks.org/g-fact-39/

成员与非成员之间的决定

二进制运算符=(赋值)、[](数组订阅)、->(成员访问)以及n元()(函数调用)运算符必须始终作为成员函数实现,因为语言的语法要求它们实现。

其他运算符可以作为成员或非成员实现。然而,其中一些函数通常必须实现为非成员函数,因为您无法修改它们的左操作数。其中最突出的是输入和输出运算符<<and>>,其左操作数是标准库中的流类,您无法更改。

对于必须选择将其作为成员函数或非成员函数实现的所有运算符,请使用以下经验规则来决定:

如果它是一元运算符,请将其实现为成员函数。如果二进制运算符对两个操作数一视同仁(保持不变),则将此运算符作为非成员函数实现。如果一个二元运算符不能平等对待两个操作数(通常它会改变其左操作数),如果它必须访问操作数的私有部分,则将其设置为左操作数类型的成员函数可能会很有用。

当然,与所有经验法则一样,也有例外。如果你有一个类型

enum Month {Jan, Feb, ..., Nov, Dec}

如果要重载它的递增和递减运算符,则不能将其作为成员函数,因为在C++中,枚举类型不能具有成员函数。所以你必须把它作为一个自由函数重载。嵌套在类模板中的类模板的运算符<()在作为类定义中内联的成员函数时更容易编写和读取。但这些确实是罕见的例外。

(但是,如果发生异常,请不要忘记操作数的常量问题,对于成员函数,该操作数将成为隐式this参数。如果作为非成员函数的运算符将其最左边的参数作为常量引用,则作为成员函数的同一运算符需要在末尾有一个常量,以使*this成为常量引用。)


继续到“常用运算符”以重载。

重载新建和删除运算符

注意:这只处理重载new和delete的语法,而不处理此类重载运算符的实现。我认为重载new和delete的语义应该有自己的常见问题解答,在运算符重载的主题中,我永远无法做到这一点。

基础

在C++中,当您编写像new T(arg)这样的新表达式时,在计算该表达式时会发生两件事:首先调用运算符new以获取原始内存,然后调用T的适当构造函数以将该原始内存转换为有效对象。同样,当您删除一个对象时,首先调用其析构函数,然后将内存返回给delete运算符。C++允许您调整这两个操作:内存管理和在分配的内存中构造/销毁对象。后者是通过为类编写构造函数和析构函数来实现的。微调内存管理是通过编写自己的运算符new和运算符delete来完成的。

运算符重载的第一个基本规则——不要这样做——尤其适用于重载new和delete。导致这些运算符过载的几乎唯一原因是性能问题和内存限制,在许多情况下,其他操作(如对所用算法的更改)将提供比试图调整内存管理高得多的成本/收益比。

C++标准库附带一组预定义的新建和删除运算符。最重要的是:

void* operator new(std::size_t) throw(std::bad_alloc); 
void  operator delete(void*) throw(); 
void* operator new[](std::size_t) throw(std::bad_alloc); 
void  operator delete[](void*) throw(); 

前两个为对象分配/解除分配内存,后两个为一个对象数组。如果您提供自己的版本,它们将不会过载,而是替换标准库中的版本。如果重载运算符new,则应始终重载匹配的运算符delete,即使您从未打算调用它。原因是,如果构造函数在计算新表达式时抛出,则运行时系统会将内存返回给运算符delete,该运算符delete与被调用以分配内存以在其中创建对象的运算符new相匹配。如果不提供匹配的delete运算符,则会调用默认的delete,这几乎总是错误的。如果重载new和delete,也应该考虑重载数组变量。

放置新的

C++允许new和delete运算符接受额外的参数。所谓的placement new允许您在某个地址创建一个对象,该地址将传递给:

class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 

标准库附带了相应的new和delete运算符重载:

void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 

注意,在上面给出的放置new的示例代码中,除非X的构造函数抛出异常,否则永远不会调用运算符delete。

还可以使用其他参数重载new和delete。与放置new的附加参数一样,这些参数也列在关键字new之后的括号内。仅仅出于历史原因,这种变体通常也被称为新放置,即使它们的参数不是用于将对象放置在特定地址。

类特定的新建和删除

最常见的情况是,您需要对内存管理进行微调,因为测量表明,特定类或一组相关类的实例经常被创建和销毁,而运行时系统的默认内存管理(针对一般性能进行了调整)在这种特定情况下处理效率低下。为了改进这一点,可以为特定类重载new和delete:

class my_class { 
  public: 
    // ... 
    void* operator new(std::size_t);
    void  operator delete(void*);
    void* operator new[](std::size_t);
    void  operator delete[](void*);
    // ...  
}; 

因此重载后,new和delete的行为类似于静态成员函数。对于my_class的对象,std::size_t参数将始终为sizeof(my_class)。然而,对于派生类的动态分配对象,也会调用这些运算符,在这种情况下,它可能会更大。

全局新建和删除

要重载全局new和delete,只需将标准库的预定义运算符替换为我们自己的运算符即可。然而,很少需要这样做。

为什么用于将对象流到std::cout或文件的运算符<<函数不能作为成员函数?

假设你有:

struct Foo
{
   int a;
   double b;

   std::ostream& operator<<(std::ostream& out) const
   {
      return out << a << " " << b;
   }
};

鉴于此,您不能使用:

Foo f = {10, 20.0};
std::cout << f;

由于运算符<<被重载为Foo的成员函数,因此运算符的LHS必须是Foo对象。这意味着,您需要使用:

Foo f = {10, 20.0};
f << std::cout

这是非常不直观的。

如果将其定义为非成员函数,

struct Foo
{
   int a;
   double b;
};

std::ostream& operator<<(std::ostream& out, Foo const& f)
{
   return out << f.a << " " << f.b;
}

您将能够使用:

Foo f = {10, 20.0};
std::cout << f;

这是非常直观的。

C语言中运算符重载的三个基本规则++

当谈到C++中的运算符重载时,您应该遵循三个基本规则。与所有这些规则一样,确实有例外。有时,人们偏离了这些准则,其结果并不坏,但这种积极的偏离很少。至少,我所看到的100个这种偏差中有99个是不合理的。然而,它也可能是千分之999。所以你最好遵守以下规则。

每当运算符的含义不明显且无可争议时,它就不应过载。相反,请提供一个具有精心选择的名称的函数。基本上,重载运算符的首要原则是:不要这样做。这可能看起来很奇怪,因为关于运算符重载有很多事情要知道,所以很多文章、书籍章节和其他文本都涉及到这一切。但是,尽管有这些看似明显的证据,但只有极少数情况下运算符重载是合适的。原因是,实际上很难理解运算符应用程序背后的语义,除非在应用程序域中使用运算符是众所周知且无可争议的。与人们普遍的看法相反,事实并非如此。始终遵循运算符众所周知的语义。C++对重载运算符的语义没有任何限制。编译器将乐于接受实现二进制+运算符的代码,以从其右操作数中减去。然而,这样的运算符的用户永远不会怀疑表达式a+b会从b中减去a。当然,这假设应用程序域中运算符的语义是无可争议的。始终提供一组相关操作中的所有操作。操作员相互关联,并与其他操作相关。如果您的类型支持a+b,用户也可以调用a+=b。如果它支持前缀increment++a,那么他们也希望a++也能工作。如果他们能检查a是否<b,他们肯定也会检查a是否>b。如果他们能复制构造你的类型,他们希望赋值也能起作用。


继续执行成员与非成员之间的决定。