Что такое перегруженный оператор в C ++? - PullRequest
6 голосов
/ 09 марта 2010

Я понимаю, что это основной вопрос, но я искал в Интернете, был на cplusplus.com, прочитал мою книгу, и я не могу понять понятие перегруженных операторов. Конкретный пример cplusplus.com:

// vectors: overloading operators example
#include <iostream>
using namespace std;

class CVector {
  public:
    int x,y;
    CVector () {};
    CVector (int,int);
    CVector operator + (CVector);
};

CVector::CVector (int a, int b) {
  x = a;
  y = b;
}

CVector CVector::operator+ (CVector param) {
  CVector temp;
  temp.x = x + param.x;
  temp.y = y + param.y;
  return (temp);
}

int main () {
  CVector a (3,1);
  CVector b (1,2);
  CVector c;
  c = a + b;
  cout << c.x << "," << c.y;
  return 0;
}

С http://www.cplusplus.com/doc/tutorial/classes2/ но, читая это, я все еще не понимаю их вообще. Мне просто нужен базовый пример точки перегруженного оператора (который я предполагаю, это "CVector CVector :: operator + (CVector param)").

Также есть пример из википедии:

 Time operator+(const Time& lhs, const Time& rhs)
 {
   Time temp = lhs;
   temp.seconds += rhs.seconds;
   if (temp.seconds >= 60)
   {
     temp.seconds -= 60;
     temp.minutes++;
   }
   temp.minutes += rhs.minutes;
   if (temp.minutes >= 60)
   {
     temp.minutes -= 60;
     temp.hours++;
   }
   temp.hours += rhs.hours;
   return temp;
 }

Из "http://en.wikipedia.org/wiki/Operator_overloading"

Текущее назначение, над которым я работаю, мне нужно перегрузить операторы ++ и -.

Заранее спасибо за информацию и извиняюсь за несколько расплывчатый вопрос, к сожалению, я просто не уверен в этом.

Ответы [ 11 ]

14 голосов
/ 09 марта 2010

Перегрузка операторов - это методика, предоставляемая C ++, позволяющая вам определить, как операторы в языке могут применяться к не встроенным объектам.

В вашем примере для перегрузки оператора класса Time для оператора +:

Time operator+(const Time& lhs, const Time& rhs);

С этой перегрузкой вы теперь можете выполнять операции сложения над Time объектами "естественным" способом:

Time t1 = some_time_initializer;
Time t2 = some_other_time_initializer;

Time t3 = t1 + t2;    // calls operator+( t1, t2)

Перегрузка для оператора - это просто функция со специальным именем «оператор», за которым следует символ для перегруженного оператора. Большинство операторов могут быть перегружены - те, которые не могут быть:

.  .*  :: and ?:

Вы можете вызывать функцию напрямую по имени, но обычно этого не происходит (точка перегрузки операторов заключается в возможности нормально использовать операторы).

Перегружаемая функция, которая вызывается, определяется нормальным разрешением перегрузки аргументов оператора - именно так компилятор знает, как вызвать operator+(), использующий типы аргументов Time из приведенного выше примера.

Еще одна вещь, о которой следует помнить при перегрузке операторов увеличения и уменьшения ++ и --, состоит в том, что существует две версии каждой из них - префикс и постфиксные формы. Постфиксная версия этих операторов принимает дополнительный параметр int (который передается 0 и не имеет никакой иной цели, кроме как различать два типа операторов). Стандарт C ++ имеет следующие примеры:

class X {
public:
    X&   operator++();      //prefix ++a
    X    operator++(int);   //postfix a++
};

class Y { };

Y&   operator++(Y&);        //prefix ++b
Y    operator++(Y&, int);   //postfix b++

Вы также должны знать, что перегруженные операторы не должны выполнять операции, аналогичные встроенным операторам - будучи более или менее обычными функциями, они могут выполнять все, что вы захотите. Например, интерфейс потоков ввода-вывода стандартной библиотеки использует операторы сдвига для вывода и ввода в / из потоков - что на самом деле не похоже на сдвиг битов. Однако, если вы попытаетесь слишком увлечься перегрузками вашего оператора, вы будете сильно путаться с людьми, которые пытаются следовать вашему коду (возможно, даже вам, когда вы посмотрите на свой код позже).

Осторожно используйте перегрузку оператора.

4 голосов
/ 09 марта 2010

Оператор в C ++ - это просто функция со специальным именем. Поэтому вместо того, чтобы говорить Add(int,int), вы говорите operator +(int,int).

Теперь, как и любую другую функцию, вы можете перегрузить ее, чтобы сказать, что она работает с другими типами. В вашем векторном примере, если вы перегрузите operator +, чтобы получить CVector аргументов (т.е. operator +(CVector, CVector)), вы можете сказать:

CVector a,b,res;
res=a+b;

Поскольку ++ и -- являются унарными (они принимают только один аргумент), для их перегрузки вы должны сделать следующее:

type operator ++(type p)
{
  type res;
  res.value++;

  return res;
}

Где type - любой тип, имеющий поле с именем value. Вы поняли.

2 голосов
/ 09 марта 2010

Из ваших комментариев выше, вы не видите смысла всей этой перегрузки оператора?

Перегрузка операторов - это просто «синтаксический сахар», скрывающий вызов метода и делающий код несколько более понятным во многих случаях.

Рассмотрим простой класс Integer, упаковывающий int. Вы должны написать add и другие арифметические методы, возможно, также увеличивающие и уменьшающие, требующие вызова метода, такого как my_int.add (5). теперь переименование метода add в operator + позволяет my_int + 5, который является более интуитивным и понятным, более чистым кодом. Но все, что он делает на самом деле - это скрывает вызов вашего оператора operator + (переименованный в add?).

Однако все становится немного сложнее, так как оператор + для чисел хорошо понятен всем, кто старше 2-го класса. Но, как в приведенном выше примере строки, операторы обычно должны применяться только там, где они имеют интуитивное значение. Пример Apples является хорошим примером того, где НЕ перегружать операторы. Но, к слову сказать, класс List, что-то вроде myList + anObject, следует интуитивно понимать как «добавление anObject к myList», следовательно, использование оператора + И оператор '-' как означающий 'Удаление из списка'.

Как я сказал выше, смысл всего этого состоит в том, чтобы сделать код (надеюсь) более понятным, как в примере со списком, что бы вы предпочли кодировать? (а что вам легче читать?) myList.add (anObject) или myList + onObject? Но в фоновом режиме метод (ваша реализация operator + или add) вызывается в любом случае. Вы можете даже подумать о том, как компилятор переписывает код: my_int + 5 станет my_int.operator + (5)

Все приведенные примеры, такие как классы Time и Vector, имеют интуитивно понятные определения для операторов. Добавление вектора ... опять же, проще кодировать (и читать) v1 = v2 + v3, чем v1 = v2.add (v3). Это то место, где вы, скорее всего, будете предупреждены о том, чтобы не перегружать себя операторами в ваших классах, потому что для большинства они просто не имеют смысла. Но, конечно, ничто не мешает вам поместить оператора в такой класс, как Apple, просто не ожидайте, что другие узнают, что он делает, не увидев код для него!

«Перегрузка» оператора просто означает, что вы предоставляете компилятору другое определение для этого оператора, примененное к экземплярам вашего класса. Скорее как методы перегрузки, одно имя ... разные параметры ...

Надеюсь, это поможет ...

2 голосов
/ 09 марта 2010

То, что вы нашли в этих ссылках, не является плохим примером того, что вы хотите перегружать операторы (например, придавая смысл добавлению векторов), но это ужасный код, когда дело касается деталей.1002 * Например, это гораздо более реалистично, показывая делегирование оператору сложного присваивания и правильную маркировку функции-члена const:

class Vector2
{
  double m_x, m_y;
public:
  Vector2(double x, double y) : m_x(x), m_y(y) {}
  // Vector2(const Vector2& other) = default;
  // Vector2& operator=(const Vector2& other) = default;

  Vector2& operator+=(const Vector2& addend) { m_x += addend.m_x; m_y += addend.m_y; return *this; }
  Vector2 operator+(const Vector2& addend) const { Vector2 sum(*this); return sum += addend; }
};
0 голосов
/ 09 марта 2010

Принятый ответ Майкла Берра довольно хорош в объяснении техники, но из комментариев кажется, что помимо «как» вас интересует «почему». Основными причинами перегрузки операторов для данного типа являются улучшение читабельности и обеспечение необходимого интерфейса.

Если у вас есть тип, для которого существует единственное общепринятое значение для оператора в области вашей проблемы, то обеспечение этого при перегрузке оператора делает код более читабельным:

std::complex<double> a(1,2), b(3,4), c( 5, 6 );
std::complex<double> d = a + b + c;         // compare to d = a.add(b).add(c);
std::complex<double> e = (a + d) + (b + c); // e = a.add(d).add( b.add(c) );

Если у вашего типа есть заданное свойство, которое естественно будет выражаться оператором, вы можете перегрузить этот конкретный оператор для вашего типа. Рассмотрим, например, что вы хотите сравнить ваши объекты на равенство. Предоставление operator==operator!=) может дать вам простой читаемый способ сделать это. Это имеет преимущество в выполнении общего интерфейса, который может использоваться с алгоритмами, которые зависят от равенства:

struct type {
   type( int x ) : value(x) {}
   int value;
};
bool operator==( type const & lhs, type const & rhs ) 
   { return lhs.value == rhs.value; }
bool operator!=( type const & lhs, type const & rhs ) 
   { return !lhs == rhs; }

std::vector<type> getObjects(); // creates and fills a vector
int main() {
   std::vector<type> objects = getObjects();
   type t( 5 );
   std::find( objects.begin(), objects.end(), t );
}

Обратите внимание, что при реализации алгоритма find он зависит от определяемого ==. Реализация find будет работать с примитивными типами, а также с любым пользовательским типом, для которого определен оператор равенства. Существует общий единый интерфейс, который имеет смысл . Сравните это с версией Java, где сравнение типов объектов должно выполняться с помощью функции-члена .equals, тогда как сравнение типов примитивов может быть выполнено с помощью ==. Позволяя вам перегружать операторы, вы можете работать с пользовательскими типами так же, как с примитивными типами.

То же самое касается заказа. Если в домене вашего класса есть четко определенный (частичный) порядок, то предоставление operator< является простым способом реализации этого порядка. Код будет читабелен, а ваш тип будет использоваться во всех ситуациях, когда требуется частичный порядок, например, внутри ассоциативных контейнеров:

bool operator<( type const & lhs, type const & rhs )
{
   return lhs < rhs;
}
std::map<type, int> m; // m will use the natural `operator<` order

Типичной ошибкой при введении в язык перегрузки операторов является ' золотой молот '. Если у вас есть золотой молот, все выглядит как гвоздь, а перегрузка оператора злоупотребляется.

Важно отметить, что причина перегрузки в первую очередь заключается в улучшении читабельности. Читаемость улучшается только в том случае, если, когда программист смотрит на код, цели каждой операции на первый взгляд понятны без необходимости читать определения. Когда вы видите, что добавляются два комплексных числа, например a + b, вы знаете, что делает код. Если определение оператора не является естественным (вы решаете реализовать его как добавление только реальной его части), тогда код станет сложнее для чтения, чем если бы вы предоставили (член) функцию. Если значение операции не определено для вашего типа, происходит то же самое:

MyVector a, b;
MyVector c = a + b;

Что такое c? Является ли это вектором, где каждый элемент i является суммой соответствующих элементов из a и b, или это вектор, созданный путем объединения элементов a перед элементами b. Чтобы понять код, вам нужно перейти к определению операции, а это значит, что перегрузка оператора менее читаема, чем предоставление функции:

MyVector c = append( a, b );

Набор операторов, которые могут быть перегружены, не ограничивается арифметическими и реляционными операторами. Вы можете перегрузить operator[], чтобы индексировать тип, или operator(), чтобы создать вызываемый объект, который можно использовать как функцию (это называется функторами) или который упростит использование класса:

class vector {
public:
   int operator[]( int );
};
vector v;
std::cout << v[0] << std::endl;

class matrix {
public:
   int operator()( int row, int column ); 
      // operator[] cannot be overloaded with more than 1 argument
};
matrix m;
std::cout << m( 3,4 ) << std::endl;

Существуют и другие варианты перегрузки операторов. В частности, operator, может быть перегружен действительно причудливыми способами для целей метапрограммирования, но это, вероятно, намного сложнее, чем то, что вам действительно нужно сейчас.

0 голосов
/ 09 марта 2010

Перегрузка оператора позволяет придать оператору собственный смысл. Например, рассмотрим следующий фрагмент кода:

char * str1 = "String1"; char * str2 = "String2"; char str3 [20];

str3 = str1 + str2;

Вы можете перегрузить оператор "+", чтобы объединить две строки. Разве это не выглядит более дружественным к программисту?

0 голосов
/ 09 марта 2010

Еще одно применение перегрузки операторов, AFAIK, уникальное для C ++, - это возможность перегрузки оператора присваивания. Если у вас есть:

class CVector
{
    // ...
    private:
        size_t  capacity;
        size_t  length;
        double* data;
};

void func()
{
    CVector a, b;
    // ...
    a = b;
}

Тогда a.data и b.data будут указывать на одно и то же местоположение, и если вы измените a, вы также повлияет на b. Это, вероятно, не то, что вы хотите. Но вы можете написать:

CVector& CVector::operator=(const CVector& rhs)
{
    delete[] data;
    capacity = length = rhs.length;
    data = new double[length];
    memcpy(data, rhs.data, length * sizeof(double));
    return (*this);
}

и получите глубокую копию.

0 голосов
/ 09 марта 2010

Прежде чем начать, есть много операторов! Вот список всех операторов C ++: list .

С учетом вышесказанного перегрузка операторов в C ++ - это способ заставить определенный оператор вести себя определенным образом для объекта.

Например, если вы используете операторы увеличения / уменьшения (++ и -) для объекта, компилятор не поймет, что нужно увеличивать / уменьшать в объекте, потому что это не примитивный тип (int, чар, плавай ...). Вы должны определить соответствующее поведение для компилятора, чтобы понять, что вы имеете в виду. Перегрузка операторов в основном сообщает компилятору, что должно быть выполнено, когда с объектом используются операторы увеличения / уменьшения.

Кроме того, вы должны обратить внимание на тот факт, что существует постфиксное увеличение / уменьшение и префиксное увеличение / уменьшение, которое становится очень важным с понятием итераторов , и вы должны отметить, что синтаксис для перегрузки этих двух Тип операторов отличается друг от друга. Вот как вы можете перегрузить эти операторы: Перегрузка операторов приращения и убывания

0 голосов
/ 09 марта 2010

Перегруженный оператор - это когда вы используете оператор для работы с типами, которые C ++ «изначально» не поддерживает для этого оператора.

Например, вы обычно можете использовать двоичный оператор «+» для добавления числовых значений (числа с плавающей запятой, числа, двойные числа и т. Д.). Вы также можете добавить целочисленный тип к указателю - например:

char foo[] = "A few words";
char *p = &(foo[3]);     // Points to "e"
char *q = foo + 3;       // Also points to "e"

Но это все! Вы не можете делать больше с помощью двоичного оператора "+".

Однако перегрузка операторов позволяет делать то, что разработчики C ++ не встроили в язык - например, использовать оператор + для объединения строк - например:

std::string a("A short"), b(" string.");
std::string c = a + b;  // c is "A short string."

Как только вы обдумаете это, примеры из Википедии станут более понятными.

0 голосов
/ 09 марта 2010

Оператор будет "+", "-" или "+ =". Они выполняют различные методы на существующих объектах. Это на самом деле сводится к вызову метода. Помимо обычных вызовов методов, они выглядят гораздо более естественными для человека. Запись «1 + 2» выглядит более нормально и короче, чем «add (1,2)». Если вы перегружаете оператор, вы меняете метод, который он выполняет.

В вашем первом примере метод оператора "+" перегружен, так что вы можете использовать его для сложения векторов.

Я бы предложил скопировать первый пример в редактор и немного поиграть с ним. Как только вы поймете, что делает код, я бы предложил реализовать вычитание и умножение вектора.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...